From b16ea1e7bfd8e03eb98ced4e4e5630fbbbfd25d6 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 30 Jul 2023 23:10:08 +0900 Subject: [PATCH 001/355] [Add] update module(test) --- VRCT.py | 20 ++++++++++++++++++++ update.cmd | 15 +++++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 update.cmd diff --git a/VRCT.py b/VRCT.py index b3d3cd92..ef7f2230 100644 --- a/VRCT.py +++ b/VRCT.py @@ -1,7 +1,11 @@ from time import sleep from os import path as os_path +from os import makedirs as os_makedirs +import subprocess +from shutil import unpack_archive from json import load as json_load from json import dump as json_dump +from requests import get as requests_get from queue import Queue import tkinter as tk import customtkinter @@ -21,6 +25,8 @@ from audio_transcriber import AudioTranscriber from translation import Translator from notification import notification_xsoverlay_for_vrct +__version__ = "1.3.1" + class App(CTk): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -420,6 +426,20 @@ class App(CTk): # check osc started send_test_action() + # auto update + response = requests_get("https://api.github.com/repos/misyaguziya/VRCT/releases/latest") + tag_name = response.json()["tag_name"] + if tag_name != __version__: + url = response.json()["assets"][0]["browser_download_url"] + res = requests_get(url, stream=True) + os_makedirs("./tmp", exist_ok=True) + with open("./tmp/download.zip", 'wb') as file: + for chunk in res.iter_content(chunk_size=1024): + file.write(chunk) + unpack_archive('./tmp/download.zip', './tmp') + command = "update.cmd" + subprocess.Popen(command.split()) + def button_config_callback(self): self.foreground_stop() self.transcription_stop() diff --git a/update.cmd b/update.cmd new file mode 100644 index 00000000..6d5587d1 --- /dev/null +++ b/update.cmd @@ -0,0 +1,15 @@ +set "current_dir=%CD%" +taskkill /im VRCT.exe /F +timeout 2 +for /d /r %%i in (*) do ( + set "folder=%%i" + set "folder=!folder:%current_dir%\=!" + if "!folder!" neq "\tmp\" ( + rd /s /q "%%i" + ) +) +timeout 2 +xcopy /s /e /y ".\tmp\VRCT\*" ".\" +timeout 2 +for /f "delims=" %%i in ('dir /b /s /ad "%current_dir%\tmp"') do rd /s /q "%%i" +VRCT.exe \ No newline at end of file From 4286fe6aabf8f51c64f272f2fedd0d2560fdc93a Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 30 Jul 2023 23:52:45 +0900 Subject: [PATCH 002/355] =?UTF-8?q?[Update]=20=E8=87=AA=E5=8B=95=E6=9B=B4?= =?UTF-8?q?=E6=96=B0=E3=81=AF=E5=8D=B1=E9=99=BA=E3=81=AA=E3=81=AE=E3=81=A7?= =?UTF-8?q?=E3=82=A2=E3=83=83=E3=83=97=E3=83=87=E3=83=BC=E3=83=88=E3=81=AE?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E7=94=A8=E3=83=95=E3=83=A9=E3=82=B0=E3=81=AE?= =?UTF-8?q?=E3=81=BF=E3=81=AE=E5=AE=9F=E8=A3=85=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VRCT.py | 13 +++---------- update.cmd | 15 --------------- 2 files changed, 3 insertions(+), 25 deletions(-) delete mode 100644 update.cmd diff --git a/VRCT.py b/VRCT.py index ef7f2230..e7b0c545 100644 --- a/VRCT.py +++ b/VRCT.py @@ -88,6 +88,7 @@ class App(CTk): self.ENABLE_AUTO_CLEAR_CHATBOX = False self.ENABLE_OSC = False self.ENABLE_NOTICE_XSOVERLAY =False + self.UPDATE_FLAG = False # load config if os_path.isfile(self.PATH_CONFIG) is not False: @@ -426,19 +427,11 @@ class App(CTk): # check osc started send_test_action() - # auto update + # check update response = requests_get("https://api.github.com/repos/misyaguziya/VRCT/releases/latest") tag_name = response.json()["tag_name"] if tag_name != __version__: - url = response.json()["assets"][0]["browser_download_url"] - res = requests_get(url, stream=True) - os_makedirs("./tmp", exist_ok=True) - with open("./tmp/download.zip", 'wb') as file: - for chunk in res.iter_content(chunk_size=1024): - file.write(chunk) - unpack_archive('./tmp/download.zip', './tmp') - command = "update.cmd" - subprocess.Popen(command.split()) + self.UPDATE_FLAG = True def button_config_callback(self): self.foreground_stop() diff --git a/update.cmd b/update.cmd deleted file mode 100644 index 6d5587d1..00000000 --- a/update.cmd +++ /dev/null @@ -1,15 +0,0 @@ -set "current_dir=%CD%" -taskkill /im VRCT.exe /F -timeout 2 -for /d /r %%i in (*) do ( - set "folder=%%i" - set "folder=!folder:%current_dir%\=!" - if "!folder!" neq "\tmp\" ( - rd /s /q "%%i" - ) -) -timeout 2 -xcopy /s /e /y ".\tmp\VRCT\*" ".\" -timeout 2 -for /f "delims=" %%i in ('dir /b /s /ad "%current_dir%\tmp"') do rd /s /q "%%i" -VRCT.exe \ No newline at end of file From 115cf8fab99f377b9ac5f1ac5b26115ac0ea5508 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Fri, 11 Aug 2023 01:53:11 +0900 Subject: [PATCH 003/355] =?UTF-8?q?[Update]=20=E9=9F=B3=E5=A3=B0=E8=AA=8D?= =?UTF-8?q?=E8=AD=98=E3=81=AEtranscribe=5Faudio=5Fqueue=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=AB=E6=99=82=E3=81=ABlanguage=E3=82=92?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VRCT.py | 6 ++---- audio_transcriber.py | 7 +++---- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/VRCT.py b/VRCT.py index b3d3cd92..647d088c 100644 --- a/VRCT.py +++ b/VRCT.py @@ -467,12 +467,11 @@ class App(CTk): self.mic_transcriber = AudioTranscriber( speaker=False, source=self.mic_audio_recorder.source, - language=transcription_lang[self.INPUT_MIC_VOICE_LANGUAGE], phrase_timeout=self.INPUT_MIC_PHRASE_TIMEOUT, max_phrases=self.INPUT_MIC_MAX_PHRASES, ) def mic_transcript_to_chatbox(): - self.mic_transcriber.transcribe_audio_queue(self.mic_audio_queue) + self.mic_transcriber.transcribe_audio_queue(self.mic_audio_queue, transcription_lang[self.INPUT_MIC_VOICE_LANGUAGE]) message = self.mic_transcriber.get_transcript() if len(message) > 0: # word filter @@ -566,13 +565,12 @@ class App(CTk): self.spk_transcriber = AudioTranscriber( speaker=True, source=self.spk_audio_recorder.source, - language=transcription_lang[self.INPUT_SPEAKER_VOICE_LANGUAGE], phrase_timeout=self.INPUT_SPEAKER_PHRASE_TIMEOUT, max_phrases=self.INPUT_SPEAKER_MAX_PHRASES, ) def spk_transcript_to_textbox(): - self.spk_transcriber.transcribe_audio_queue(self.spk_audio_queue) + self.spk_transcriber.transcribe_audio_queue(self.spk_audio_queue, transcription_lang[self.INPUT_SPEAKER_VOICE_LANGUAGE]) message = self.spk_transcriber.get_transcript() if len(message) > 0: # translate diff --git a/audio_transcriber.py b/audio_transcriber.py index aadd6adf..94c858e7 100644 --- a/audio_transcriber.py +++ b/audio_transcriber.py @@ -9,9 +9,8 @@ PHRASE_TIMEOUT = 3 MAX_PHRASES = 10 class AudioTranscriber: - def __init__(self, speaker, source, language, phrase_timeout, max_phrases): + def __init__(self, speaker, source, phrase_timeout, max_phrases): self.speaker = speaker - self.language = language self.phrase_timeout = phrase_timeout self.max_phrases = max_phrases self.transcript_data = [] @@ -27,7 +26,7 @@ class AudioTranscriber: "process_data_func": self.process_speaker_data if speaker else self.process_speaker_data } - def transcribe_audio_queue(self, audio_queue): + def transcribe_audio_queue(self, audio_queue, language): # while True: audio, time_spoken = audio_queue.get() self.update_last_sample_and_phrase_status(audio, time_spoken) @@ -37,7 +36,7 @@ class AudioTranscriber: # fd, path = tempfile.mkstemp(suffix=".wav") # os.close(fd) audio_data = self.audio_sources["process_data_func"]() - text = self.audio_recognizer.recognize_google(audio_data, language=self.language) + text = self.audio_recognizer.recognize_google(audio_data, language=language) except Exception as e: pass finally: From 944346d608e854f8e7a08962d7bc7c346b3b98cc Mon Sep 17 00:00:00 2001 From: misygauziya Date: Fri, 11 Aug 2023 01:57:44 +0900 Subject: [PATCH 004/355] =?UTF-8?q?[bugfix]=20=5F=5Fversion=5F=5F=20?= =?UTF-8?q?=E3=82=921.3.1=20->=201.3.2=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VRCT.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/VRCT.py b/VRCT.py index e7b0c545..6f35ff41 100644 --- a/VRCT.py +++ b/VRCT.py @@ -25,7 +25,7 @@ from audio_transcriber import AudioTranscriber from translation import Translator from notification import notification_xsoverlay_for_vrct -__version__ = "1.3.1" +__version__ = "1.3.2" class App(CTk): def __init__(self, *args, **kwargs): From 83e2d4bed8453c7f7a937812cd7ee3e125844fd1 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Fri, 11 Aug 2023 02:17:25 +0900 Subject: [PATCH 005/355] [update] remove packages --- VRCT.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/VRCT.py b/VRCT.py index ca2d4b02..238cf878 100644 --- a/VRCT.py +++ b/VRCT.py @@ -1,8 +1,5 @@ from time import sleep from os import path as os_path -from os import makedirs as os_makedirs -import subprocess -from shutil import unpack_archive from json import load as json_load from json import dump as json_dump from requests import get as requests_get @@ -14,7 +11,7 @@ from PIL.Image import open as Image_open from flashtext import KeywordProcessor from threading import Thread -from utils import save_json, print_textbox, thread_fnc, get_localized_text, widget_main_window_label_setter +from utils import print_textbox, thread_fnc, get_localized_text, widget_main_window_label_setter from osc_tools import send_typing, send_message, send_test_action, receive_osc_parameters from window_config import ToplevelWindowConfig from window_information import ToplevelWindowInformation From b7896336fc52880444792f5d9855a8eb9e33355c Mon Sep 17 00:00:00 2001 From: misygauziya Date: Tue, 15 Aug 2023 08:03:48 +0900 Subject: [PATCH 006/355] [Add] config.py method getter/setter --- VRCT.py | 27 ++-- config.py | 389 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 399 insertions(+), 17 deletions(-) create mode 100644 config.py diff --git a/VRCT.py b/VRCT.py index 238cf878..a3c93ec9 100644 --- a/VRCT.py +++ b/VRCT.py @@ -36,10 +36,11 @@ class App(CTk): self.PATH_CONFIG = "./config.json" ## main window - self.ENABLE_TRANSLATION = False + # self.ENABLE_TRANSLATION = False # self.ENABLE_TRANSCRIPTION_SEND = False # self.ENABLE_TRANSCRIPTION_RECEIVE = False - self.ENABLE_FOREGROUND = False + # self.ENABLE_FOREGROUND = False + ## UI self.TRANSPARENCY = 100 self.APPEARANCE_THEME = "System" @@ -224,10 +225,6 @@ class App(CTk): with open(self.PATH_CONFIG, 'w') as fp: config = { - # "ENABLE_TRANSLATION": self.ENABLE_TRANSLATION, - # "ENABLE_TRANSCRIPTION_SEND": self.ENABLE_TRANSCRIPTION_SEND, - # "ENABLE_TRANSCRIPTION_RECEIVE": self.ENABLE_TRANSCRIPTION_RECEIVE, - # "ENABLE_FOREGROUND": self.ENABLE_FOREGROUND, "TRANSPARENCY": self.TRANSPARENCY, "APPEARANCE_THEME": self.APPEARANCE_THEME, "UI_SCALING": self.UI_SCALING, @@ -456,8 +453,7 @@ class App(CTk): self.information_window.focus() def checkbox_translation_callback(self): - self.ENABLE_TRANSLATION = self.checkbox_translation.get() - if self.ENABLE_TRANSLATION is True: + if self.checkbox_translation.get() is True: print_textbox(self.textbox_message_log, "Start translation", "INFO") print_textbox(self.textbox_message_system_log, "Start translation", "INFO") else: @@ -675,8 +671,7 @@ class App(CTk): th_transcription_receive_stop.start() def checkbox_foreground_callback(self): - self.ENABLE_FOREGROUND = self.checkbox_foreground.get() - if self.ENABLE_FOREGROUND: + if self.checkbox_foreground.get(): self.attributes("-topmost", True) print_textbox(self.textbox_message_log, "Start foreground", "INFO") print_textbox(self.textbox_message_system_log, "Start foreground", "INFO") @@ -686,24 +681,22 @@ class App(CTk): print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO") def foreground_start(self): - self.ENABLE_FOREGROUND = self.checkbox_foreground.get() - if self.ENABLE_FOREGROUND: + if self.checkbox_foreground.get(): self.attributes("-topmost", True) print_textbox(self.textbox_message_log, "Start foreground", "INFO") print_textbox(self.textbox_message_system_log, "Start foreground", "INFO") def foreground_stop(self): - if self.ENABLE_FOREGROUND: + if self.checkbox_foreground.get(): self.attributes("-topmost", False) print_textbox(self.textbox_message_log, "Stop foreground", "INFO") print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO") - self.ENABLE_FOREGROUND = False def entry_message_box_press_key_enter(self, event): # send OSC typing send_typing(False, self.OSC_IP_ADDRESS, self.OSC_PORT) - if self.ENABLE_FOREGROUND: + if self.checkbox_foreground.get(): self.attributes("-topmost", True) message = self.entry_message_box.get() @@ -746,7 +739,7 @@ class App(CTk): def entry_message_box_press_key_any(self, event): # send OSC typing send_typing(True, self.OSC_IP_ADDRESS, self.OSC_PORT) - if self.ENABLE_FOREGROUND: + if self.checkbox_foreground.get(): self.attributes("-topmost", False) if event.keysym != "??": @@ -757,7 +750,7 @@ class App(CTk): def entry_message_box_leave(self, event): # send OSC typing send_typing(False, self.OSC_IP_ADDRESS, self.OSC_PORT) - if self.ENABLE_FOREGROUND: + if self.checkbox_foreground.get(): self.attributes("-topmost", True) def delete_window(self): diff --git a/config.py b/config.py new file mode 100644 index 00000000..038a1714 --- /dev/null +++ b/config.py @@ -0,0 +1,389 @@ +from os import path as os_path +from json import load as json_load +from json import dump as json_dump +import tkinter as tk +from tkinter import font +from languages import transcription_lang, translators, translation_lang, selectable_languages +from audio_utils import get_input_device_list, get_output_device_list, get_default_input_device, get_default_output_device + +class Config: + def __init__(self): + self._PATH_CONFIG = "./config.json" + self._TRANSPARENCY = 100 + self._APPEARANCE_THEME = "System" + self._UI_SCALING = "100%" + self._FONT_FAMILY = "Yu Gothic UI" + self._UI_LANGUAGE = "en" + self._CHOICE_TRANSLATOR = translators[0] + self._INPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[0] + self._INPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[1] + self._OUTPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[1] + self._OUTPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[0] + self._CHOICE_MIC_HOST = get_default_input_device()["host"]["name"] + self._CHOICE_MIC_DEVICE = get_default_input_device()["device"]["name"] + self._INPUT_MIC_VOICE_LANGUAGE = list(transcription_lang.keys())[0] + self._INPUT_MIC_ENERGY_THRESHOLD = 300 + self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = True + self._INPUT_MIC_RECORD_TIMEOUT = 3 + self._INPUT_MIC_PHRASE_TIMEOUT = 3 + self._INPUT_MIC_MAX_PHRASES = 10 + self._INPUT_MIC_WORD_FILTER = [] + self._CHOICE_SPEAKER_DEVICE = get_default_output_device()["name"] + self._INPUT_SPEAKER_VOICE_LANGUAGE = list(transcription_lang.keys())[1] + self._INPUT_SPEAKER_ENERGY_THRESHOLD = 300 + self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = True + self._INPUT_SPEAKER_RECORD_TIMEOUT = 3 + self._INPUT_SPEAKER_PHRASE_TIMEOUT = 3 + self._INPUT_SPEAKER_MAX_PHRASES = 10 + self._OSC_IP_ADDRESS = "127.0.0.1" + self._OSC_PORT = 9000 + self._AUTH_KEYS = { + "DeepL(web)": None, + "DeepL(auth)": None, + "Bing(web)": None, + "Google(web)": None, + } + self._MESSAGE_FORMAT = "[message]([translation])" + self._ENABLE_AUTO_CLEAR_CHATBOX = False + self._ENABLE_OSC = False + self._ENABLE_NOTICE_XSOVERLAY = False + self._UPDATE_FLAG = False + self.load_config() + + @property + def PATH_CONFIG(self): + return self._PATH_CONFIG + + @property + def TRANSPARENCY(self): + return self._TRANSPARENCY + + @TRANSPARENCY.setter + def TRANSPARENCY(self, value): + if type(value) is int and 0 <= value <= 100: + self._TRANSPARENCY = value + + @property + def APPEARANCE_THEME(self): + return self._APPEARANCE_THEME + + @APPEARANCE_THEME.setter + def APPEARANCE_THEME(self, value): + if value in ["Light", "Dark", "System"]: + self._APPEARANCE_THEME = value + + @property + def UI_SCALING(self): + return self._UI_SCALING + + @UI_SCALING.setter + def UI_SCALING(self, value): + if value in ["80%", "90%", "100%", "110%", "120%"]: + self._UI_SCALING = value + + @property + def FONT_FAMILY(self): + return self._FONT_FAMILY + + @FONT_FAMILY.setter + def FONT_FAMILY(self, value): + root = tk.Tk() + root.withdraw() + if value in list(font.families()): + self._FONT_FAMILY = value + root.destroy() + + @property + def UI_LANGUAGE(self): + return self._UI_LANGUAGE + + @UI_LANGUAGE.setter + def UI_LANGUAGE(self, value): + if value in list(selectable_languages.keys()): + self._UI_LANGUAGE = value + + @property + def CHOICE_TRANSLATOR(self): + return self._CHOICE_TRANSLATOR + + @CHOICE_TRANSLATOR.setter + def CHOICE_TRANSLATOR(self, value): + if value in translators: + self._CHOICE_TRANSLATOR = value + + @property + def INPUT_SOURCE_LANG(self): + return self._INPUT_SOURCE_LANG + + @INPUT_SOURCE_LANG.setter + def INPUT_SOURCE_LANG(self, value): + if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): + self._INPUT_SOURCE_LANG = value + + @property + def INPUT_TARGET_LANG(self): + return self._INPUT_TARGET_LANG + + @INPUT_TARGET_LANG.setter + def INPUT_TARGET_LANG(self, value): + if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): + self._INPUT_TARGET_LANG = value + + @property + def OUTPUT_SOURCE_LANG(self): + return self._OUTPUT_SOURCE_LANG + + @OUTPUT_SOURCE_LANG.setter + def OUTPUT_SOURCE_LANG(self, value): + if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): + self._OUTPUT_SOURCE_LANG = value + + @property + def OUTPUT_TARGET_LANG(self): + return self._OUTPUT_TARGET_LANG + + @OUTPUT_TARGET_LANG.setter + def OUTPUT_TARGET_LANG(self, value): + if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): + self._OUTPUT_TARGET_LANG = value + + @property + def CHOICE_MIC_HOST(self): + return self._CHOICE_MIC_HOST + + @CHOICE_MIC_HOST.setter + def CHOICE_MIC_HOST(self, value): + if value in [host for host in get_input_device_list().keys()]: + self._CHOICE_MIC_HOST = value + + @property + def CHOICE_MIC_DEVICE(self): + return self._CHOICE_MIC_DEVICE + + @CHOICE_MIC_DEVICE.setter + def CHOICE_MIC_DEVICE(self, value): + if value in [device["name"] for device in get_input_device_list()[self.CHOICE_MIC_HOST]]: + self._CHOICE_MIC_DEVICE = value + + @property + def INPUT_MIC_VOICE_LANGUAGE(self): + return self._INPUT_MIC_VOICE_LANGUAGE + + @INPUT_MIC_VOICE_LANGUAGE.setter + def INPUT_MIC_VOICE_LANGUAGE(self, value): + if value in list(transcription_lang.keys()): + self._INPUT_MIC_VOICE_LANGUAGE = value + + @property + def INPUT_MIC_ENERGY_THRESHOLD(self): + return self._INPUT_MIC_ENERGY_THRESHOLD + + @INPUT_MIC_ENERGY_THRESHOLD.setter + def INPUT_MIC_ENERGY_THRESHOLD(self, value): + if type(value) is int: + self._INPUT_MIC_ENERGY_THRESHOLD = value + + @property + def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self): + return self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD + + @INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD.setter + def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self, value): + if type(value) is bool: + self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value + + @property + def INPUT_MIC_RECORD_TIMEOUT(self): + return self._INPUT_MIC_RECORD_TIMEOUT + + @INPUT_MIC_RECORD_TIMEOUT.setter + def INPUT_MIC_RECORD_TIMEOUT(self, value): + if type(value) is int: + self._INPUT_MIC_RECORD_TIMEOUT = value + + @property + def INPUT_MIC_PHRASE_TIMEOUT(self): + return self._INPUT_MIC_PHRASE_TIMEOUT + + @INPUT_MIC_PHRASE_TIMEOUT.setter + def INPUT_MIC_PHRASE_TIMEOUT(self, value): + if type(value) is int: + self._INPUT_MIC_PHRASE_TIMEOUT = value + + @property + def INPUT_MIC_MAX_PHRASES(self): + return self._INPUT_MIC_MAX_PHRASES + + @INPUT_MIC_MAX_PHRASES.setter + def INPUT_MIC_MAX_PHRASES(self, value): + if type(value) is int: + self._INPUT_MIC_MAX_PHRASES = value + + @property + def INPUT_MIC_WORD_FILTER(self): + return self._INPUT_MIC_WORD_FILTER + + @INPUT_MIC_WORD_FILTER.setter + def INPUT_MIC_WORD_FILTER(self, value): + if type(value) is list: + self._INPUT_MIC_WORD_FILTER = value + + @property + def CHOICE_SPEAKER_DEVICE(self): + return self._CHOICE_SPEAKER_DEVICE + + @CHOICE_SPEAKER_DEVICE.setter + def CHOICE_SPEAKER_DEVICE(self, value): + if value in [device["name"] for device in get_output_device_list()]: + speaker_device = [device for device in get_output_device_list() if device["name"] == value][0] + if get_default_output_device()["index"] == speaker_device["index"]: + self._CHOICE_SPEAKER_DEVICE = value + + @property + def INPUT_SPEAKER_VOICE_LANGUAGE(self): + return self._INPUT_SPEAKER_VOICE_LANGUAGE + + @INPUT_SPEAKER_VOICE_LANGUAGE.setter + def INPUT_SPEAKER_VOICE_LANGUAGE(self, value): + if value in list(transcription_lang.keys()): + self._INPUT_SPEAKER_VOICE_LANGUAGE = value + + @property + def INPUT_SPEAKER_ENERGY_THRESHOLD(self): + return self._INPUT_SPEAKER_ENERGY_THRESHOLD + + @INPUT_SPEAKER_ENERGY_THRESHOLD.setter + def INPUT_SPEAKER_ENERGY_THRESHOLD(self, value): + if type(value) is int: + self._INPUT_SPEAKER_ENERGY_THRESHOLD = value + + @property + def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self): + return self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD + + @INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD.setter + def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self, value): + if type(value) is bool: + self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value + + @property + def INPUT_SPEAKER_RECORD_TIMEOUT(self): + return self._INPUT_SPEAKER_RECORD_TIMEOUT + + @INPUT_SPEAKER_RECORD_TIMEOUT.setter + def INPUT_SPEAKER_RECORD_TIMEOUT(self, value): + if type(value) is int: + self._INPUT_SPEAKER_RECORD_TIMEOUT = value + + @property + def INPUT_SPEAKER_PHRASE_TIMEOUT(self): + return self._INPUT_SPEAKER_PHRASE_TIMEOUT + + @INPUT_SPEAKER_PHRASE_TIMEOUT.setter + def INPUT_SPEAKER_PHRASE_TIMEOUT(self, value): + if type(value) is int: + self._INPUT_SPEAKER_PHRASE_TIMEOUT = value + + @property + def INPUT_SPEAKER_MAX_PHRASES(self): + return self._INPUT_SPEAKER_MAX_PHRASES + + @INPUT_SPEAKER_MAX_PHRASES.setter + def INPUT_SPEAKER_MAX_PHRASES(self, value): + if type(value) is int: + self._INPUT_SPEAKER_MAX_PHRASES = value + + @property + def OSC_IP_ADDRESS(self): + return self._OSC_IP_ADDRESS + + @OSC_IP_ADDRESS.setter + def OSC_IP_ADDRESS(self, value): + if type(value) is str: + self._OSC_IP_ADDRESS = value + + @property + def OSC_PORT(self): + return self._OSC_PORT + + @OSC_PORT.setter + def OSC_PORT(self, value): + if type(value) is int: + self._OSC_PORT = value + + @property + def AUTH_KEYS(self): + return self._AUTH_KEYS + + @AUTH_KEYS.setter + def AUTH_KEYS(self, value): + if type(value) is dict and set(value.keys()) == set(self.AUTH_KEYS.keys()): + for key, value in value.items(): + if type(value) is str: + self._AUTH_KEYS[key] = value[key] + + @property + def MESSAGE_FORMAT(self): + return self._MESSAGE_FORMAT + + @MESSAGE_FORMAT.setter + def MESSAGE_FORMAT(self, value): + if type(value) is str: + self._MESSAGE_FORMAT = value + + @property + def ENABLE_AUTO_CLEAR_CHATBOX(self): + return self._ENABLE_AUTO_CLEAR_CHATBOX + + @ENABLE_AUTO_CLEAR_CHATBOX.setter + def ENABLE_AUTO_CLEAR_CHATBOX(self, value): + if type(value) is bool: + self._ENABLE_AUTO_CLEAR_CHATBOX = value + + @property + def ENABLE_OSC(self): + return self._ENABLE_OSC + + @ENABLE_OSC.setter + def ENABLE_OSC(self, value): + if type(value) is bool: + self._ENABLE_OSC = value + + @property + def ENABLE_NOTICE_XSOVERLAY(self): + return self._ENABLE_NOTICE_XSOVERLAY + + @ENABLE_NOTICE_XSOVERLAY.setter + def ENABLE_NOTICE_XSOVERLAY(self, value): + if type(value) is bool: + self._ENABLE_NOTICE_XSOVERLAY = value + + @property + def UPDATE_FLAG(self): + return self._UPDATE_FLAG + + @UPDATE_FLAG.setter + def UPDATE_FLAG(self, value): + if type(value) is bool: + self._UPDATE_FLAG = value + + def load_config(self): + if os_path.isfile(self.PATH_CONFIG) is not False: + with open(self.PATH_CONFIG, 'r') as fp: + config = json_load(fp) + + for key in config.keys(): + setattr(self, key, config[key]) + + with open(self.PATH_CONFIG, 'w') as fp: + setter_methods = [ + name for name, obj in vars(type(self)).items() + if isinstance(obj, property) and obj.fset is not None + ] + config = {} + for method in setter_methods: + config[method] = getattr(self, method) + json_dump(config, fp, indent=4) + +if __name__ == "__main__": + c = Config() \ No newline at end of file From 6ada555c5f37a47ce950768c75527ac058e56316 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Tue, 15 Aug 2023 10:42:26 +0900 Subject: [PATCH 007/355] [Update] config.py add save config.json --- config.py | 684 +++++++++++++++++++++++++++++------------------------- 1 file changed, 364 insertions(+), 320 deletions(-) diff --git a/config.py b/config.py index 038a1714..fa1a7ce1 100644 --- a/config.py +++ b/config.py @@ -1,13 +1,375 @@ +import inspect from os import path as os_path from json import load as json_load from json import dump as json_dump import tkinter as tk from tkinter import font +from utils import save_json from languages import transcription_lang, translators, translation_lang, selectable_languages from audio_utils import get_input_device_list, get_output_device_list, get_default_input_device, get_default_output_device class Config: - def __init__(self): + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super(Config, cls).__new__(cls) + cls._instance.init_config() + cls._instance.load_config() + return cls._instance + + @property + def PATH_CONFIG(self): + return self._PATH_CONFIG + + @property + def TRANSPARENCY(self): + return self._TRANSPARENCY + + @TRANSPARENCY.setter + def TRANSPARENCY(self, value): + if type(value) is int and 0 <= value <= 100: + self._TRANSPARENCY = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def APPEARANCE_THEME(self): + return self._APPEARANCE_THEME + + @APPEARANCE_THEME.setter + def APPEARANCE_THEME(self, value): + if value in ["Light", "Dark", "System"]: + self._APPEARANCE_THEME = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def UI_SCALING(self): + return self._UI_SCALING + + @UI_SCALING.setter + def UI_SCALING(self, value): + if value in ["80%", "90%", "100%", "110%", "120%"]: + self._UI_SCALING = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def FONT_FAMILY(self): + return self._FONT_FAMILY + + @FONT_FAMILY.setter + def FONT_FAMILY(self, value): + root = tk.Tk() + root.withdraw() + if value in list(font.families()): + self._FONT_FAMILY = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + root.destroy() + + @property + def UI_LANGUAGE(self): + return self._UI_LANGUAGE + + @UI_LANGUAGE.setter + def UI_LANGUAGE(self, value): + if value in list(selectable_languages.keys()): + self._UI_LANGUAGE = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def CHOICE_TRANSLATOR(self): + return self._CHOICE_TRANSLATOR + + @CHOICE_TRANSLATOR.setter + def CHOICE_TRANSLATOR(self, value): + if value in translators: + self._CHOICE_TRANSLATOR = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SOURCE_LANG(self): + return self._INPUT_SOURCE_LANG + + @INPUT_SOURCE_LANG.setter + def INPUT_SOURCE_LANG(self, value): + if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): + self._INPUT_SOURCE_LANG = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_TARGET_LANG(self): + return self._INPUT_TARGET_LANG + + @INPUT_TARGET_LANG.setter + def INPUT_TARGET_LANG(self, value): + if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): + self._INPUT_TARGET_LANG = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def OUTPUT_SOURCE_LANG(self): + return self._OUTPUT_SOURCE_LANG + + @OUTPUT_SOURCE_LANG.setter + def OUTPUT_SOURCE_LANG(self, value): + if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): + self._OUTPUT_SOURCE_LANG = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def OUTPUT_TARGET_LANG(self): + return self._OUTPUT_TARGET_LANG + + @OUTPUT_TARGET_LANG.setter + def OUTPUT_TARGET_LANG(self, value): + if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): + self._OUTPUT_TARGET_LANG = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def CHOICE_MIC_HOST(self): + return self._CHOICE_MIC_HOST + + @CHOICE_MIC_HOST.setter + def CHOICE_MIC_HOST(self, value): + if value in [host for host in get_input_device_list().keys()]: + self._CHOICE_MIC_HOST = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def CHOICE_MIC_DEVICE(self): + return self._CHOICE_MIC_DEVICE + + @CHOICE_MIC_DEVICE.setter + def CHOICE_MIC_DEVICE(self, value): + if value in [device["name"] for device in get_input_device_list()[self.CHOICE_MIC_HOST]]: + self._CHOICE_MIC_DEVICE = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_VOICE_LANGUAGE(self): + return self._INPUT_MIC_VOICE_LANGUAGE + + @INPUT_MIC_VOICE_LANGUAGE.setter + def INPUT_MIC_VOICE_LANGUAGE(self, value): + if value in list(transcription_lang.keys()): + self._INPUT_MIC_VOICE_LANGUAGE = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_ENERGY_THRESHOLD(self): + return self._INPUT_MIC_ENERGY_THRESHOLD + + @INPUT_MIC_ENERGY_THRESHOLD.setter + def INPUT_MIC_ENERGY_THRESHOLD(self, value): + if type(value) is int: + self._INPUT_MIC_ENERGY_THRESHOLD = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self): + return self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD + + @INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD.setter + def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self, value): + if type(value) is bool: + self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_RECORD_TIMEOUT(self): + return self._INPUT_MIC_RECORD_TIMEOUT + + @INPUT_MIC_RECORD_TIMEOUT.setter + def INPUT_MIC_RECORD_TIMEOUT(self, value): + if type(value) is int: + self._INPUT_MIC_RECORD_TIMEOUT = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_PHRASE_TIMEOUT(self): + return self._INPUT_MIC_PHRASE_TIMEOUT + + @INPUT_MIC_PHRASE_TIMEOUT.setter + def INPUT_MIC_PHRASE_TIMEOUT(self, value): + if type(value) is int: + self._INPUT_MIC_PHRASE_TIMEOUT = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_MAX_PHRASES(self): + return self._INPUT_MIC_MAX_PHRASES + + @INPUT_MIC_MAX_PHRASES.setter + def INPUT_MIC_MAX_PHRASES(self, value): + if type(value) is int: + self._INPUT_MIC_MAX_PHRASES = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_WORD_FILTER(self): + return self._INPUT_MIC_WORD_FILTER + + @INPUT_MIC_WORD_FILTER.setter + def INPUT_MIC_WORD_FILTER(self, value): + if type(value) is list: + self._INPUT_MIC_WORD_FILTER = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def CHOICE_SPEAKER_DEVICE(self): + return self._CHOICE_SPEAKER_DEVICE + + @CHOICE_SPEAKER_DEVICE.setter + def CHOICE_SPEAKER_DEVICE(self, value): + if value in [device["name"] for device in get_output_device_list()]: + speaker_device = [device for device in get_output_device_list() if device["name"] == value][0] + if get_default_output_device()["index"] == speaker_device["index"]: + self._CHOICE_SPEAKER_DEVICE = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SPEAKER_VOICE_LANGUAGE(self): + return self._INPUT_SPEAKER_VOICE_LANGUAGE + + @INPUT_SPEAKER_VOICE_LANGUAGE.setter + def INPUT_SPEAKER_VOICE_LANGUAGE(self, value): + if value in list(transcription_lang.keys()): + self._INPUT_SPEAKER_VOICE_LANGUAGE = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SPEAKER_ENERGY_THRESHOLD(self): + return self._INPUT_SPEAKER_ENERGY_THRESHOLD + + @INPUT_SPEAKER_ENERGY_THRESHOLD.setter + def INPUT_SPEAKER_ENERGY_THRESHOLD(self, value): + if type(value) is int: + self._INPUT_SPEAKER_ENERGY_THRESHOLD = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self): + return self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD + + @INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD.setter + def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self, value): + if type(value) is bool: + self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SPEAKER_RECORD_TIMEOUT(self): + return self._INPUT_SPEAKER_RECORD_TIMEOUT + + @INPUT_SPEAKER_RECORD_TIMEOUT.setter + def INPUT_SPEAKER_RECORD_TIMEOUT(self, value): + if type(value) is int: + self._INPUT_SPEAKER_RECORD_TIMEOUT = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SPEAKER_PHRASE_TIMEOUT(self): + return self._INPUT_SPEAKER_PHRASE_TIMEOUT + + @INPUT_SPEAKER_PHRASE_TIMEOUT.setter + def INPUT_SPEAKER_PHRASE_TIMEOUT(self, value): + if type(value) is int: + self._INPUT_SPEAKER_PHRASE_TIMEOUT = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SPEAKER_MAX_PHRASES(self): + return self._INPUT_SPEAKER_MAX_PHRASES + + @INPUT_SPEAKER_MAX_PHRASES.setter + def INPUT_SPEAKER_MAX_PHRASES(self, value): + if type(value) is int: + self._INPUT_SPEAKER_MAX_PHRASES = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def OSC_IP_ADDRESS(self): + return self._OSC_IP_ADDRESS + + @OSC_IP_ADDRESS.setter + def OSC_IP_ADDRESS(self, value): + if type(value) is str: + self._OSC_IP_ADDRESS = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def OSC_PORT(self): + return self._OSC_PORT + + @OSC_PORT.setter + def OSC_PORT(self, value): + if type(value) is int: + self._OSC_PORT = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def AUTH_KEYS(self): + return self._AUTH_KEYS + + @AUTH_KEYS.setter + def AUTH_KEYS(self, value): + if type(value) is dict and set(value.keys()) == set(self.AUTH_KEYS.keys()): + for key, value in value.items(): + if type(value) is str: + self._AUTH_KEYS[key] = value[key] + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.AUTH_KEYS) + + @property + def MESSAGE_FORMAT(self): + return self._MESSAGE_FORMAT + + @MESSAGE_FORMAT.setter + def MESSAGE_FORMAT(self, value): + if type(value) is str: + self._MESSAGE_FORMAT = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def ENABLE_AUTO_CLEAR_CHATBOX(self): + return self._ENABLE_AUTO_CLEAR_CHATBOX + + @ENABLE_AUTO_CLEAR_CHATBOX.setter + def ENABLE_AUTO_CLEAR_CHATBOX(self, value): + if type(value) is bool: + self._ENABLE_AUTO_CLEAR_CHATBOX = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def ENABLE_OSC(self): + return self._ENABLE_OSC + + @ENABLE_OSC.setter + def ENABLE_OSC(self, value): + if type(value) is bool: + self._ENABLE_OSC = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def ENABLE_NOTICE_XSOVERLAY(self): + return self._ENABLE_NOTICE_XSOVERLAY + + @ENABLE_NOTICE_XSOVERLAY.setter + def ENABLE_NOTICE_XSOVERLAY(self, value): + if type(value) is bool: + self._ENABLE_NOTICE_XSOVERLAY = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def UPDATE_FLAG(self): + return self._UPDATE_FLAG + + @UPDATE_FLAG.setter + def UPDATE_FLAG(self, value): + if type(value) is bool: + self._UPDATE_FLAG = value + save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + def init_config(self): self._PATH_CONFIG = "./config.json" self._TRANSPARENCY = 100 self._APPEARANCE_THEME = "System" @@ -48,324 +410,6 @@ class Config: self._ENABLE_OSC = False self._ENABLE_NOTICE_XSOVERLAY = False self._UPDATE_FLAG = False - self.load_config() - - @property - def PATH_CONFIG(self): - return self._PATH_CONFIG - - @property - def TRANSPARENCY(self): - return self._TRANSPARENCY - - @TRANSPARENCY.setter - def TRANSPARENCY(self, value): - if type(value) is int and 0 <= value <= 100: - self._TRANSPARENCY = value - - @property - def APPEARANCE_THEME(self): - return self._APPEARANCE_THEME - - @APPEARANCE_THEME.setter - def APPEARANCE_THEME(self, value): - if value in ["Light", "Dark", "System"]: - self._APPEARANCE_THEME = value - - @property - def UI_SCALING(self): - return self._UI_SCALING - - @UI_SCALING.setter - def UI_SCALING(self, value): - if value in ["80%", "90%", "100%", "110%", "120%"]: - self._UI_SCALING = value - - @property - def FONT_FAMILY(self): - return self._FONT_FAMILY - - @FONT_FAMILY.setter - def FONT_FAMILY(self, value): - root = tk.Tk() - root.withdraw() - if value in list(font.families()): - self._FONT_FAMILY = value - root.destroy() - - @property - def UI_LANGUAGE(self): - return self._UI_LANGUAGE - - @UI_LANGUAGE.setter - def UI_LANGUAGE(self, value): - if value in list(selectable_languages.keys()): - self._UI_LANGUAGE = value - - @property - def CHOICE_TRANSLATOR(self): - return self._CHOICE_TRANSLATOR - - @CHOICE_TRANSLATOR.setter - def CHOICE_TRANSLATOR(self, value): - if value in translators: - self._CHOICE_TRANSLATOR = value - - @property - def INPUT_SOURCE_LANG(self): - return self._INPUT_SOURCE_LANG - - @INPUT_SOURCE_LANG.setter - def INPUT_SOURCE_LANG(self, value): - if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): - self._INPUT_SOURCE_LANG = value - - @property - def INPUT_TARGET_LANG(self): - return self._INPUT_TARGET_LANG - - @INPUT_TARGET_LANG.setter - def INPUT_TARGET_LANG(self, value): - if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): - self._INPUT_TARGET_LANG = value - - @property - def OUTPUT_SOURCE_LANG(self): - return self._OUTPUT_SOURCE_LANG - - @OUTPUT_SOURCE_LANG.setter - def OUTPUT_SOURCE_LANG(self, value): - if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): - self._OUTPUT_SOURCE_LANG = value - - @property - def OUTPUT_TARGET_LANG(self): - return self._OUTPUT_TARGET_LANG - - @OUTPUT_TARGET_LANG.setter - def OUTPUT_TARGET_LANG(self, value): - if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): - self._OUTPUT_TARGET_LANG = value - - @property - def CHOICE_MIC_HOST(self): - return self._CHOICE_MIC_HOST - - @CHOICE_MIC_HOST.setter - def CHOICE_MIC_HOST(self, value): - if value in [host for host in get_input_device_list().keys()]: - self._CHOICE_MIC_HOST = value - - @property - def CHOICE_MIC_DEVICE(self): - return self._CHOICE_MIC_DEVICE - - @CHOICE_MIC_DEVICE.setter - def CHOICE_MIC_DEVICE(self, value): - if value in [device["name"] for device in get_input_device_list()[self.CHOICE_MIC_HOST]]: - self._CHOICE_MIC_DEVICE = value - - @property - def INPUT_MIC_VOICE_LANGUAGE(self): - return self._INPUT_MIC_VOICE_LANGUAGE - - @INPUT_MIC_VOICE_LANGUAGE.setter - def INPUT_MIC_VOICE_LANGUAGE(self, value): - if value in list(transcription_lang.keys()): - self._INPUT_MIC_VOICE_LANGUAGE = value - - @property - def INPUT_MIC_ENERGY_THRESHOLD(self): - return self._INPUT_MIC_ENERGY_THRESHOLD - - @INPUT_MIC_ENERGY_THRESHOLD.setter - def INPUT_MIC_ENERGY_THRESHOLD(self, value): - if type(value) is int: - self._INPUT_MIC_ENERGY_THRESHOLD = value - - @property - def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self): - return self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD - - @INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD.setter - def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self, value): - if type(value) is bool: - self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value - - @property - def INPUT_MIC_RECORD_TIMEOUT(self): - return self._INPUT_MIC_RECORD_TIMEOUT - - @INPUT_MIC_RECORD_TIMEOUT.setter - def INPUT_MIC_RECORD_TIMEOUT(self, value): - if type(value) is int: - self._INPUT_MIC_RECORD_TIMEOUT = value - - @property - def INPUT_MIC_PHRASE_TIMEOUT(self): - return self._INPUT_MIC_PHRASE_TIMEOUT - - @INPUT_MIC_PHRASE_TIMEOUT.setter - def INPUT_MIC_PHRASE_TIMEOUT(self, value): - if type(value) is int: - self._INPUT_MIC_PHRASE_TIMEOUT = value - - @property - def INPUT_MIC_MAX_PHRASES(self): - return self._INPUT_MIC_MAX_PHRASES - - @INPUT_MIC_MAX_PHRASES.setter - def INPUT_MIC_MAX_PHRASES(self, value): - if type(value) is int: - self._INPUT_MIC_MAX_PHRASES = value - - @property - def INPUT_MIC_WORD_FILTER(self): - return self._INPUT_MIC_WORD_FILTER - - @INPUT_MIC_WORD_FILTER.setter - def INPUT_MIC_WORD_FILTER(self, value): - if type(value) is list: - self._INPUT_MIC_WORD_FILTER = value - - @property - def CHOICE_SPEAKER_DEVICE(self): - return self._CHOICE_SPEAKER_DEVICE - - @CHOICE_SPEAKER_DEVICE.setter - def CHOICE_SPEAKER_DEVICE(self, value): - if value in [device["name"] for device in get_output_device_list()]: - speaker_device = [device for device in get_output_device_list() if device["name"] == value][0] - if get_default_output_device()["index"] == speaker_device["index"]: - self._CHOICE_SPEAKER_DEVICE = value - - @property - def INPUT_SPEAKER_VOICE_LANGUAGE(self): - return self._INPUT_SPEAKER_VOICE_LANGUAGE - - @INPUT_SPEAKER_VOICE_LANGUAGE.setter - def INPUT_SPEAKER_VOICE_LANGUAGE(self, value): - if value in list(transcription_lang.keys()): - self._INPUT_SPEAKER_VOICE_LANGUAGE = value - - @property - def INPUT_SPEAKER_ENERGY_THRESHOLD(self): - return self._INPUT_SPEAKER_ENERGY_THRESHOLD - - @INPUT_SPEAKER_ENERGY_THRESHOLD.setter - def INPUT_SPEAKER_ENERGY_THRESHOLD(self, value): - if type(value) is int: - self._INPUT_SPEAKER_ENERGY_THRESHOLD = value - - @property - def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self): - return self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD - - @INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD.setter - def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self, value): - if type(value) is bool: - self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value - - @property - def INPUT_SPEAKER_RECORD_TIMEOUT(self): - return self._INPUT_SPEAKER_RECORD_TIMEOUT - - @INPUT_SPEAKER_RECORD_TIMEOUT.setter - def INPUT_SPEAKER_RECORD_TIMEOUT(self, value): - if type(value) is int: - self._INPUT_SPEAKER_RECORD_TIMEOUT = value - - @property - def INPUT_SPEAKER_PHRASE_TIMEOUT(self): - return self._INPUT_SPEAKER_PHRASE_TIMEOUT - - @INPUT_SPEAKER_PHRASE_TIMEOUT.setter - def INPUT_SPEAKER_PHRASE_TIMEOUT(self, value): - if type(value) is int: - self._INPUT_SPEAKER_PHRASE_TIMEOUT = value - - @property - def INPUT_SPEAKER_MAX_PHRASES(self): - return self._INPUT_SPEAKER_MAX_PHRASES - - @INPUT_SPEAKER_MAX_PHRASES.setter - def INPUT_SPEAKER_MAX_PHRASES(self, value): - if type(value) is int: - self._INPUT_SPEAKER_MAX_PHRASES = value - - @property - def OSC_IP_ADDRESS(self): - return self._OSC_IP_ADDRESS - - @OSC_IP_ADDRESS.setter - def OSC_IP_ADDRESS(self, value): - if type(value) is str: - self._OSC_IP_ADDRESS = value - - @property - def OSC_PORT(self): - return self._OSC_PORT - - @OSC_PORT.setter - def OSC_PORT(self, value): - if type(value) is int: - self._OSC_PORT = value - - @property - def AUTH_KEYS(self): - return self._AUTH_KEYS - - @AUTH_KEYS.setter - def AUTH_KEYS(self, value): - if type(value) is dict and set(value.keys()) == set(self.AUTH_KEYS.keys()): - for key, value in value.items(): - if type(value) is str: - self._AUTH_KEYS[key] = value[key] - - @property - def MESSAGE_FORMAT(self): - return self._MESSAGE_FORMAT - - @MESSAGE_FORMAT.setter - def MESSAGE_FORMAT(self, value): - if type(value) is str: - self._MESSAGE_FORMAT = value - - @property - def ENABLE_AUTO_CLEAR_CHATBOX(self): - return self._ENABLE_AUTO_CLEAR_CHATBOX - - @ENABLE_AUTO_CLEAR_CHATBOX.setter - def ENABLE_AUTO_CLEAR_CHATBOX(self, value): - if type(value) is bool: - self._ENABLE_AUTO_CLEAR_CHATBOX = value - - @property - def ENABLE_OSC(self): - return self._ENABLE_OSC - - @ENABLE_OSC.setter - def ENABLE_OSC(self, value): - if type(value) is bool: - self._ENABLE_OSC = value - - @property - def ENABLE_NOTICE_XSOVERLAY(self): - return self._ENABLE_NOTICE_XSOVERLAY - - @ENABLE_NOTICE_XSOVERLAY.setter - def ENABLE_NOTICE_XSOVERLAY(self, value): - if type(value) is bool: - self._ENABLE_NOTICE_XSOVERLAY = value - - @property - def UPDATE_FLAG(self): - return self._UPDATE_FLAG - - @UPDATE_FLAG.setter - def UPDATE_FLAG(self, value): - if type(value) is bool: - self._UPDATE_FLAG = value def load_config(self): if os_path.isfile(self.PATH_CONFIG) is not False: @@ -386,4 +430,4 @@ class Config: json_dump(config, fp, indent=4) if __name__ == "__main__": - c = Config() \ No newline at end of file + instance = Config() \ No newline at end of file From 1231fbd83c56fcef6306670cccb2bc909712f34a Mon Sep 17 00:00:00 2001 From: misygauziya Date: Tue, 15 Aug 2023 11:45:37 +0900 Subject: [PATCH 008/355] =?UTF-8?q?[Update]=20config.py=E3=81=AE=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=82=92=E4=BB=96=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E3=81=AB=E9=81=A9=E5=BF=9C=E3=81=97=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VRCT.py | 346 ++++++----------------------------- config.py | 25 +-- window_config.py | 413 +++++++++++++++++++----------------------- window_information.py | 3 +- 4 files changed, 245 insertions(+), 542 deletions(-) diff --git a/VRCT.py b/VRCT.py index a3c93ec9..d52398eb 100644 --- a/VRCT.py +++ b/VRCT.py @@ -1,10 +1,7 @@ from time import sleep from os import path as os_path -from json import load as json_load -from json import dump as json_dump from requests import get as requests_get from queue import Queue -import tkinter as tk import customtkinter from customtkinter import CTk, CTkFrame, CTkCheckBox, CTkFont, CTkButton, CTkImage, CTkTabview, CTkTextbox, CTkEntry from PIL.Image import open as Image_open @@ -15,11 +12,12 @@ from utils import print_textbox, thread_fnc, get_localized_text, widget_main_win from osc_tools import send_typing, send_message, send_test_action, receive_osc_parameters from window_config import ToplevelWindowConfig from window_information import ToplevelWindowInformation -from languages import transcription_lang, translators, translation_lang, selectable_languages -from audio_utils import get_input_device_list, get_output_device_list, get_default_input_device, get_default_output_device +from languages import transcription_lang +from audio_utils import get_input_device_list, get_output_device_list from audio_recorder import SelectedMicRecorder, SelectedSpeakerRecorder from audio_transcriber import AudioTranscriber from translation import Translator +from config import config from notification import notification_xsoverlay_for_vrct __version__ = "1.3.2" @@ -32,238 +30,14 @@ class App(CTk): self.translator = Translator() self.keyword_processor = KeywordProcessor() - # init config - self.PATH_CONFIG = "./config.json" - - ## main window - # self.ENABLE_TRANSLATION = False - # self.ENABLE_TRANSCRIPTION_SEND = False - # self.ENABLE_TRANSCRIPTION_RECEIVE = False - # self.ENABLE_FOREGROUND = False - - ## UI - self.TRANSPARENCY = 100 - self.APPEARANCE_THEME = "System" - self.UI_SCALING = "100%" - self.FONT_FAMILY = "Yu Gothic UI" - self.UI_LANGUAGE = "en" - ## Translation - self.CHOICE_TRANSLATOR = translators[0] - self.INPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[0] - self.INPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[1] - self.OUTPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[1] - self.OUTPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[0] - ## Transcription Send - self.CHOICE_MIC_HOST = get_default_input_device()["host"]["name"] - self.CHOICE_MIC_DEVICE = get_default_input_device()["device"]["name"] - self.INPUT_MIC_VOICE_LANGUAGE = list(transcription_lang.keys())[0] - self.INPUT_MIC_ENERGY_THRESHOLD = 300 - self.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = True - self.INPUT_MIC_RECORD_TIMEOUT = 3 - self.INPUT_MIC_PHRASE_TIMEOUT = 3 - self.INPUT_MIC_MAX_PHRASES = 10 - self.INPUT_MIC_WORD_FILTER = [] - ## Transcription Receive - self.CHOICE_SPEAKER_DEVICE = get_default_output_device()["name"] - self.INPUT_SPEAKER_VOICE_LANGUAGE = list(transcription_lang.keys())[1] - self.INPUT_SPEAKER_ENERGY_THRESHOLD = 300 - self.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = True - self.INPUT_SPEAKER_RECORD_TIMEOUT = 3 - self.INPUT_SPEAKER_PHRASE_TIMEOUT = 3 - self.INPUT_SPEAKER_MAX_PHRASES = 10 - - ## Parameter - self.OSC_IP_ADDRESS = "127.0.0.1" - self.OSC_PORT = 9000 - self.AUTH_KEYS = { - "DeepL(web)": None, - "DeepL(auth)": None, - "Bing(web)": None, - "Google(web)": None, - } - self.MESSAGE_FORMAT = "[message]([translation])" - # Others - self.ENABLE_AUTO_CLEAR_CHATBOX = False - self.ENABLE_OSC = False - self.ENABLE_NOTICE_XSOVERLAY =False - self.UPDATE_FLAG = False - - # load config - if os_path.isfile(self.PATH_CONFIG) is not False: - with open(self.PATH_CONFIG, 'r') as fp: - config = json_load(fp) - # main window - # main windowは初期はすべてOFFにする - # if "ENABLE_TRANSLATION" in config.keys(): - # if type(config["ENABLE_TRANSLATION"]) is bool: - # self.ENABLE_TRANSLATION = config["ENABLE_TRANSLATION"] - - # 環境に依ってマイクとスピーカーを同時起動するとエラーが発生するため、起動時は強制的にOFFにする - # if "ENABLE_TRANSCRIPTION_SEND" in config.keys(): - # if type(config["ENABLE_TRANSCRIPTION_SEND"]) is bool: - # self.ENABLE_TRANSCRIPTION_SEND = config["ENABLE_TRANSCRIPTION_SEND"] - # if "ENABLE_TRANSCRIPTION_RECEIVE" in config.keys(): - # if type(config["ENABLE_TRANSCRIPTION_RECEIVE"]) is bool: - # self.ENABLE_TRANSCRIPTION_RECEIVE = config["ENABLE_TRANSCRIPTION_RECEIVE"] - - # if "ENABLE_FOREGROUND" in config.keys(): - # if type(config["ENABLE_FOREGROUND"]) is bool: - # self.ENABLE_FOREGROUND = config["ENABLE_FOREGROUND"] - - # tab ui - if "TRANSPARENCY" in config.keys(): - if type(config["TRANSPARENCY"]) is int: - if 0 <= config["TRANSPARENCY"] <= 100: - self.TRANSPARENCY = config["TRANSPARENCY"] - if "APPEARANCE_THEME" in config.keys(): - if config["APPEARANCE_THEME"] in ["Light", "Dark", "System"]: - self.APPEARANCE_THEME = config["APPEARANCE_THEME"] - if "UI_SCALING" in config.keys(): - if config["UI_SCALING"] in ["80%", "90%", "100%", "110%", "120%"]: - self.UI_SCALING = config["UI_SCALING"] - if "FONT_FAMILY" in config.keys(): - if config["FONT_FAMILY"] in list(tk.font.families()): - self.FONT_FAMILY = config["FONT_FAMILY"] - if "UI_LANGUAGE" in config.keys(): - if config["UI_LANGUAGE"] in list(selectable_languages.keys()): - self.UI_LANGUAGE = config["UI_LANGUAGE"] - - # translation - if "CHOICE_TRANSLATOR" in config.keys(): - if config["CHOICE_TRANSLATOR"] in list(self.translator.translator_status.keys()): - self.CHOICE_TRANSLATOR = config["CHOICE_TRANSLATOR"] - if "INPUT_SOURCE_LANG" in config.keys(): - if config["INPUT_SOURCE_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): - self.INPUT_SOURCE_LANG = config["INPUT_SOURCE_LANG"] - if "INPUT_TARGET_LANG" in config.keys(): - if config["INPUT_TARGET_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): - self.INPUT_TARGET_LANG = config["INPUT_TARGET_LANG"] - if "OUTPUT_SOURCE_LANG" in config.keys(): - if config["OUTPUT_SOURCE_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): - self.OUTPUT_SOURCE_LANG = config["OUTPUT_SOURCE_LANG"] - if "OUTPUT_TARGET_LANG" in config.keys(): - if config["OUTPUT_TARGET_LANG"] in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): - self.OUTPUT_TARGET_LANG = config["OUTPUT_TARGET_LANG"] - - # Transcription - if "CHOICE_MIC_HOST" in config.keys(): - if config["CHOICE_MIC_HOST"] in [host for host in get_input_device_list().keys()]: - self.CHOICE_MIC_HOST = config["CHOICE_MIC_HOST"] - if "CHOICE_MIC_DEVICE" in config.keys(): - if config["CHOICE_MIC_DEVICE"] in [device["name"] for device in get_input_device_list()[self.CHOICE_MIC_HOST]]: - self.CHOICE_MIC_DEVICE = config["CHOICE_MIC_DEVICE"] - if "INPUT_MIC_VOICE_LANGUAGE" in config.keys(): - if config["INPUT_MIC_VOICE_LANGUAGE"] in list(transcription_lang.keys()): - self.INPUT_MIC_VOICE_LANGUAGE = config["INPUT_MIC_VOICE_LANGUAGE"] - if "INPUT_MIC_ENERGY_THRESHOLD" in config.keys(): - if type(config["INPUT_MIC_ENERGY_THRESHOLD"]) is int: - self.INPUT_MIC_ENERGY_THRESHOLD = config["INPUT_MIC_ENERGY_THRESHOLD"] - if "INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD" in config.keys(): - if type(config["INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD"]) is bool: - self.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = config["INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD"] - if "INPUT_MIC_RECORD_TIMEOUT" in config.keys(): - if type(config["INPUT_MIC_RECORD_TIMEOUT"]) is int: - self.INPUT_MIC_RECORD_TIMEOUT = config["INPUT_MIC_RECORD_TIMEOUT"] - if "INPUT_MIC_PHRASE_TIMEOUT" in config.keys(): - if type(config["INPUT_MIC_PHRASE_TIMEOUT"]) is int: - self.INPUT_MIC_PHRASE_TIMEOUT = config["INPUT_MIC_PHRASE_TIMEOUT"] - if "INPUT_MIC_MAX_PHRASES" in config.keys(): - if type(config["INPUT_MIC_MAX_PHRASES"]) is int: - self.INPUT_MIC_MAX_PHRASES = config["INPUT_MIC_MAX_PHRASES"] - if "INPUT_MIC_WORD_FILTER" in config.keys(): - if type(config["INPUT_MIC_WORD_FILTER"]) is list: - self.INPUT_MIC_WORD_FILTER = config["INPUT_MIC_WORD_FILTER"] - - if "CHOICE_SPEAKER_DEVICE" in config.keys(): - if config["CHOICE_SPEAKER_DEVICE"] in [device["name"] for device in get_output_device_list()]: - speaker_device = [device for device in get_output_device_list() if device["name"] == config["CHOICE_SPEAKER_DEVICE"]][0] - if get_default_output_device()["index"] == speaker_device["index"]: - self.CHOICE_SPEAKER_DEVICE = config["CHOICE_SPEAKER_DEVICE"] - if "INPUT_SPEAKER_VOICE_LANGUAGE" in config.keys(): - if config["INPUT_SPEAKER_VOICE_LANGUAGE"] in list(transcription_lang.keys()): - self.INPUT_SPEAKER_VOICE_LANGUAGE = config["INPUT_SPEAKER_VOICE_LANGUAGE"] - if "INPUT_SPEAKER_ENERGY_THRESHOLD" in config.keys(): - if type(config["INPUT_SPEAKER_ENERGY_THRESHOLD"]) is int: - self.INPUT_SPEAKER_ENERGY_THRESHOLD = config["INPUT_SPEAKER_ENERGY_THRESHOLD"] - if "INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD" in config.keys(): - if type(config["INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD"]) is bool: - self.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config["INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD"] - if "INPUT_SPEAKER_RECORD_TIMEOUT" in config.keys(): - if type(config["INPUT_SPEAKER_RECORD_TIMEOUT"]) is int: - self.INPUT_SPEAKER_RECORD_TIMEOUT = config["INPUT_SPEAKER_RECORD_TIMEOUT"] - if "INPUT_SPEAKER_PHRASE_TIMEOUT" in config.keys(): - if type(config["INPUT_SPEAKER_PHRASE_TIMEOUT"]) is int: - self.INPUT_SPEAKER_PHRASE_TIMEOUT = config["INPUT_SPEAKER_PHRASE_TIMEOUT"] - if "INPUT_SPEAKER_MAX_PHRASES" in config.keys(): - if type(config["INPUT_SPEAKER_MAX_PHRASES"]) is int: - self.INPUT_MIC_MAX_PHRASES = config["INPUT_SPEAKER_MAX_PHRASES"] - - # Parameter - if "OSC_IP_ADDRESS" in config.keys(): - if type(config["OSC_IP_ADDRESS"]) is str: - self.OSC_IP_ADDRESS = config["OSC_IP_ADDRESS"] - if "OSC_PORT" in config.keys(): - if type(config["OSC_PORT"]) is int: - self.OSC_PORT = config["OSC_PORT"] - if "AUTH_KEYS" in config.keys(): - if type(config["AUTH_KEYS"]) is dict: - if set(config["AUTH_KEYS"].keys()) == set(self.AUTH_KEYS.keys()): - for key, value in config["AUTH_KEYS"].items(): - if type(value) is str: - self.AUTH_KEYS[key] = config["AUTH_KEYS"][key] - if "MESSAGE_FORMAT" in config.keys(): - if type(config["MESSAGE_FORMAT"]) is str: - self.MESSAGE_FORMAT = config["MESSAGE_FORMAT"] - - # Others - if "ENABLE_AUTO_CLEAR_CHATBOX" in config.keys(): - if type(config["ENABLE_AUTO_CLEAR_CHATBOX"]) is bool: - self.ENABLE_AUTO_CLEAR_CHATBOX = config["ENABLE_AUTO_CLEAR_CHATBOX"] - if "ENABLE_NOTICE_XSOVERLAY" in config.keys(): - if type(config["ENABLE_NOTICE_XSOVERLAY"]) is bool: - self.ENABLE_NOTICE_XSOVERLAY = config["ENABLE_NOTICE_XSOVERLAY"] - - with open(self.PATH_CONFIG, 'w') as fp: - config = { - "TRANSPARENCY": self.TRANSPARENCY, - "APPEARANCE_THEME": self.APPEARANCE_THEME, - "UI_SCALING": self.UI_SCALING, - "UI_LANGUAGE": self.UI_LANGUAGE, - "FONT_FAMILY": self.FONT_FAMILY, - "CHOICE_TRANSLATOR": self.CHOICE_TRANSLATOR, - "INPUT_SOURCE_LANG": self.INPUT_SOURCE_LANG, - "INPUT_TARGET_LANG": self.INPUT_TARGET_LANG, - "OUTPUT_SOURCE_LANG": self.OUTPUT_SOURCE_LANG, - "OUTPUT_TARGET_LANG": self.OUTPUT_TARGET_LANG, - "CHOICE_MIC_HOST": self.CHOICE_MIC_HOST, - "CHOICE_MIC_DEVICE": self.CHOICE_MIC_DEVICE, - "INPUT_MIC_VOICE_LANGUAGE": self.INPUT_MIC_VOICE_LANGUAGE, - "INPUT_MIC_ENERGY_THRESHOLD": self.INPUT_MIC_ENERGY_THRESHOLD, - "INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD": self.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, - "INPUT_MIC_RECORD_TIMEOUT": self.INPUT_MIC_RECORD_TIMEOUT, - "INPUT_MIC_PHRASE_TIMEOUT": self.INPUT_MIC_PHRASE_TIMEOUT, - "INPUT_MIC_MAX_PHRASES": self.INPUT_MIC_MAX_PHRASES, - "INPUT_MIC_WORD_FILTER": self.INPUT_MIC_WORD_FILTER, - "CHOICE_SPEAKER_DEVICE": self.CHOICE_SPEAKER_DEVICE, - "INPUT_SPEAKER_VOICE_LANGUAGE": self.INPUT_SPEAKER_VOICE_LANGUAGE, - "INPUT_SPEAKER_ENERGY_THRESHOLD": self.INPUT_SPEAKER_ENERGY_THRESHOLD, - "INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD": self.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, - "INPUT_SPEAKER_RECORD_TIMEOUT": self.INPUT_SPEAKER_RECORD_TIMEOUT, - "INPUT_SPEAKER_PHRASE_TIMEOUT": self.INPUT_SPEAKER_PHRASE_TIMEOUT, - "INPUT_SPEAKER_MAX_PHRASES": self.INPUT_SPEAKER_MAX_PHRASES, - "OSC_IP_ADDRESS": self.OSC_IP_ADDRESS, - "OSC_PORT": self.OSC_PORT, - "AUTH_KEYS": self.AUTH_KEYS, - "MESSAGE_FORMAT": self.MESSAGE_FORMAT, - "ENABLE_AUTO_CLEAR_CHATBOX": self.ENABLE_AUTO_CLEAR_CHATBOX, - "ENABLE_NOTICE_XSOVERLAY": self.ENABLE_NOTICE_XSOVERLAY, - } - json_dump(config, fp, indent=4) - ## set UI theme - customtkinter.set_appearance_mode(self.APPEARANCE_THEME) + customtkinter.set_appearance_mode(config.APPEARANCE_THEME) customtkinter.set_default_color_theme("blue") + ## flags + self.ENABLE_OSC = False + self.UPDATE_FLAG = False + # init main window self.iconbitmap(os_path.join(os_path.dirname(__file__), "img", "app.ico")) self.title("VRCT") @@ -286,7 +60,7 @@ class App(CTk): onvalue=True, offvalue=False, command=self.checkbox_translation_callback, - font=CTkFont(family=self.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.checkbox_translation.grid(row=0, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") @@ -297,7 +71,7 @@ class App(CTk): onvalue=True, offvalue=False, command=self.checkbox_transcription_send_callback, - font=CTkFont(family=self.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.checkbox_transcription_send.grid(row=1, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") @@ -308,7 +82,7 @@ class App(CTk): onvalue=True, offvalue=False, command=self.checkbox_transcription_receive_callback, - font=CTkFont(family=self.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.checkbox_transcription_receive.grid(row=2, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") @@ -319,7 +93,7 @@ class App(CTk): onvalue=True, offvalue=False, command=self.checkbox_foreground_callback, - font=CTkFont(family=self.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.checkbox_foreground.grid(row=3, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") @@ -344,7 +118,7 @@ class App(CTk): self.button_config.grid(row=5, column=1, padx=(5, 10), pady=(5, 5), sticky="wse") # load ui language data - language_yaml_data = get_localized_text(f"{self.UI_LANGUAGE}") + language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") # add tabview textbox self.add_tabview_logs(language_yaml_data) @@ -352,13 +126,13 @@ class App(CTk): self.entry_message_box = CTkEntry( self, placeholder_text="message", - font=CTkFont(family=self.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.entry_message_box.grid(row=1, column=1, columnspan=2, padx=5, pady=(5, 10), sticky="nsew") # set default values ## set translator - if self.translator.authentication(self.CHOICE_TRANSLATOR, self.AUTH_KEYS[self.CHOICE_TRANSLATOR]) is False: + if self.translator.authentication(config.CHOICE_TRANSLATOR, config.AUTH_KEYS[config.CHOICE_TRANSLATOR]) is False: # error update Auth key print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") @@ -392,7 +166,7 @@ class App(CTk): # self.checkbox_foreground.deselect() ## set word filter - for f in self.INPUT_MIC_WORD_FILTER: + for f in config.INPUT_MIC_WORD_FILTER: self.keyword_processor.add_keyword(f) ## set bind entry message box @@ -401,10 +175,10 @@ class App(CTk): self.entry_message_box.bind("", self.entry_message_box_leave) ## set transparency for main window - self.wm_attributes("-alpha", self.TRANSPARENCY/100) + self.wm_attributes("-alpha", config.TRANSPARENCY/100) ## set UI scale - new_scaling_float = int(self.UI_SCALING.replace("%", "")) / 100 + new_scaling_float = int(config.UI_SCALING.replace("%", "")) / 100 customtkinter.set_widget_scaling(new_scaling_float) # delete window @@ -462,22 +236,22 @@ class App(CTk): def transcription_send_start(self): self.mic_audio_queue = Queue() - mic_device = [device for device in get_input_device_list()[self.CHOICE_MIC_HOST] if device["name"] == self.CHOICE_MIC_DEVICE][0] + mic_device = [device for device in get_input_device_list()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] self.mic_audio_recorder = SelectedMicRecorder( mic_device, - self.INPUT_MIC_ENERGY_THRESHOLD, - self.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, - self.INPUT_MIC_RECORD_TIMEOUT, + config.INPUT_MIC_ENERGY_THRESHOLD, + config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, + config.INPUT_MIC_RECORD_TIMEOUT, ) self.mic_audio_recorder.record_into_queue(self.mic_audio_queue) self.mic_transcriber = AudioTranscriber( speaker=False, source=self.mic_audio_recorder.source, - phrase_timeout=self.INPUT_MIC_PHRASE_TIMEOUT, - max_phrases=self.INPUT_MIC_MAX_PHRASES, + phrase_timeout=config.INPUT_MIC_PHRASE_TIMEOUT, + max_phrases=config.INPUT_MIC_MAX_PHRASES, ) def mic_transcript_to_chatbox(): - self.mic_transcriber.transcribe_audio_queue(self.mic_audio_queue, transcription_lang[self.INPUT_MIC_VOICE_LANGUAGE]) + self.mic_transcriber.transcribe_audio_queue(self.mic_audio_queue, transcription_lang[config.INPUT_MIC_VOICE_LANGUAGE]) message = self.mic_transcriber.get_transcript() if len(message) > 0: # word filter @@ -489,23 +263,23 @@ class App(CTk): # translate if self.checkbox_translation.get() is False: voice_message = f"{message}" - elif self.translator.translator_status[self.CHOICE_TRANSLATOR] is False: + elif self.translator.translator_status[config.CHOICE_TRANSLATOR] is False: print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") voice_message = f"{message}" else: result = self.translator.translate( - translator_name=self.CHOICE_TRANSLATOR, - source_language=self.INPUT_SOURCE_LANG, - target_language=self.INPUT_TARGET_LANG, + translator_name=config.CHOICE_TRANSLATOR, + source_language=config.INPUT_SOURCE_LANG, + target_language=config.INPUT_TARGET_LANG, message=message ) - voice_message = self.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) + voice_message = config.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) if self.checkbox_transcription_send.get() is True: if self.ENABLE_OSC is True: # send OSC message - send_message(voice_message, self.OSC_IP_ADDRESS, self.OSC_PORT) + send_message(voice_message, config.OSC_IP_ADDRESS, config.OSC_PORT) else: print_textbox(self.textbox_message_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") print_textbox(self.textbox_message_system_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") @@ -560,48 +334,48 @@ class App(CTk): def transcription_receive_start(self): self.spk_audio_queue = Queue() - spk_device = [device for device in get_output_device_list() if device["name"] == self.CHOICE_SPEAKER_DEVICE][0] + spk_device = [device for device in get_output_device_list() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] self.spk_audio_recorder = SelectedSpeakerRecorder( spk_device, - self.INPUT_SPEAKER_ENERGY_THRESHOLD, - self.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, - self.INPUT_SPEAKER_RECORD_TIMEOUT, + config.INPUT_SPEAKER_ENERGY_THRESHOLD, + config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, + config.INPUT_SPEAKER_RECORD_TIMEOUT, ) self.spk_audio_recorder.record_into_queue(self.spk_audio_queue) self.spk_transcriber = AudioTranscriber( speaker=True, source=self.spk_audio_recorder.source, - phrase_timeout=self.INPUT_SPEAKER_PHRASE_TIMEOUT, - max_phrases=self.INPUT_SPEAKER_MAX_PHRASES, + phrase_timeout=config.INPUT_SPEAKER_PHRASE_TIMEOUT, + max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, ) def spk_transcript_to_textbox(): - self.spk_transcriber.transcribe_audio_queue(self.spk_audio_queue, transcription_lang[self.INPUT_SPEAKER_VOICE_LANGUAGE]) + self.spk_transcriber.transcribe_audio_queue(self.spk_audio_queue, transcription_lang[config.INPUT_SPEAKER_VOICE_LANGUAGE]) message = self.spk_transcriber.get_transcript() if len(message) > 0: # translate if self.checkbox_translation.get() is False: voice_message = f"{message}" - elif self.translator.translator_status[self.CHOICE_TRANSLATOR] is False: + elif self.translator.translator_status[config.CHOICE_TRANSLATOR] is False: print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") voice_message = f"{message}" else: result = self.translator.translate( - translator_name=self.CHOICE_TRANSLATOR, - source_language=self.OUTPUT_SOURCE_LANG, - target_language=self.OUTPUT_TARGET_LANG, + translator_name=config.CHOICE_TRANSLATOR, + source_language=config.OUTPUT_SOURCE_LANG, + target_language=config.OUTPUT_TARGET_LANG, message=message ) - voice_message = self.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) + voice_message = config.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) # send OSC message - # send_message(voice_message, self.OSC_IP_ADDRESS, self.OSC_PORT) + # send_message(voice_message, config.OSC_IP_ADDRESS, self.OSC_PORT) if self.checkbox_transcription_receive.get() is True: # update textbox message receive log print_textbox(self.textbox_message_log, f"{voice_message}", "RECEIVE") print_textbox(self.textbox_message_receive_log, f"{voice_message}", "RECEIVE") - if self.ENABLE_NOTICE_XSOVERLAY is True: + if config.ENABLE_NOTICE_XSOVERLAY is True: notification_xsoverlay_for_vrct(content=f"{voice_message}") self.spk_print_transcript = thread_fnc(spk_transcript_to_textbox) @@ -694,7 +468,7 @@ class App(CTk): def entry_message_box_press_key_enter(self, event): # send OSC typing - send_typing(False, self.OSC_IP_ADDRESS, self.OSC_PORT) + send_typing(False, config.OSC_IP_ADDRESS, config.OSC_PORT) if self.checkbox_foreground.get(): self.attributes("-topmost", True) @@ -704,22 +478,22 @@ class App(CTk): # translate if self.checkbox_translation.get() is False: chat_message = f"{message}" - elif self.translator.translator_status[self.CHOICE_TRANSLATOR] is False: + elif self.translator.translator_status[config.CHOICE_TRANSLATOR] is False: print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") chat_message = f"{message}" else: result = self.translator.translate( - translator_name=self.CHOICE_TRANSLATOR, - source_language=self.INPUT_SOURCE_LANG, - target_language=self.INPUT_TARGET_LANG, + translator_name=config.CHOICE_TRANSLATOR, + source_language=config.INPUT_SOURCE_LANG, + target_language=config.INPUT_TARGET_LANG, message=message ) - chat_message = self.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) + chat_message = config.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) # send OSC message if self.ENABLE_OSC is True: - send_message(chat_message, self.OSC_IP_ADDRESS, self.OSC_PORT) + send_message(chat_message, config.OSC_IP_ADDRESS, config.OSC_PORT) else: print_textbox(self.textbox_message_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") print_textbox(self.textbox_message_system_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") @@ -729,7 +503,7 @@ class App(CTk): print_textbox(self.textbox_message_send_log, f"{chat_message}", "SEND") # delete message in entry message box - if self.ENABLE_AUTO_CLEAR_CHATBOX is True: + if config.ENABLE_AUTO_CLEAR_CHATBOX is True: self.entry_message_box.delete(0, customtkinter.END) BREAK_KEYSYM_LIST = [ @@ -738,7 +512,7 @@ class App(CTk): ] def entry_message_box_press_key_any(self, event): # send OSC typing - send_typing(True, self.OSC_IP_ADDRESS, self.OSC_PORT) + send_typing(True, config.OSC_IP_ADDRESS, config.OSC_PORT) if self.checkbox_foreground.get(): self.attributes("-topmost", False) @@ -749,7 +523,7 @@ class App(CTk): def entry_message_box_leave(self, event): # send OSC typing - send_typing(False, self.OSC_IP_ADDRESS, self.OSC_PORT) + send_typing(False, config.OSC_IP_ADDRESS, config.OSC_PORT) if self.checkbox_foreground.get(): self.attributes("-topmost", True) @@ -776,7 +550,7 @@ class App(CTk): self.tabview_logs.add(main_tab_title_receive) self.tabview_logs.add(main_tab_title_system) self.tabview_logs.grid(row=0, column=1, padx=0, pady=0, sticky="nsew") - self.tabview_logs._segmented_button.configure(font=CTkFont(family=self.FONT_FAMILY)) + self.tabview_logs._segmented_button.configure(font=CTkFont(family=config.FONT_FAMILY)) self.tabview_logs._segmented_button.grid(sticky="W") self.tabview_logs.tab(main_tab_title_log).grid_rowconfigure(0, weight=1) self.tabview_logs.tab(main_tab_title_log).grid_columnconfigure(0, weight=1) @@ -791,7 +565,7 @@ class App(CTk): # add textbox message log self.textbox_message_log = CTkTextbox( self.tabview_logs.tab(main_tab_title_log), - font=CTkFont(family=self.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.textbox_message_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") self.textbox_message_log.configure(state='disabled') @@ -799,7 +573,7 @@ class App(CTk): # add textbox message send log self.textbox_message_send_log = CTkTextbox( self.tabview_logs.tab(main_tab_title_send), - font=CTkFont(family=self.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.textbox_message_send_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") self.textbox_message_send_log.configure(state='disabled') @@ -807,7 +581,7 @@ class App(CTk): # add textbox message receive log self.textbox_message_receive_log = CTkTextbox( self.tabview_logs.tab(main_tab_title_receive), - font=CTkFont(family=self.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.textbox_message_receive_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") self.textbox_message_receive_log.configure(state='disabled') @@ -815,7 +589,7 @@ class App(CTk): # add textbox message system log self.textbox_message_system_log = CTkTextbox( self.tabview_logs.tab(main_tab_title_system), - font=CTkFont(family=self.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.textbox_message_system_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") self.textbox_message_system_log.configure(state='disabled') diff --git a/config.py b/config.py index fa1a7ce1..8b2d3290 100644 --- a/config.py +++ b/config.py @@ -339,16 +339,6 @@ class Config: self._ENABLE_AUTO_CLEAR_CHATBOX = value save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property - def ENABLE_OSC(self): - return self._ENABLE_OSC - - @ENABLE_OSC.setter - def ENABLE_OSC(self, value): - if type(value) is bool: - self._ENABLE_OSC = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property def ENABLE_NOTICE_XSOVERLAY(self): return self._ENABLE_NOTICE_XSOVERLAY @@ -359,16 +349,6 @@ class Config: self._ENABLE_NOTICE_XSOVERLAY = value save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property - def UPDATE_FLAG(self): - return self._UPDATE_FLAG - - @UPDATE_FLAG.setter - def UPDATE_FLAG(self, value): - if type(value) is bool: - self._UPDATE_FLAG = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - def init_config(self): self._PATH_CONFIG = "./config.json" self._TRANSPARENCY = 100 @@ -407,9 +387,7 @@ class Config: } self._MESSAGE_FORMAT = "[message]([translation])" self._ENABLE_AUTO_CLEAR_CHATBOX = False - self._ENABLE_OSC = False self._ENABLE_NOTICE_XSOVERLAY = False - self._UPDATE_FLAG = False def load_config(self): if os_path.isfile(self.PATH_CONFIG) is not False: @@ -429,5 +407,4 @@ class Config: config[method] = getattr(self, method) json_dump(config, fp, indent=4) -if __name__ == "__main__": - instance = Config() \ No newline at end of file +config = Config() \ No newline at end of file diff --git a/window_config.py b/window_config.py index 346c4a03..ef18acf2 100644 --- a/window_config.py +++ b/window_config.py @@ -8,7 +8,8 @@ from customtkinter import CTkToplevel, CTkTabview, CTkFont, CTkLabel, CTkSlider, from flashtext import KeywordProcessor from threading import Thread -from utils import save_json, print_textbox, thread_fnc, get_localized_text, get_key_by_value, widget_config_window_label_setter +from config import config +from utils import print_textbox, thread_fnc, get_localized_text, get_key_by_value, widget_config_window_label_setter from audio_utils import get_input_device_list, get_output_device_list, get_default_output_device from audio_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder from languages import translation_lang, transcription_lang, selectable_languages @@ -42,7 +43,7 @@ class ToplevelWindowConfig(CTkToplevel): self.speaker_energy_plot_progressbar = None # load ui language data - language_yaml_data = get_localized_text(f"{self.parent.UI_LANGUAGE}") + language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") # add tabview config self.add_tabview_config(language_yaml_data, selectable_languages) # set all config window labels @@ -52,23 +53,20 @@ class ToplevelWindowConfig(CTkToplevel): def slider_transparency_callback(self, value): self.parent.wm_attributes("-alpha", value/100) - self.parent.TRANSPARENCY = value - save_json(self.parent.PATH_CONFIG, "TRANSPARENCY", self.parent.TRANSPARENCY) + config.TRANSPARENCY = value def optionmenu_appearance_theme_callback(self, choice): self.optionmenu_appearance_theme.set(choice) customtkinter.set_appearance_mode(choice) - self.parent.APPEARANCE_THEME = choice - save_json(self.parent.PATH_CONFIG, "APPEARANCE_THEME", self.parent.APPEARANCE_THEME) + config.APPEARANCE_THEME = choice def optionmenu_ui_scaling_callback(self, choice): self.optionmenu_ui_scaling.set(choice) new_scaling_float = int(choice.replace("%", "")) / 100 customtkinter.set_widget_scaling(new_scaling_float) - self.parent.UI_SCALING = choice - save_json(self.parent.PATH_CONFIG, "UI_SCALING", self.parent.UI_SCALING) + config.UI_SCALING = choice def optionmenu_font_family_callback(self, choice): self.optionmenu_font_family.set(choice) @@ -176,18 +174,15 @@ class ToplevelWindowConfig(CTkToplevel): except: pass - self.parent.FONT_FAMILY = choice - save_json(self.parent.PATH_CONFIG, "FONT_FAMILY", self.parent.FONT_FAMILY) + config.FONT_FAMILY = choice def optionmenu_ui_language_callback(self, choice): self.optionmenu_ui_language.set(choice) self.withdraw() - pre_language_yaml_data = get_localized_text(f"{self.parent.UI_LANGUAGE}") - self.parent.UI_LANGUAGE = get_key_by_value(selectable_languages, choice) - language_yaml_data = get_localized_text(f"{self.parent.UI_LANGUAGE}") - save_json(self.parent.PATH_CONFIG, "UI_LANGUAGE", self.parent.UI_LANGUAGE) - + pre_language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") + config.UI_LANGUAGE = get_key_by_value(selectable_languages, choice) + language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") # delete self.parent.delete_tabview_logs(pre_language_yaml_data) @@ -207,7 +202,7 @@ class ToplevelWindowConfig(CTkToplevel): def optionmenu_translation_translator_callback(self, choice): self.optionmenu_translation_translator.set(choice) - if self.parent.translator.authentication(choice, self.parent.AUTH_KEYS[choice]) is False: + if self.parent.translator.authentication(choice, config.AUTH_KEYS[choice]) is False: print_textbox(self.parent.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.parent.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") else: @@ -234,44 +229,30 @@ class ToplevelWindowConfig(CTkToplevel): self.scrollableDropdown_translation_output_target_language.configure( values=list(translation_lang[choice]["target"].keys())) - self.parent.CHOICE_TRANSLATOR = choice - self.parent.INPUT_SOURCE_LANG = list(translation_lang[choice]["source"].keys())[0] - self.parent.INPUT_TARGET_LANG = list(translation_lang[choice]["target"].keys())[1] - self.parent.OUTPUT_SOURCE_LANG = list(translation_lang[choice]["source"].keys())[1] - self.parent.OUTPUT_TARGET_LANG = list(translation_lang[choice]["target"].keys())[0] - save_json(self.parent.PATH_CONFIG, "CHOICE_TRANSLATOR", self.parent.CHOICE_TRANSLATOR) - save_json(self.parent.PATH_CONFIG, "INPUT_SOURCE_LANG", self.parent.INPUT_SOURCE_LANG) - save_json(self.parent.PATH_CONFIG, "INPUT_TARGET_LANG", self.parent.INPUT_TARGET_LANG) - save_json(self.parent.PATH_CONFIG, "OUTPUT_SOURCE_LANG", self.parent.OUTPUT_SOURCE_LANG) - save_json(self.parent.PATH_CONFIG, "OUTPUT_TARGET_LANG", self.parent.OUTPUT_TARGET_LANG) + config.CHOICE_TRANSLATOR = choice + config.INPUT_SOURCE_LANG = list(translation_lang[choice]["source"].keys())[0] + config.INPUT_TARGET_LANG = list(translation_lang[choice]["target"].keys())[1] + config.OUTPUT_SOURCE_LANG = list(translation_lang[choice]["source"].keys())[1] + config.OUTPUT_TARGET_LANG = list(translation_lang[choice]["target"].keys())[0] def optionmenu_translation_input_source_language_callback(self, choice): self.optionmenu_translation_input_source_language.set(choice) - - self.parent.INPUT_SOURCE_LANG = choice - save_json(self.parent.PATH_CONFIG, "INPUT_SOURCE_LANG", self.parent.INPUT_SOURCE_LANG) + config.INPUT_SOURCE_LANG = choice def optionmenu_translation_input_target_language_callback(self, choice): self.optionmenu_translation_input_target_language.set(choice) - - self.parent.INPUT_TARGET_LANG = choice - save_json(self.parent.PATH_CONFIG, "INPUT_TARGET_LANG", self.parent.INPUT_TARGET_LANG) + config.INPUT_TARGET_LANG = choice def optionmenu_translation_output_source_language_callback(self, choice): self.optionmenu_translation_output_source_language.set(choice) - - self.parent.OUTPUT_SOURCE_LANG = choice - save_json(self.parent.PATH_CONFIG, "OUTPUT_SOURCE_LANG", self.parent.OUTPUT_SOURCE_LANG) + config.OUTPUT_SOURCE_LANG = choice def optionmenu_translation_output_target_language_callback(self, choice): self.optionmenu_translation_output_target_language.set(choice) - - self.parent.OUTPUT_TARGET_LANG = choice - save_json(self.parent.PATH_CONFIG, "OUTPUT_TARGET_LANG", self.parent.OUTPUT_TARGET_LANG) + config.OUTPUT_TARGET_LANG = choice def optionmenu_input_mic_host_callback(self, choice): self.optionmenu_input_mic_host.set(choice) - self.optionmenu_input_mic_device.configure( values=[device["name"] for device in get_input_device_list()[choice]], variable=StringVar(value=[device["name"] for device in get_input_device_list()[choice]][0])) @@ -279,24 +260,18 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_input_mic_device.configure(values=[device["name"] for device in get_input_device_list()[choice]]) - self.parent.CHOICE_MIC_HOST = choice - self.parent.CHOICE_MIC_DEVICE = [device["name"] for device in get_input_device_list()[choice]][0] - save_json(self.parent.PATH_CONFIG, "CHOICE_MIC_HOST", self.parent.CHOICE_MIC_HOST) - save_json(self.parent.PATH_CONFIG, "CHOICE_MIC_DEVICE", self.parent.CHOICE_MIC_DEVICE) + config.CHOICE_MIC_HOST = choice + config.CHOICE_MIC_DEVICE = [device["name"] for device in get_input_device_list()[choice]][0] def optionmenu_input_mic_device_callback(self, choice): self.optionmenu_input_mic_device.set(choice) - - self.parent.CHOICE_MIC_DEVICE = choice - save_json(self.parent.PATH_CONFIG, "CHOICE_MIC_DEVICE", self.parent.CHOICE_MIC_DEVICE) + config.CHOICE_MIC_DEVICE = choice self.checkbox_input_mic_threshold_check.deselect() self.checkbox_input_mic_threshold_check_callback() def optionmenu_input_mic_voice_language_callback(self, choice): self.optionmenu_input_mic_voice_language.set(choice) - - self.parent.INPUT_MIC_VOICE_LANGUAGE = choice - save_json(self.parent.PATH_CONFIG, "INPUT_MIC_VOICE_LANGUAGE", self.parent.INPUT_MIC_VOICE_LANGUAGE) + config.INPUT_MIC_VOICE_LANGUAGE = choice def progressBar_input_mic_energy_plot(self): if self.mic_energy_queue.empty() is False: @@ -309,7 +284,7 @@ class ToplevelWindowConfig(CTkToplevel): def mic_threshold_check_start(self): self.mic_energy_queue = Queue() - mic_device = [device for device in get_input_device_list()[self.parent.CHOICE_MIC_HOST] if device["name"] == self.parent.CHOICE_MIC_DEVICE][0] + mic_device = [device for device in get_input_device_list()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] self.mic_energy_recorder = SelectedMicEnergyRecorder(mic_device) self.mic_energy_recorder.record_into_queue(self.mic_energy_queue) self.mic_energy_plot_progressbar = thread_fnc(self.progressBar_input_mic_energy_plot) @@ -341,55 +316,45 @@ class ToplevelWindowConfig(CTkToplevel): th_mic_threshold_check_stop.start() def slider_input_mic_energy_threshold_callback(self, value): - self.parent.INPUT_MIC_ENERGY_THRESHOLD = int(value) - save_json(self.parent.PATH_CONFIG, "INPUT_MIC_ENERGY_THRESHOLD", self.parent.INPUT_MIC_ENERGY_THRESHOLD) + config.INPUT_MIC_ENERGY_THRESHOLD = int(value) def checkbox_input_mic_dynamic_energy_threshold_callback(self): - value = self.checkbox_input_mic_dynamic_energy_threshold.get() - self.parent.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value - save_json(self.parent.PATH_CONFIG, "INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD", self.parent.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD) + config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = self.checkbox_input_mic_dynamic_energy_threshold.get() def entry_input_mic_record_timeout_callback(self, event): - self.parent.INPUT_MIC_RECORD_TIMEOUT = int(self.entry_input_mic_record_timeout.get()) - save_json(self.parent.PATH_CONFIG, "INPUT_MIC_RECORD_TIMEOUT", self.parent.INPUT_MIC_RECORD_TIMEOUT) + config.INPUT_MIC_RECORD_TIMEOUT = int(self.entry_input_mic_record_timeout.get()) def entry_input_mic_phrase_timeout_callback(self, event): - self.parent.INPUT_MIC_PHRASE_TIMEOUT = int(self.entry_input_mic_phrase_timeout.get()) - save_json(self.parent.PATH_CONFIG, "INPUT_MIC_PHRASE_TIMEOUT", self.parent.INPUT_MIC_PHRASE_TIMEOUT) + config.INPUT_MIC_PHRASE_TIMEOUT = int(self.entry_input_mic_phrase_timeout.get()) def entry_input_mic_max_phrases_callback(self, event): - self.parent.INPUT_MIC_MAX_PHRASES = int(self.entry_input_mic_max_phrases.get()) - save_json(self.parent.PATH_CONFIG, "INPUT_MIC_MAX_PHRASES", self.parent.INPUT_MIC_MAX_PHRASES) + config.INPUT_MIC_MAX_PHRASES = int(self.entry_input_mic_max_phrases.get()) def entry_input_mic_word_filters_callback(self, event): word_filter = self.entry_input_mic_word_filter.get() word_filter = [w.strip() for w in word_filter.split(",") if len(w.strip()) > 0] word_filter = ",".join(word_filter) if len(word_filter) > 0: - self.parent.INPUT_MIC_WORD_FILTER = word_filter.split(",") + config.INPUT_MIC_WORD_FILTER = word_filter.split(",") else: - self.parent.INPUT_MIC_WORD_FILTER = [] + config.INPUT_MIC_WORD_FILTER = [] self.parent.keyword_processor = KeywordProcessor() for f in self.parent.INPUT_MIC_WORD_FILTER: self.parent.keyword_processor.add_keyword(f) - save_json(self.parent.PATH_CONFIG, "INPUT_MIC_WORD_FILTER", self.parent.INPUT_MIC_WORD_FILTER) def optionmenu_input_speaker_device_callback(self, choice): speaker_device = [device for device in get_output_device_list() if device["name"] == choice][0] if get_default_output_device()["index"] == speaker_device["index"]: self.optionmenu_input_speaker_device.set(choice) - self.parent.CHOICE_SPEAKER_DEVICE = choice - save_json(self.parent.PATH_CONFIG, "CHOICE_SPEAKER_DEVICE", self.parent.CHOICE_SPEAKER_DEVICE) + config.CHOICE_SPEAKER_DEVICE = choice else: print_textbox(self.parent.textbox_message_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") print_textbox(self.parent.textbox_message_system_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") - self.optionmenu_input_speaker_device.configure(variable=StringVar(value=self.parent.CHOICE_SPEAKER_DEVICE)) + self.optionmenu_input_speaker_device.configure(variable=StringVar(value=config.CHOICE_SPEAKER_DEVICE)) def optionmenu_input_speaker_voice_language_callback(self, choice): self.optionmenu_input_speaker_voice_language.set(choice) - - self.parent.INPUT_SPEAKER_VOICE_LANGUAGE = choice - save_json(self.parent.PATH_CONFIG, "INPUT_SPEAKER_VOICE_LANGUAGE", self.parent.INPUT_SPEAKER_VOICE_LANGUAGE) + config.INPUT_SPEAKER_VOICE_LANGUAGE = choice def progressBar_input_speaker_energy_plot(self): if self.speaker_energy_queue.empty() is False: @@ -406,7 +371,7 @@ class ToplevelWindowConfig(CTkToplevel): self.speaker_energy_queue.put(energy) def speaker_threshold_check_start(self): - speaker_device = [device for device in get_output_device_list() if device["name"] == self.parent.CHOICE_SPEAKER_DEVICE][0] + speaker_device = [device for device in get_output_device_list() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] if get_default_output_device()["index"] == speaker_device["index"]: self.speaker_energy_queue = Queue() @@ -448,54 +413,41 @@ class ToplevelWindowConfig(CTkToplevel): th_speaker_threshold_check_stop.start() def slider_input_speaker_energy_threshold_callback(self, value): - self.parent.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) - save_json(self.parent.PATH_CONFIG, "INPUT_SPEAKER_ENERGY_THRESHOLD", self.parent.INPUT_SPEAKER_ENERGY_THRESHOLD) + config.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) def checkbox_input_speaker_dynamic_energy_threshold_callback(self): - value = self.checkbox_input_speaker_dynamic_energy_threshold.get() - self.parent.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value - save_json(self.parent.PATH_CONFIG, "INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD", self.parent.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD) + config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = self.checkbox_input_speaker_dynamic_energy_threshold.get() def entry_input_speaker_record_timeout_callback(self, event): - self.parent.INPUT_SPEAKER_RECORD_TIMEOUT = int(self.entry_input_speaker_record_timeout.get()) - save_json(self.parent.PATH_CONFIG, "INPUT_SPEAKER_RECORD_TIMEOUT", self.parent.INPUT_SPEAKER_RECORD_TIMEOUT) + config.INPUT_SPEAKER_RECORD_TIMEOUT = int(self.entry_input_speaker_record_timeout.get()) def entry_input_speaker_phrase_timeout_callback(self, event): - self.parent.INPUT_SPEAKER_PHRASE_TIMEOUT = int(self.entry_input_speaker_phrase_timeout.get()) - save_json(self.parent.PATH_CONFIG, "INPUT_SPEAKER_PHRASE_TIMEOUT", self.parent.INPUT_SPEAKER_PHRASE_TIMEOUT) + config.INPUT_SPEAKER_PHRASE_TIMEOUT = int(self.entry_input_speaker_phrase_timeout.get()) def entry_input_speaker_max_phrases_callback(self, event): - self.parent.INPUT_SPEAKER_MAX_PHRASES = int(self.entry_input_speaker_max_phrases.get()) - save_json(self.parent.PATH_CONFIG, "INPUT_SPEAKER_MAX_PHRASES", self.parent.INPUT_SPEAKER_MAX_PHRASES) + config.INPUT_SPEAKER_MAX_PHRASES = int(self.entry_input_speaker_max_phrases.get()) def entry_ip_address_callback(self, event): - self.parent.OSC_IP_ADDRESS = self.entry_ip_address.get() - save_json(self.parent.PATH_CONFIG, "OSC_IP_ADDRESS", self.parent.OSC_IP_ADDRESS) + config.OSC_IP_ADDRESS = self.entry_ip_address.get() def entry_port_callback(self, event): - self.parent.OSC_PORT = self.entry_port.get() - save_json(self.parent.PATH_CONFIG, "OSC_PORT", self.parent.OSC_PORT) + config.OSC_PORT = self.entry_port.get() def entry_authkey_callback(self, event): value = self.entry_authkey.get() if len(value) > 0: if self.parent.translator.authentication("DeepL(auth)", value) is True: - self.parent.AUTH_KEYS["DeepL(auth)"] = value - save_json(self.parent.PATH_CONFIG, "AUTH_KEYS", self.parent.AUTH_KEYS) + config.AUTH_KEYS["DeepL(auth)"] = value print_textbox(self.parent.textbox_message_log, "Auth key update completed", "INFO") print_textbox(self.parent.textbox_message_system_log, "Auth key update completed", "INFO") else: pass def checkbox_auto_clear_chatbox_callback(self): - value = self.checkbox_auto_clear_chatbox.get() - self.parent.ENABLE_AUTO_CLEAR_CHATBOX = value - save_json(self.parent.PATH_CONFIG, "ENABLE_AUTO_CLEAR_CHATBOX", self.parent.ENABLE_AUTO_CLEAR_CHATBOX) + config.ENABLE_AUTO_CLEAR_CHATBOX = self.checkbox_auto_clear_chatbox.get() def checkbox_notice_xsoverlay_callback(self): - value = self.checkbox_notice_xsoverlay.get() - self.parent.ENABLE_NOTICE_XSOVERLAY = value - save_json(self.parent.PATH_CONFIG, "ENABLE_NOTICE_XSOVERLAY", self.parent.ENABLE_NOTICE_XSOVERLAY) + config.ENABLE_NOTICE_XSOVERLAY = self.checkbox_notice_xsoverlay.get() def delete_window(self): self.checkbox_input_mic_threshold_check.deselect() @@ -522,8 +474,7 @@ class ToplevelWindowConfig(CTkToplevel): def entry_message_format_callback(self, event): value = self.entry_message_format.get() if len(value) > 0: - self.parent.MESSAGE_FORMAT = value - save_json(self.parent.PATH_CONFIG, "MESSAGE_FORMAT", self.parent.MESSAGE_FORMAT) + config.MESSAGE_FORMAT = value def delete_tabview_config(self, pre_language_yaml_data): self.tabview_config.delete(pre_language_yaml_data["config_tab_title_ui"]) @@ -554,7 +505,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription).grid_columnconfigure(1, weight=1) self.tabview_config.tab(config_tab_title_parameter).grid_columnconfigure(1, weight=1) self.tabview_config.tab(config_tab_title_others).grid_columnconfigure(1, weight=1) - self.tabview_config._segmented_button.configure(font=CTkFont(family=self.parent.FONT_FAMILY)) + self.tabview_config._segmented_button.configure(font=CTkFont(family=config.FONT_FAMILY)) self.tabview_config._segmented_button.grid(sticky="W") # tab UI @@ -566,7 +517,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_ui), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_transparency.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.slider_transparency = CTkSlider( @@ -574,7 +525,7 @@ class ToplevelWindowConfig(CTkToplevel): from_=50, to=100, command=self.slider_transparency_callback, - variable=DoubleVar(value=self.parent.TRANSPARENCY), + variable=DoubleVar(value=config.TRANSPARENCY), ) self.slider_transparency.grid(row=row, column=1, columnspan=1, padx=padx, pady=10, sticky="nsew") @@ -584,16 +535,16 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_ui), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_appearance_theme.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.optionmenu_appearance_theme = CTkOptionMenu( self.tabview_config.tab(config_tab_title_ui), values=["Light", "Dark", "System"], command=self.optionmenu_appearance_theme_callback, - variable=StringVar(value=self.parent.APPEARANCE_THEME), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + variable=StringVar(value=config.APPEARANCE_THEME), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_appearance_theme.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -605,7 +556,7 @@ class ToplevelWindowConfig(CTkToplevel): justify="left", button_color="transparent", command=self.optionmenu_appearance_theme_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_appearance_theme.bind( "", @@ -618,16 +569,16 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_ui), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_ui_scaling.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.optionmenu_ui_scaling = CTkOptionMenu( self.tabview_config.tab(config_tab_title_ui), values=["80%", "90%", "100%", "110%", "120%"], command=self.optionmenu_ui_scaling_callback, - variable=StringVar(value=self.parent.UI_SCALING), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + variable=StringVar(value=config.UI_SCALING), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_ui_scaling.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -639,7 +590,7 @@ class ToplevelWindowConfig(CTkToplevel): justify="left", button_color="transparent", command=self.optionmenu_ui_scaling_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_ui_scaling.bind( "", @@ -652,7 +603,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_ui), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_font_family.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") font_families = list(tk_font.families()) @@ -660,9 +611,9 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_ui), values=font_families, command=self.optionmenu_font_family_callback, - variable=StringVar(value=self.parent.FONT_FAMILY), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + variable=StringVar(value=config.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_font_family.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -674,7 +625,7 @@ class ToplevelWindowConfig(CTkToplevel): justify="left", button_color="transparent", command=self.optionmenu_font_family_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_font_family.bind( "", @@ -687,7 +638,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_ui), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_ui_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") selectable_languages_values = list(selectable_languages.values()) @@ -695,9 +646,9 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_ui), values=selectable_languages_values, command=self.optionmenu_ui_language_callback, - variable=StringVar(value=selectable_languages[self.parent.UI_LANGUAGE]), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + variable=StringVar(value=selectable_languages[config.UI_LANGUAGE]), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_ui_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -709,7 +660,7 @@ class ToplevelWindowConfig(CTkToplevel): justify="left", button_color="transparent", command=self.optionmenu_ui_language_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_ui_language.bind( "", @@ -725,16 +676,16 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_translation), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.label_translation_translator.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.optionmenu_translation_translator = CTkOptionMenu( self.tabview_config.tab(config_tab_title_translation), values=list(self.parent.translator.translator_status.keys()), command=self.optionmenu_translation_translator_callback, - variable=StringVar(value=self.parent.CHOICE_TRANSLATOR), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + variable=StringVar(value=config.CHOICE_TRANSLATOR), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_translation_translator.grid(row=row, column=1, columnspan=3, padx=padx, pady=pady, sticky="nsew") @@ -746,7 +697,7 @@ class ToplevelWindowConfig(CTkToplevel): justify="left", button_color="transparent", command=self.optionmenu_translation_translator_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_translation_translator.bind( "", @@ -759,7 +710,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_translation), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_translation_input_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") @@ -767,10 +718,10 @@ class ToplevelWindowConfig(CTkToplevel): self.optionmenu_translation_input_source_language = CTkOptionMenu( self.tabview_config.tab(config_tab_title_translation), command=self.optionmenu_translation_input_source_language_callback, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["source"].keys()), - variable=StringVar(value=self.parent.INPUT_SOURCE_LANG), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), + variable=StringVar(value=config.INPUT_SOURCE_LANG), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_translation_input_source_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -778,11 +729,11 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_translation_input_source_language = CTkScrollableDropdown( self.optionmenu_translation_input_source_language, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["source"].keys()), + values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), justify="left", button_color="transparent", command=self.optionmenu_translation_input_source_language_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_translation_input_source_language.bind( "", @@ -794,7 +745,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_translation), text="-->", fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_translation_input_arrow.grid(row=row, column=2, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -802,10 +753,10 @@ class ToplevelWindowConfig(CTkToplevel): self.optionmenu_translation_input_target_language = CTkOptionMenu( self.tabview_config.tab(config_tab_title_translation), command=self.optionmenu_translation_input_target_language_callback, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["target"].keys()), - variable=StringVar(value=self.parent.INPUT_TARGET_LANG), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), + variable=StringVar(value=config.INPUT_TARGET_LANG), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_translation_input_target_language.grid(row=row, column=3, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -813,11 +764,11 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_translation_input_target_language = CTkScrollableDropdown( self.optionmenu_translation_input_target_language, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["target"].keys()), + values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), justify="left", button_color="transparent", command=self.optionmenu_translation_input_target_language_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_translation_input_target_language.bind( "", @@ -830,7 +781,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_translation), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_translation_output_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") @@ -838,10 +789,10 @@ class ToplevelWindowConfig(CTkToplevel): self.optionmenu_translation_output_source_language = CTkOptionMenu( self.tabview_config.tab(config_tab_title_translation), command=self.optionmenu_translation_output_source_language_callback, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["source"].keys()), - variable=StringVar(value=self.parent.OUTPUT_SOURCE_LANG), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), + variable=StringVar(value=config.OUTPUT_SOURCE_LANG), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_translation_output_source_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -849,11 +800,11 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_translation_output_source_language = CTkScrollableDropdown( self.optionmenu_translation_output_source_language, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["source"].keys()), + values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), justify="left", button_color="transparent", command=self.optionmenu_translation_output_source_language_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_translation_output_source_language.bind( "", @@ -865,7 +816,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_translation), text="-->", fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_translation_output_arrow.grid(row=row, column=2, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -873,10 +824,10 @@ class ToplevelWindowConfig(CTkToplevel): self.optionmenu_translation_output_target_language = CTkOptionMenu( self.tabview_config.tab(config_tab_title_translation), command=self.optionmenu_translation_output_target_language_callback, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["target"].keys()), - variable=StringVar(value=self.parent.OUTPUT_TARGET_LANG), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), + variable=StringVar(value=config.OUTPUT_TARGET_LANG), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_translation_output_target_language.grid(row=row, column=3, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -884,11 +835,11 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_translation_output_target_language = CTkScrollableDropdown( self.optionmenu_translation_output_target_language, - values=list(translation_lang[self.parent.CHOICE_TRANSLATOR]["target"].keys()), + values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), justify="left", button_color="transparent", command=self.optionmenu_translation_output_target_language_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_translation_output_target_language.bind( "", @@ -904,16 +855,16 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_mic_host.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.optionmenu_input_mic_host = CTkOptionMenu( self.tabview_config.tab(config_tab_title_transcription), values=[host for host in get_input_device_list().keys()], command=self.optionmenu_input_mic_host_callback, - variable=StringVar(value=self.parent.CHOICE_MIC_HOST), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + variable=StringVar(value=config.CHOICE_MIC_HOST), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_input_mic_host.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -925,7 +876,7 @@ class ToplevelWindowConfig(CTkToplevel): justify="left", button_color="transparent", command=self.optionmenu_input_mic_host_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_input_mic_host.bind( "", @@ -938,16 +889,16 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_mic_device.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.optionmenu_input_mic_device = CTkOptionMenu( self.tabview_config.tab(config_tab_title_transcription), - values=[device["name"] for device in get_input_device_list()[self.parent.CHOICE_MIC_HOST]], + values=[device["name"] for device in get_input_device_list()[config.CHOICE_MIC_HOST]], command=self.optionmenu_input_mic_device_callback, - variable=StringVar(value=self.parent.CHOICE_MIC_DEVICE), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + variable=StringVar(value=config.CHOICE_MIC_DEVICE), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_input_mic_device.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -955,11 +906,11 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_input_mic_device = CTkScrollableDropdown( self.optionmenu_input_mic_device, - values=[device["name"] for device in get_input_device_list()[self.parent.CHOICE_MIC_HOST]], + values=[device["name"] for device in get_input_device_list()[config.CHOICE_MIC_HOST]], justify="left", button_color="transparent", command=self.optionmenu_input_mic_device_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_input_mic_device.bind( "", @@ -972,16 +923,16 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_mic_voice_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.optionmenu_input_mic_voice_language = CTkOptionMenu( self.tabview_config.tab(config_tab_title_transcription), values=list(transcription_lang.keys()), command=self.optionmenu_input_mic_voice_language_callback, - variable=StringVar(value=self.parent.INPUT_MIC_VOICE_LANGUAGE), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + variable=StringVar(value=config.INPUT_MIC_VOICE_LANGUAGE), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_input_mic_voice_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -993,7 +944,7 @@ class ToplevelWindowConfig(CTkToplevel): justify="left", button_color="transparent", command=self.optionmenu_input_mic_voice_language_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_input_voice_language.bind( "", @@ -1006,7 +957,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_mic_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") @@ -1019,7 +970,7 @@ class ToplevelWindowConfig(CTkToplevel): button_corner_radius=3, number_of_steps=self.MAX_MIC_ENERGY_THRESHOLD, command=self.slider_input_mic_energy_threshold_callback, - variable=IntVar(value=self.parent.INPUT_MIC_ENERGY_THRESHOLD), + variable=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), ) self.slider_input_mic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=0, pady=5, sticky="nsew") @@ -1031,7 +982,7 @@ class ToplevelWindowConfig(CTkToplevel): onvalue=True, offvalue=False, command=self.checkbox_input_mic_threshold_check_callback, - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.checkbox_input_mic_threshold_check.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") @@ -1048,7 +999,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_mic_dynamic_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.checkbox_input_mic_dynamic_energy_threshold = CTkCheckBox( @@ -1057,10 +1008,10 @@ class ToplevelWindowConfig(CTkToplevel): onvalue=True, offvalue=False, command=self.checkbox_input_mic_dynamic_energy_threshold_callback, - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.checkbox_input_mic_dynamic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - if self.parent.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD is True: + if config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD is True: self.checkbox_input_mic_dynamic_energy_threshold.select() else: self.checkbox_input_mic_dynamic_energy_threshold.deselect() @@ -1071,13 +1022,13 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_mic_record_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.entry_input_mic_record_timeout = CTkEntry( self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=self.parent.INPUT_MIC_RECORD_TIMEOUT), - font=CTkFont(family=self.parent.FONT_FAMILY) + textvariable=StringVar(value=config.INPUT_MIC_RECORD_TIMEOUT), + font=CTkFont(family=config.FONT_FAMILY) ) self.entry_input_mic_record_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") self.entry_input_mic_record_timeout.bind("", self.entry_input_mic_record_timeout_callback) @@ -1088,13 +1039,13 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_mic_phrase_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.entry_input_mic_phrase_timeout = CTkEntry( self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=self.parent.INPUT_MIC_PHRASE_TIMEOUT), - font=CTkFont(family=self.parent.FONT_FAMILY) + textvariable=StringVar(value=config.INPUT_MIC_PHRASE_TIMEOUT), + font=CTkFont(family=config.FONT_FAMILY) ) self.entry_input_mic_phrase_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") self.entry_input_mic_phrase_timeout.bind("", self.entry_input_mic_phrase_timeout_callback) @@ -1105,13 +1056,13 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_mic_max_phrases.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.entry_input_mic_max_phrases = CTkEntry( self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=self.parent.INPUT_MIC_MAX_PHRASES), - font=CTkFont(family=self.parent.FONT_FAMILY) + textvariable=StringVar(value=config.INPUT_MIC_MAX_PHRASES), + font=CTkFont(family=config.FONT_FAMILY) ) self.entry_input_mic_max_phrases.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") self.entry_input_mic_max_phrases.bind("", self.entry_input_mic_max_phrases_callback) @@ -1122,18 +1073,18 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_mic_word_filter.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - if len(self.parent.INPUT_MIC_WORD_FILTER) > 0: - textvariable=StringVar(value=",".join(self.parent.INPUT_MIC_WORD_FILTER)) + if len(config.INPUT_MIC_WORD_FILTER) > 0: + textvariable=StringVar(value=",".join(config.INPUT_MIC_WORD_FILTER)) else: textvariable=None self.entry_input_mic_word_filter = CTkEntry( self.tabview_config.tab(config_tab_title_transcription), textvariable=textvariable, placeholder_text="AAA,BBB,CCC", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.entry_input_mic_word_filter.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") self.entry_input_mic_word_filter.bind("", self.entry_input_mic_word_filters_callback) @@ -1144,16 +1095,16 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_speaker_device.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.optionmenu_input_speaker_device = CTkOptionMenu( self.tabview_config.tab(config_tab_title_transcription), values=[device["name"] for device in get_output_device_list()], command=self.optionmenu_input_speaker_device_callback, - variable=StringVar(value=self.parent.CHOICE_SPEAKER_DEVICE), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + variable=StringVar(value=config.CHOICE_SPEAKER_DEVICE), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_input_speaker_device.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -1165,7 +1116,7 @@ class ToplevelWindowConfig(CTkToplevel): justify="left", button_color="transparent", command=self.optionmenu_input_speaker_device_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_input_speaker_device.bind( "", @@ -1178,16 +1129,16 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_speaker_voice_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.optionmenu_input_speaker_voice_language = CTkOptionMenu( self.tabview_config.tab(config_tab_title_transcription), values=list(transcription_lang.keys()), command=self.optionmenu_input_speaker_voice_language_callback, - variable=StringVar(value=self.parent.INPUT_SPEAKER_VOICE_LANGUAGE), - font=CTkFont(family=self.parent.FONT_FAMILY), - dropdown_font=CTkFont(family=self.parent.FONT_FAMILY), + variable=StringVar(value=config.INPUT_SPEAKER_VOICE_LANGUAGE), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), ) self.optionmenu_input_speaker_voice_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") @@ -1199,7 +1150,7 @@ class ToplevelWindowConfig(CTkToplevel): justify="left", button_color="transparent", command=self.optionmenu_input_speaker_voice_language_callback, - font=CTkFont(family=self.parent.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), ) self.scrollableDropdown_input_speaker_voice_language.bind( "", @@ -1212,7 +1163,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_speaker_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") @@ -1226,7 +1177,7 @@ class ToplevelWindowConfig(CTkToplevel): button_corner_radius=3, number_of_steps=self.MAX_SPEAKER_ENERGY_THRESHOLD, command=self.slider_input_speaker_energy_threshold_callback, - variable=IntVar(value=self.parent.INPUT_SPEAKER_ENERGY_THRESHOLD), + variable=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), ) self.slider_input_speaker_energy_threshold.grid(row=row, column=1, columnspan=1, padx=0, pady=5, sticky="nsew") @@ -1238,7 +1189,7 @@ class ToplevelWindowConfig(CTkToplevel): onvalue=True, offvalue=False, command=self.checkbox_input_speaker_threshold_check_callback, - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.checkbox_input_speaker_threshold_check.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") @@ -1255,7 +1206,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_speaker_dynamic_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.checkbox_input_speaker_dynamic_energy_threshold = CTkCheckBox( @@ -1264,10 +1215,10 @@ class ToplevelWindowConfig(CTkToplevel): onvalue=True, offvalue=False, command=self.checkbox_input_speaker_dynamic_energy_threshold_callback, - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.checkbox_input_speaker_dynamic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - if self.parent.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD is True: + if config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD is True: self.checkbox_input_speaker_dynamic_energy_threshold.select() else: self.checkbox_input_speaker_dynamic_energy_threshold.deselect() @@ -1278,13 +1229,13 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_speaker_record_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.entry_input_speaker_record_timeout = CTkEntry( self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=self.parent.INPUT_SPEAKER_RECORD_TIMEOUT), - font=CTkFont(family=self.parent.FONT_FAMILY) + textvariable=StringVar(value=config.INPUT_SPEAKER_RECORD_TIMEOUT), + font=CTkFont(family=config.FONT_FAMILY) ) self.entry_input_speaker_record_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") self.entry_input_speaker_record_timeout.bind("", self.entry_input_speaker_record_timeout_callback) @@ -1295,13 +1246,13 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_speaker_phrase_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.entry_input_speaker_phrase_timeout = CTkEntry( self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=self.parent.INPUT_SPEAKER_PHRASE_TIMEOUT), - font=CTkFont(family=self.parent.FONT_FAMILY) + textvariable=StringVar(value=config.INPUT_SPEAKER_PHRASE_TIMEOUT), + font=CTkFont(family=config.FONT_FAMILY) ) self.entry_input_speaker_phrase_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") self.entry_input_speaker_phrase_timeout.bind("", self.entry_input_speaker_phrase_timeout_callback) @@ -1312,13 +1263,13 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_transcription), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_input_speaker_max_phrases.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.entry_input_speaker_max_phrases = CTkEntry( self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=self.parent.INPUT_SPEAKER_MAX_PHRASES), - font=CTkFont(family=self.parent.FONT_FAMILY) + textvariable=StringVar(value=config.INPUT_SPEAKER_MAX_PHRASES), + font=CTkFont(family=config.FONT_FAMILY) ) self.entry_input_speaker_max_phrases.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") self.entry_input_speaker_max_phrases.bind("", self.entry_input_speaker_max_phrases_callback) @@ -1332,13 +1283,13 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_parameter), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_ip_address.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.entry_ip_address = CTkEntry( self.tabview_config.tab(config_tab_title_parameter), - textvariable=StringVar(value=self.parent.OSC_IP_ADDRESS), - font=CTkFont(family=self.parent.FONT_FAMILY) + textvariable=StringVar(value=config.OSC_IP_ADDRESS), + font=CTkFont(family=config.FONT_FAMILY) ) self.entry_ip_address.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") self.entry_ip_address.bind("", self.entry_ip_address_callback) @@ -1349,13 +1300,13 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_parameter), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_port.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.entry_port = CTkEntry( self.tabview_config.tab(config_tab_title_parameter), - textvariable=StringVar(value=self.parent.OSC_PORT), - font=CTkFont(family=self.parent.FONT_FAMILY) + textvariable=StringVar(value=config.OSC_PORT), + font=CTkFont(family=config.FONT_FAMILY) ) self.entry_port.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") self.entry_port.bind("", self.entry_port_callback) @@ -1366,13 +1317,13 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_parameter), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_authkey.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.entry_authkey = CTkEntry( self.tabview_config.tab(config_tab_title_parameter), - textvariable=StringVar(value=self.parent.AUTH_KEYS["DeepL(auth)"]), - font=CTkFont(family=self.parent.FONT_FAMILY) + textvariable=StringVar(value=config.AUTH_KEYS["DeepL(auth)"]), + font=CTkFont(family=config.FONT_FAMILY) ) self.entry_authkey.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") self.entry_authkey.bind("", self.entry_authkey_callback) @@ -1383,13 +1334,13 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_parameter), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_message_format.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.entry_message_format = CTkEntry( self.tabview_config.tab(config_tab_title_parameter), - textvariable=StringVar(value=self.parent.MESSAGE_FORMAT), - font=CTkFont(family=self.parent.FONT_FAMILY) + textvariable=StringVar(value=config.MESSAGE_FORMAT), + font=CTkFont(family=config.FONT_FAMILY) ) self.entry_message_format.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") self.entry_message_format.bind("", self.entry_message_format_callback) @@ -1401,7 +1352,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_others), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_checkbox_auto_clear_chatbox.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.checkbox_auto_clear_chatbox = CTkCheckBox( @@ -1410,10 +1361,10 @@ class ToplevelWindowConfig(CTkToplevel): onvalue=True, offvalue=False, command=self.checkbox_auto_clear_chatbox_callback, - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.checkbox_auto_clear_chatbox.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - if self.parent.ENABLE_AUTO_CLEAR_CHATBOX is True: + if config.ENABLE_AUTO_CLEAR_CHATBOX is True: self.checkbox_auto_clear_chatbox.select() else: self.checkbox_auto_clear_chatbox.deselect() @@ -1424,7 +1375,7 @@ class ToplevelWindowConfig(CTkToplevel): self.tabview_config.tab(config_tab_title_others), text=init_lang_text, fg_color="transparent", - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.label_checkbox_notice_xsoverlay.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.checkbox_notice_xsoverlay = CTkCheckBox( @@ -1433,10 +1384,10 @@ class ToplevelWindowConfig(CTkToplevel): onvalue=True, offvalue=False, command=self.checkbox_notice_xsoverlay_callback, - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.checkbox_notice_xsoverlay.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - if self.parent.ENABLE_NOTICE_XSOVERLAY is True: + if config.ENABLE_NOTICE_XSOVERLAY is True: self.checkbox_notice_xsoverlay.select() else: self.checkbox_notice_xsoverlay.deselect() diff --git a/window_information.py b/window_information.py index 8e912c0d..cc8d8792 100644 --- a/window_information.py +++ b/window_information.py @@ -1,5 +1,6 @@ import os from customtkinter import CTkToplevel, CTkTextbox, CTkFont +from config import config class ToplevelWindowInformation(CTkToplevel): def __init__(self, parent, *args, **kwargs): @@ -16,7 +17,7 @@ class ToplevelWindowInformation(CTkToplevel): # create textbox information self.textbox_information = CTkTextbox( self, - font=CTkFont(family=self.parent.FONT_FAMILY) + font=CTkFont(family=config.FONT_FAMILY) ) self.textbox_information.grid(row=0, column=0, padx=(10, 10), pady=(10, 10), sticky="nsew") textbox_information_message = """VRCT(v1.3.2) From 65d14278767c92e19267cb498bb7417c5a09a551 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Tue, 15 Aug 2023 12:44:06 +0900 Subject: [PATCH 009/355] =?UTF-8?q?[bugfix]=20config.py=20AUTH=5FKEYES?= =?UTF-8?q?=E3=81=AEsetter=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- window_config.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/config.py b/config.py index 8b2d3290..cf5075b2 100644 --- a/config.py +++ b/config.py @@ -316,7 +316,7 @@ class Config: if type(value) is dict and set(value.keys()) == set(self.AUTH_KEYS.keys()): for key, value in value.items(): if type(value) is str: - self._AUTH_KEYS[key] = value[key] + self._AUTH_KEYS[key] = value save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.AUTH_KEYS) @property diff --git a/window_config.py b/window_config.py index ef18acf2..8b28937e 100644 --- a/window_config.py +++ b/window_config.py @@ -437,7 +437,9 @@ class ToplevelWindowConfig(CTkToplevel): value = self.entry_authkey.get() if len(value) > 0: if self.parent.translator.authentication("DeepL(auth)", value) is True: - config.AUTH_KEYS["DeepL(auth)"] = value + auth_keys = config.AUTH_KEYS + auth_keys["DeepL(auth)"] = value + config.AUTH_KEYS = auth_keys print_textbox(self.parent.textbox_message_log, "Auth key update completed", "INFO") print_textbox(self.parent.textbox_message_system_log, "Auth key update completed", "INFO") else: From 65f5b6bc0edf12132f6ed8244e4fa39a295a8467 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Tue, 15 Aug 2023 22:43:08 +0900 Subject: [PATCH 010/355] [bugfix] remove adjust_for_noise() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ENERGY_THRESHOLD/DYNAMIC_ENERGY_THRESHOLDが機能していない問題を修正 --- audio_recorder.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/audio_recorder.py b/audio_recorder.py index ab03d78b..5d968062 100644 --- a/audio_recorder.py +++ b/audio_recorder.py @@ -32,7 +32,7 @@ class SelectedMicRecorder(BaseRecorder): sample_rate=int(device["defaultSampleRate"]), ) super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout) - self.adjust_for_noise() + # self.adjust_for_noise() class SelectedSpeakerRecorder(BaseRecorder): def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout): @@ -44,7 +44,7 @@ class SelectedSpeakerRecorder(BaseRecorder): channels=device["maxInputChannels"] ) super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout) - self.adjust_for_noise() + # self.adjust_for_noise() class BaseEnergyRecorder: def __init__(self, source): @@ -76,7 +76,7 @@ class SelectedMicEnergyRecorder(BaseEnergyRecorder): sample_rate=int(device["defaultSampleRate"]), ) super().__init__(source=source) - self.adjust_for_noise() + # self.adjust_for_noise() class SelectedSpeakeEnergyRecorder(BaseEnergyRecorder): def __init__(self, device): @@ -88,4 +88,4 @@ class SelectedSpeakeEnergyRecorder(BaseEnergyRecorder): channels=device["maxInputChannels"] ) super().__init__(source=source) - self.adjust_for_noise() \ No newline at end of file + # self.adjust_for_noise() \ No newline at end of file From 19b29b4955190f805f66dbf66fb63810feb887d0 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Wed, 16 Aug 2023 01:59:07 +0900 Subject: [PATCH 011/355] =?UTF-8?q?[Update]=20=E5=87=A6=E7=90=86=E3=82=92?= =?UTF-8?q?=E5=88=86=E5=89=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VRCT.py | 244 +++++++++++++++++++++++------------------------------- config.py | 25 ++++++ 2 files changed, 128 insertions(+), 141 deletions(-) diff --git a/VRCT.py b/VRCT.py index d52398eb..d62184f0 100644 --- a/VRCT.py +++ b/VRCT.py @@ -34,10 +34,6 @@ class App(CTk): customtkinter.set_appearance_mode(config.APPEARANCE_THEME) customtkinter.set_default_color_theme("blue") - ## flags - self.ENABLE_OSC = False - self.UPDATE_FLAG = False - # init main window self.iconbitmap(os_path.join(os_path.dirname(__file__), "img", "app.ico")) self.title("VRCT") @@ -45,82 +41,12 @@ class App(CTk): self.minsize(400, 175) self.grid_columnconfigure(1, weight=1) self.grid_rowconfigure(0, weight=1) + self.wm_attributes("-alpha", config.TRANSPARENCY/100) + customtkinter.set_widget_scaling(int(config.UI_SCALING.replace("%", "")) / 100) + self.protocol("WM_DELETE_WINDOW", self.delete_window) - # add sidebar left - self.sidebar_frame = CTkFrame(self, corner_radius=0) - self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsw") - self.sidebar_frame.grid_rowconfigure(5, weight=1) - - init_lang_text = "Loading..." - - # add checkbox translation - self.checkbox_translation = CTkCheckBox( - self.sidebar_frame, - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_translation_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_translation.grid(row=0, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") - - # add checkbox transcription send - self.checkbox_transcription_send = CTkCheckBox( - self.sidebar_frame, - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_transcription_send_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_transcription_send.grid(row=1, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") - - # add checkbox transcription receive - self.checkbox_transcription_receive = CTkCheckBox( - self.sidebar_frame, - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_transcription_receive_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_transcription_receive.grid(row=2, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") - - # add checkbox foreground - self.checkbox_foreground = CTkCheckBox( - self.sidebar_frame, - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_foreground_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_foreground.grid(row=3, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") - - # add button information - self.button_information = CTkButton( - self.sidebar_frame, - text=None, - width=36, - command=self.button_information_callback, - image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "info-icon-white.png"))) - ) - self.button_information.grid(row=5, column=0, padx=(10, 5), pady=(5, 5), sticky="wse") - - # add button config - self.button_config = CTkButton( - self.sidebar_frame, - text=None, - width=36, - command=self.button_config_callback, - image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "config-icon-white.png"))) - ) - self.button_config.grid(row=5, column=1, padx=(5, 10), pady=(5, 5), sticky="wse") - - # load ui language data - language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") - # add tabview textbox - self.add_tabview_logs(language_yaml_data) + # add sidebar + self.add_sidebar() # add entry message box self.entry_message_box = CTkEntry( @@ -129,64 +55,28 @@ class App(CTk): font=CTkFont(family=config.FONT_FAMILY), ) self.entry_message_box.grid(row=1, column=1, columnspan=2, padx=5, pady=(5, 10), sticky="nsew") + self.entry_message_box.bind("", self.entry_message_box_press_key_enter) + self.entry_message_box.bind("", self.entry_message_box_press_key_any) + self.entry_message_box.bind("", self.entry_message_box_leave) - # set default values - ## set translator + # add tabview textbox + self.add_tabview_logs(get_localized_text(f"{config.UI_LANGUAGE}")) + + self.config_window = ToplevelWindowConfig(self) + self.information_window = ToplevelWindowInformation(self) + self.init_process() + + def init_process(self): + # set translator if self.translator.authentication(config.CHOICE_TRANSLATOR, config.AUTH_KEYS[config.CHOICE_TRANSLATOR]) is False: # error update Auth key print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") - # ## set checkbox enable translation - # if self.ENABLE_TRANSLATION: - # self.checkbox_translation.select() - # self.checkbox_translation_callback() - # else: - # self.checkbox_translation.deselect() - - # ## set checkbox enable transcription send - # if self.ENABLE_TRANSCRIPTION_SEND: - # self.checkbox_transcription_send.select() - # self.checkbox_transcription_send_callback() - # else: - # self.checkbox_transcription_send.deselect() - - # ## set checkbox enable transcription receive - # if self.ENABLE_TRANSCRIPTION_RECEIVE: - # self.checkbox_transcription_receive.select() - # self.checkbox_transcription_receive_callback() - # else: - # self.checkbox_transcription_receive.deselect() - - # ## set set checkbox enable foreground - # if self.ENABLE_FOREGROUND: - # self.checkbox_foreground.select() - # self.checkbox_foreground_callback() - # else: - # self.checkbox_foreground.deselect() - - ## set word filter + # set word filter for f in config.INPUT_MIC_WORD_FILTER: self.keyword_processor.add_keyword(f) - ## set bind entry message box - self.entry_message_box.bind("", self.entry_message_box_press_key_enter) - self.entry_message_box.bind("", self.entry_message_box_press_key_any) - self.entry_message_box.bind("", self.entry_message_box_leave) - - ## set transparency for main window - self.wm_attributes("-alpha", config.TRANSPARENCY/100) - - ## set UI scale - new_scaling_float = int(config.UI_SCALING.replace("%", "")) / 100 - customtkinter.set_widget_scaling(new_scaling_float) - - # delete window - self.protocol("WM_DELETE_WINDOW", self.delete_window) - - self.config_window = ToplevelWindowConfig(self) - self.information_window = ToplevelWindowInformation(self) - # start receive osc th_receive_osc_parameters = Thread(target=receive_osc_parameters, args=(self.check_osc_receive,)) th_receive_osc_parameters.daemon = True @@ -196,10 +86,10 @@ class App(CTk): send_test_action() # check update - response = requests_get("https://api.github.com/repos/misyaguziya/VRCT/releases/latest") + response = requests_get(config.GITHUB_URL) tag_name = response.json()["tag_name"] if tag_name != __version__: - self.UPDATE_FLAG = True + config.UPDATE_FLAG = True def button_config_callback(self): self.foreground_stop() @@ -277,7 +167,7 @@ class App(CTk): voice_message = config.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) if self.checkbox_transcription_send.get() is True: - if self.ENABLE_OSC is True: + if config.ENABLE_OSC is True: # send OSC message send_message(voice_message, config.OSC_IP_ADDRESS, config.OSC_PORT) else: @@ -492,7 +382,7 @@ class App(CTk): chat_message = config.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) # send OSC message - if self.ENABLE_OSC is True: + if config.ENABLE_OSC is True: send_message(chat_message, config.OSC_IP_ADDRESS, config.OSC_PORT) else: print_textbox(self.textbox_message_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") @@ -531,6 +421,77 @@ class App(CTk): self.quit() self.destroy() + def add_sidebar(self): + init_lang_text = "Loading..." + self.sidebar_frame = CTkFrame(master=self, corner_radius=0) + + # add checkbox translation + self.checkbox_translation = CTkCheckBox( + self.sidebar_frame, + text=init_lang_text, + onvalue=True, + offvalue=False, + command=self.checkbox_translation_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + + # add checkbox transcription send + self.checkbox_transcription_send = CTkCheckBox( + self.sidebar_frame, + text=init_lang_text, + onvalue=True, + offvalue=False, + command=self.checkbox_transcription_send_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + + # add checkbox transcription receive + self.checkbox_transcription_receive = CTkCheckBox( + self.sidebar_frame, + text=init_lang_text, + onvalue=True, + offvalue=False, + command=self.checkbox_transcription_receive_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + + # add checkbox foreground + self.checkbox_foreground = CTkCheckBox( + self.sidebar_frame, + text=init_lang_text, + onvalue=True, + offvalue=False, + command=self.checkbox_foreground_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + + # add button information + self.button_information = CTkButton( + self.sidebar_frame, + text=None, + width=36, + command=self.button_information_callback, + image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "info-icon-white.png"))) + ) + + # add button config + self.button_config = CTkButton( + self.sidebar_frame, + text=None, + width=36, + command=self.button_config_callback, + image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "config-icon-white.png"))) + ) + + self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsw") + self.sidebar_frame.grid_rowconfigure(5, weight=1) + self.checkbox_translation.grid(row=0, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") + self.checkbox_transcription_send.grid(row=1, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") + self.checkbox_transcription_receive.grid(row=2, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") + self.checkbox_foreground.grid(row=3, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") + self.button_information.grid(row=5, column=0, padx=(10, 5), pady=(5, 5), sticky="wse") + self.button_config.grid(row=5, column=1, padx=(5, 10), pady=(5, 5), sticky="wse") + def delete_tabview_logs(self, pre_language_yaml_data): self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_log"]) self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_send"]) @@ -567,39 +528,40 @@ class App(CTk): self.tabview_logs.tab(main_tab_title_log), font=CTkFont(family=config.FONT_FAMILY) ) - self.textbox_message_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") - self.textbox_message_log.configure(state='disabled') # add textbox message send log self.textbox_message_send_log = CTkTextbox( self.tabview_logs.tab(main_tab_title_send), font=CTkFont(family=config.FONT_FAMILY) ) - self.textbox_message_send_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") - self.textbox_message_send_log.configure(state='disabled') # add textbox message receive log self.textbox_message_receive_log = CTkTextbox( self.tabview_logs.tab(main_tab_title_receive), font=CTkFont(family=config.FONT_FAMILY) ) - self.textbox_message_receive_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") - self.textbox_message_receive_log.configure(state='disabled') # add textbox message system log self.textbox_message_system_log = CTkTextbox( self.tabview_logs.tab(main_tab_title_system), font=CTkFont(family=config.FONT_FAMILY) ) + + self.textbox_message_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") + self.textbox_message_send_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") + self.textbox_message_receive_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") self.textbox_message_system_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") + self.textbox_message_log.configure(state='disabled') + self.textbox_message_send_log.configure(state='disabled') + self.textbox_message_receive_log.configure(state='disabled') self.textbox_message_system_log.configure(state='disabled') widget_main_window_label_setter(self, language_yaml_data) def check_osc_receive(self, address, osc_arguments): - if self.ENABLE_OSC is False: - self.ENABLE_OSC = True - # print(address, osc_arguments) + print(address, osc_arguments) + if config.ENABLE_OSC is False: + config.ENABLE_OSC = True if __name__ == "__main__": try: diff --git a/config.py b/config.py index cf5075b2..753cc7f2 100644 --- a/config.py +++ b/config.py @@ -349,6 +349,28 @@ class Config: self._ENABLE_NOTICE_XSOVERLAY = value save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property + def ENABLE_OSC(self): + return self._ENABLE_OSC + + @ENABLE_OSC.setter + def ENABLE_OSC(self, value): + if type(value) is bool: + self._ENABLE_OSC = value + + @property + def UPDATE_FLAG(self): + return self._UPDATE_FLAG + + @UPDATE_FLAG.setter + def UPDATE_FLAG(self, value): + if type(value) is bool: + self._UPDATE_FLAG = value + + @property + def GITHUB_URL(self): + return self._GITHUB_URL + def init_config(self): self._PATH_CONFIG = "./config.json" self._TRANSPARENCY = 100 @@ -388,6 +410,9 @@ class Config: self._MESSAGE_FORMAT = "[message]([translation])" self._ENABLE_AUTO_CLEAR_CHATBOX = False self._ENABLE_NOTICE_XSOVERLAY = False + self._ENABLE_OSC = False + self._UPDATE_FLAG = False + self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" def load_config(self): if os_path.isfile(self.PATH_CONFIG) is not False: From 027e9397d5bb60f29d84380ca1bd80ce3815f1b3 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Fri, 18 Aug 2023 15:39:57 +0900 Subject: [PATCH 012/355] =?UTF-8?q?[Update]=20=E8=A6=81=E7=B4=A0=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=82=92model.py=E3=82=92=E9=80=9A=E3=81=97=E3=81=A6?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VRCT.py | 240 +++++++------------------------------ config.py | 63 ++++++++++ model.py | 304 +++++++++++++++++++++++++++++++++++++++++++++++ window_config.py | 137 ++++++--------------- 4 files changed, 445 insertions(+), 299 deletions(-) create mode 100644 model.py diff --git a/VRCT.py b/VRCT.py index d62184f0..af03393a 100644 --- a/VRCT.py +++ b/VRCT.py @@ -1,35 +1,21 @@ from time import sleep from os import path as os_path -from requests import get as requests_get -from queue import Queue + import customtkinter from customtkinter import CTk, CTkFrame, CTkCheckBox, CTkFont, CTkButton, CTkImage, CTkTabview, CTkTextbox, CTkEntry from PIL.Image import open as Image_open -from flashtext import KeywordProcessor from threading import Thread -from utils import print_textbox, thread_fnc, get_localized_text, widget_main_window_label_setter -from osc_tools import send_typing, send_message, send_test_action, receive_osc_parameters +from utils import print_textbox, get_localized_text, widget_main_window_label_setter from window_config import ToplevelWindowConfig from window_information import ToplevelWindowInformation -from languages import transcription_lang -from audio_utils import get_input_device_list, get_output_device_list -from audio_recorder import SelectedMicRecorder, SelectedSpeakerRecorder -from audio_transcriber import AudioTranscriber -from translation import Translator from config import config -from notification import notification_xsoverlay_for_vrct - -__version__ = "1.3.2" +from model import model class App(CTk): def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) - # init instance - self.translator = Translator() - self.keyword_processor = KeywordProcessor() - ## set UI theme customtkinter.set_appearance_mode(config.APPEARANCE_THEME) customtkinter.set_default_color_theme("blue") @@ -68,28 +54,16 @@ class App(CTk): def init_process(self): # set translator - if self.translator.authentication(config.CHOICE_TRANSLATOR, config.AUTH_KEYS[config.CHOICE_TRANSLATOR]) is False: + if model.authenticationTranslator() is False: # error update Auth key print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") # set word filter - for f in config.INPUT_MIC_WORD_FILTER: - self.keyword_processor.add_keyword(f) + model.addKeywords() - # start receive osc - th_receive_osc_parameters = Thread(target=receive_osc_parameters, args=(self.check_osc_receive,)) - th_receive_osc_parameters.daemon = True - th_receive_osc_parameters.start() - - # check osc started - send_test_action() - - # check update - response = requests_get(config.GITHUB_URL) - tag_name = response.json()["tag_name"] - if tag_name != __version__: - config.UPDATE_FLAG = True + # check OSC started + model.oscCheck() def button_config_callback(self): self.foreground_stop() @@ -117,7 +91,8 @@ class App(CTk): self.information_window.focus() def checkbox_translation_callback(self): - if self.checkbox_translation.get() is True: + config.ENABLE_TRANSLATION = self.checkbox_translation.get() + if config.ENABLE_TRANSLATION is True: print_textbox(self.textbox_message_log, "Start translation", "INFO") print_textbox(self.textbox_message_system_log, "Start translation", "INFO") else: @@ -125,61 +100,7 @@ class App(CTk): print_textbox(self.textbox_message_system_log, "Stop translation", "INFO") def transcription_send_start(self): - self.mic_audio_queue = Queue() - mic_device = [device for device in get_input_device_list()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] - self.mic_audio_recorder = SelectedMicRecorder( - mic_device, - config.INPUT_MIC_ENERGY_THRESHOLD, - config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, - config.INPUT_MIC_RECORD_TIMEOUT, - ) - self.mic_audio_recorder.record_into_queue(self.mic_audio_queue) - self.mic_transcriber = AudioTranscriber( - speaker=False, - source=self.mic_audio_recorder.source, - phrase_timeout=config.INPUT_MIC_PHRASE_TIMEOUT, - max_phrases=config.INPUT_MIC_MAX_PHRASES, - ) - def mic_transcript_to_chatbox(): - self.mic_transcriber.transcribe_audio_queue(self.mic_audio_queue, transcription_lang[config.INPUT_MIC_VOICE_LANGUAGE]) - message = self.mic_transcriber.get_transcript() - if len(message) > 0: - # word filter - if len(self.keyword_processor.extract_keywords(message)) != 0: - print_textbox(self.textbox_message_log, f"Detect WordFilter :{message}", "INFO") - print_textbox(self.textbox_message_system_log, f"Detect WordFilter :{message}", "INFO") - return - - # translate - if self.checkbox_translation.get() is False: - voice_message = f"{message}" - elif self.translator.translator_status[config.CHOICE_TRANSLATOR] is False: - print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") - print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") - voice_message = f"{message}" - else: - result = self.translator.translate( - translator_name=config.CHOICE_TRANSLATOR, - source_language=config.INPUT_SOURCE_LANG, - target_language=config.INPUT_TARGET_LANG, - message=message - ) - voice_message = config.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) - - if self.checkbox_transcription_send.get() is True: - if config.ENABLE_OSC is True: - # send OSC message - send_message(voice_message, config.OSC_IP_ADDRESS, config.OSC_PORT) - else: - print_textbox(self.textbox_message_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") - print_textbox(self.textbox_message_system_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") - # update textbox message log - print_textbox(self.textbox_message_log, f"{voice_message}", "SEND") - print_textbox(self.textbox_message_send_log, f"{voice_message}", "SEND") - - self.mic_print_transcript = thread_fnc(mic_transcript_to_chatbox) - self.mic_print_transcript.daemon = True - self.mic_print_transcript.start() + model.startMicTranscript(self.textbox_message_log, self.textbox_message_send_log, self.textbox_message_system_log) print_textbox(self.textbox_message_log, "Start voice2chatbox", "INFO") print_textbox(self.textbox_message_system_log, "Start voice2chatbox", "INFO") self.checkbox_transcription_send.configure(state="normal") @@ -187,12 +108,7 @@ class App(CTk): self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) def transcription_send_stop(self): - if isinstance(self.mic_print_transcript, thread_fnc): - self.mic_print_transcript.stop() - if self.mic_audio_recorder.stop != None: - self.mic_audio_recorder.stop() - self.mic_audio_recorder.stop = None - + model.stopMicTranscript() print_textbox(self.textbox_message_log, "Stop voice2chatbox", "INFO") print_textbox(self.textbox_message_system_log, "Stop voice2chatbox", "INFO") self.checkbox_transcription_send.configure(state="normal") @@ -200,20 +116,16 @@ class App(CTk): self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) def transcription_send_stop_for_config(self): - if isinstance(self.mic_print_transcript, thread_fnc): - self.mic_print_transcript.stop() - if self.mic_audio_recorder.stop != None: - self.mic_audio_recorder.stop() - self.mic_audio_recorder.stop = None - + model.stopMicTranscript() print_textbox(self.textbox_message_log, "Stop voice2chatbox", "INFO") print_textbox(self.textbox_message_system_log, "Stop voice2chatbox", "INFO") def checkbox_transcription_send_callback(self): + config.ENABLE_TRANSCRIPTION_SEND = self.checkbox_transcription_send.get() self.checkbox_transcription_send.configure(state="disabled") self.checkbox_transcription_receive.configure(state="disabled") self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) - if self.checkbox_transcription_send.get() is True: + if config.ENABLE_TRANSCRIPTION_SEND is True: th_transcription_send_start = Thread(target=self.transcription_send_start) th_transcription_send_start.daemon = True th_transcription_send_start.start() @@ -223,54 +135,7 @@ class App(CTk): th_transcription_send_stop.start() def transcription_receive_start(self): - self.spk_audio_queue = Queue() - spk_device = [device for device in get_output_device_list() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] - self.spk_audio_recorder = SelectedSpeakerRecorder( - spk_device, - config.INPUT_SPEAKER_ENERGY_THRESHOLD, - config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, - config.INPUT_SPEAKER_RECORD_TIMEOUT, - ) - self.spk_audio_recorder.record_into_queue(self.spk_audio_queue) - self.spk_transcriber = AudioTranscriber( - speaker=True, - source=self.spk_audio_recorder.source, - phrase_timeout=config.INPUT_SPEAKER_PHRASE_TIMEOUT, - max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, - ) - - def spk_transcript_to_textbox(): - self.spk_transcriber.transcribe_audio_queue(self.spk_audio_queue, transcription_lang[config.INPUT_SPEAKER_VOICE_LANGUAGE]) - message = self.spk_transcriber.get_transcript() - if len(message) > 0: - # translate - if self.checkbox_translation.get() is False: - voice_message = f"{message}" - elif self.translator.translator_status[config.CHOICE_TRANSLATOR] is False: - print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") - print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") - voice_message = f"{message}" - else: - result = self.translator.translate( - translator_name=config.CHOICE_TRANSLATOR, - source_language=config.OUTPUT_SOURCE_LANG, - target_language=config.OUTPUT_TARGET_LANG, - message=message - ) - voice_message = config.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) - # send OSC message - # send_message(voice_message, config.OSC_IP_ADDRESS, self.OSC_PORT) - - if self.checkbox_transcription_receive.get() is True: - # update textbox message receive log - print_textbox(self.textbox_message_log, f"{voice_message}", "RECEIVE") - print_textbox(self.textbox_message_receive_log, f"{voice_message}", "RECEIVE") - if config.ENABLE_NOTICE_XSOVERLAY is True: - notification_xsoverlay_for_vrct(content=f"{voice_message}") - - self.spk_print_transcript = thread_fnc(spk_transcript_to_textbox) - self.spk_print_transcript.daemon = True - self.spk_print_transcript.start() + model.startSpeakerTranscript(self.textbox_message_log, self.textbox_message_receive_log, self.textbox_message_system_log) print_textbox(self.textbox_message_log, "Start speaker2log", "INFO") print_textbox(self.textbox_message_system_log, "Start speaker2log", "INFO") self.checkbox_transcription_send.configure(state="normal") @@ -278,12 +143,7 @@ class App(CTk): self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) def transcription_receive_stop(self): - if isinstance(self.spk_print_transcript, thread_fnc): - self.spk_print_transcript.stop() - if self.spk_audio_recorder.stop != None: - self.spk_audio_recorder.stop() - self.spk_audio_recorder.stop = None - + model.stopSpeakerTranscript() print_textbox(self.textbox_message_log, "Stop speaker2log", "INFO") print_textbox(self.textbox_message_system_log, "Stop speaker2log", "INFO") self.checkbox_transcription_send.configure(state="normal") @@ -291,20 +151,16 @@ class App(CTk): self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) def transcription_receive_stop_for_config(self): - if isinstance(self.spk_print_transcript, thread_fnc): - self.spk_print_transcript.stop() - if self.spk_audio_recorder.stop != None: - self.spk_audio_recorder.stop() - self.spk_audio_recorder.stop = None - + model.stopSpeakerTranscript() print_textbox(self.textbox_message_log, "Stop speaker2log", "INFO") print_textbox(self.textbox_message_system_log, "Stop speaker2log", "INFO") def checkbox_transcription_receive_callback(self): + config.ENABLE_TRANSCRIPTION_RECEIVE = self.checkbox_transcription_receive.get() self.checkbox_transcription_send.configure(state="disabled") self.checkbox_transcription_receive.configure(state="disabled") self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) - if self.checkbox_transcription_receive.get() is True: + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: th_transcription_receive_start = Thread(target=self.transcription_receive_start) th_transcription_receive_start.daemon = True th_transcription_receive_start.start() @@ -314,28 +170,29 @@ class App(CTk): th_transcription_receive_stop.start() def transcription_start(self): - if self.checkbox_transcription_send.get() is True: + if config.ENABLE_TRANSCRIPTION_SEND is True: th_transcription_send_start = Thread(target=self.transcription_send_start) th_transcription_send_start.daemon = True th_transcription_send_start.start() sleep(2) - if self.checkbox_transcription_receive.get() is True: + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: th_transcription_receive_start = Thread(target=self.transcription_receive_start) th_transcription_receive_start.daemon = True th_transcription_receive_start.start() def transcription_stop(self): - if self.checkbox_transcription_send.get() is True: + if config.ENABLE_TRANSCRIPTION_SEND is True: th_transcription_send_stop = Thread(target=self.transcription_send_stop_for_config) th_transcription_send_stop.daemon = True th_transcription_send_stop.start() - if self.checkbox_transcription_receive.get() is True: + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: th_transcription_receive_stop = Thread(target=self.transcription_receive_stop_for_config) th_transcription_receive_stop.daemon = True th_transcription_receive_stop.start() def checkbox_foreground_callback(self): - if self.checkbox_foreground.get(): + config.ENABLE_FOREGROUND = self.checkbox_foreground.get() + if config.ENABLE_FOREGROUND: self.attributes("-topmost", True) print_textbox(self.textbox_message_log, "Start foreground", "INFO") print_textbox(self.textbox_message_system_log, "Start foreground", "INFO") @@ -345,45 +202,39 @@ class App(CTk): print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO") def foreground_start(self): - if self.checkbox_foreground.get(): + if config.ENABLE_FOREGROUND: self.attributes("-topmost", True) print_textbox(self.textbox_message_log, "Start foreground", "INFO") print_textbox(self.textbox_message_system_log, "Start foreground", "INFO") def foreground_stop(self): - if self.checkbox_foreground.get(): + if config.ENABLE_FOREGROUND: self.attributes("-topmost", False) print_textbox(self.textbox_message_log, "Stop foreground", "INFO") print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO") def entry_message_box_press_key_enter(self, event): - # send OSC typing - send_typing(False, config.OSC_IP_ADDRESS, config.OSC_PORT) + # osc stop send typing + model.oscStopSendTyping() - if self.checkbox_foreground.get(): + if config.ENABLE_FOREGROUND: self.attributes("-topmost", True) message = self.entry_message_box.get() if len(message) > 0: # translate - if self.checkbox_translation.get() is False: + if config.ENABLE_TRANSLATION is False: chat_message = f"{message}" - elif self.translator.translator_status[config.CHOICE_TRANSLATOR] is False: + elif model.getTranslatorStatus() is False: print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") chat_message = f"{message}" else: - result = self.translator.translate( - translator_name=config.CHOICE_TRANSLATOR, - source_language=config.INPUT_SOURCE_LANG, - target_language=config.INPUT_TARGET_LANG, - message=message - ) - chat_message = config.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", result) + chat_message = model.getInputTranslate(message) # send OSC message if config.ENABLE_OSC is True: - send_message(chat_message, config.OSC_IP_ADDRESS, config.OSC_PORT) + model.oscSendMessage(chat_message) else: print_textbox(self.textbox_message_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") print_textbox(self.textbox_message_system_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") @@ -396,25 +247,21 @@ class App(CTk): if config.ENABLE_AUTO_CLEAR_CHATBOX is True: self.entry_message_box.delete(0, customtkinter.END) - BREAK_KEYSYM_LIST = [ - "Delete", "Select", "Up", "Down", "Next", "End", "Print", - "Prior","Insert","Home", "Left", "Clear", "Right", "Linefeed" - ] def entry_message_box_press_key_any(self, event): - # send OSC typing - send_typing(True, config.OSC_IP_ADDRESS, config.OSC_PORT) - if self.checkbox_foreground.get(): + # osc start send typing + model.oscStartSendTyping() + if config.ENABLE_FOREGROUND: self.attributes("-topmost", False) if event.keysym != "??": - if len(event.char) != 0 and event.keysym in self.BREAK_KEYSYM_LIST: + if len(event.char) != 0 and event.keysym in config.BREAK_KEYSYM_LIST: self.entry_message_box.insert("end", event.char) return "break" def entry_message_box_leave(self, event): - # send OSC typing - send_typing(False, config.OSC_IP_ADDRESS, config.OSC_PORT) - if self.checkbox_foreground.get(): + # osc stop send typing + model.oscStopSendTyping() + if config.ENABLE_FOREGROUND: self.attributes("-topmost", True) def delete_window(self): @@ -558,11 +405,6 @@ class App(CTk): widget_main_window_label_setter(self, language_yaml_data) - def check_osc_receive(self, address, osc_arguments): - print(address, osc_arguments) - if config.ENABLE_OSC is False: - config.ENABLE_OSC = True - if __name__ == "__main__": try: app = App() diff --git a/config.py b/config.py index 753cc7f2..0cb6f299 100644 --- a/config.py +++ b/config.py @@ -18,10 +18,50 @@ class Config: cls._instance.load_config() return cls._instance + @property + def VERSION(self): + return self._VERSION + @property def PATH_CONFIG(self): return self._PATH_CONFIG + @property + def ENABLE_TRANSLATION(self): + return self._ENABLE_TRANSLATION + + @ENABLE_TRANSLATION.setter + def ENABLE_TRANSLATION(self, value): + if type(value) is bool: + self._ENABLE_TRANSLATION = value + + @property + def ENABLE_TRANSCRIPTION_SEND(self): + return self._ENABLE_TRANSCRIPTION_SEND + + @ENABLE_TRANSCRIPTION_SEND.setter + def ENABLE_TRANSCRIPTION_SEND(self, value): + if type(value) is bool: + self._ENABLE_TRANSCRIPTION_SEND = value + + @property + def ENABLE_TRANSCRIPTION_RECEIVE(self): + return self._ENABLE_TRANSCRIPTION_RECEIVE + + @ENABLE_TRANSCRIPTION_RECEIVE.setter + def ENABLE_TRANSCRIPTION_RECEIVE(self, value): + if type(value) is bool: + self._ENABLE_TRANSCRIPTION_RECEIVE = value + + @property + def ENABLE_FOREGROUND(self): + return self._ENABLE_FOREGROUND + + @ENABLE_FOREGROUND.setter + def ENABLE_FOREGROUND(self, value): + if type(value) is bool: + self._ENABLE_FOREGROUND = value + @property def TRANSPARENCY(self): return self._TRANSPARENCY @@ -371,8 +411,25 @@ class Config: def GITHUB_URL(self): return self._GITHUB_URL + @property + def BREAK_KEYSYM_LIST(self): + return self._BREAK_KEYSYM_LIST + + @property + def MAX_MIC_ENERGY_THRESHOLD(self): + return self._MAX_MIC_ENERGY_THRESHOLD + + @property + def MAX_SPEAKER_ENERGY_THRESHOLD(self): + return self._MAX_SPEAKER_ENERGY_THRESHOLD + def init_config(self): + self._VERSION = "1.3.2" self._PATH_CONFIG = "./config.json" + self._ENABLE_TRANSLATION = False + self._ENABLE_TRANSCRIPTION_SEND = False + self._ENABLE_TRANSCRIPTION_RECEIVE = False + self._ENABLE_FOREGROUND = False self._TRANSPARENCY = 100 self._APPEARANCE_THEME = "System" self._UI_SCALING = "100%" @@ -413,6 +470,12 @@ class Config: self._ENABLE_OSC = False self._UPDATE_FLAG = False self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" + self._BREAK_KEYSYM_LIST = [ + "Delete", "Select", "Up", "Down", "Next", "End", "Print", + "Prior","Insert","Home", "Left", "Clear", "Right", "Linefeed" + ] + self._MAX_MIC_ENERGY_THRESHOLD = 2000 + self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 def load_config(self): if os_path.isfile(self.PATH_CONFIG) is not False: diff --git a/model.py b/model.py new file mode 100644 index 00000000..c550f43e --- /dev/null +++ b/model.py @@ -0,0 +1,304 @@ +from time import sleep +from queue import Queue +from threading import Thread +from requests import get as requests_get + +from translation import Translator +from flashtext import KeywordProcessor +from osc_tools import send_typing, send_message, send_test_action, receive_osc_parameters +from languages import transcription_lang +from audio_utils import get_input_device_list, get_output_device_list, get_default_output_device +from audio_recorder import SelectedMicRecorder, SelectedSpeakerRecorder +from audio_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder +from audio_transcriber import AudioTranscriber +from utils import print_textbox, thread_fnc +from config import config +from notification import notification_xsoverlay_for_vrct + +class Model: + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super(Model, cls).__new__(cls) + cls._instance.init() + return cls._instance + + def init(self): + self.mic_energy_recorder = None + self.mic_energy_plot_progressbar = None + self.speaker_energy_get_progressbar = None + self.speaker_energy_plot_progressbar = None + self.translator = Translator() + self.keyword_processor = KeywordProcessor() + + def resetTranslator(self): + del self.translator + self.translator = Translator() + + def resetKeywordProcessor(self): + del self.translator + self.keyword_processor = KeywordProcessor() + + def authenticationTranslator(self, choice_translator=None, auth_key=None): + if choice_translator == None: + choice_translator = config.CHOICE_TRANSLATOR + if auth_key == None: + auth_key = config.AUTH_KEYS[choice_translator] + + result = self.translator.authentication(choice_translator, auth_key) + if result: + auth_keys = config.AUTH_KEYS + auth_keys[choice_translator] = auth_key + config.AUTH_KEYS = auth_keys + return result + + def getTranslatorStatus(self): + return self.translator.translator_status[config.CHOICE_TRANSLATOR] + + def getListTranslatorName(self): + return list(self.translator.translator_status.keys()) + + def getInputTranslate(self, message): + translation = self.translator.translate( + translator_name=config.CHOICE_TRANSLATOR, + source_language=config.INPUT_SOURCE_LANG, + target_language=config.INPUT_TARGET_LANG, + message=message + ) + message = config.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", translation) + return message + + def getOutputTranslate(self, message): + translation = self.translator.translate( + translator_name=config.CHOICE_TRANSLATOR, + source_language=config.OUTPUT_SOURCE_LANG, + target_language=config.OUTPUT_TARGET_LANG, + message=message + ) + message = config.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", translation) + return message + + def addKeywords(self): + for f in config.INPUT_MIC_WORD_FILTER: + self.keyword_processor.add_keyword(f) + + def checkKeywords(self, message): + return len(self.keyword_processor.extract_keywords(message)) != 0 + + @staticmethod + def oscStartSendTyping(): + send_typing(True, config.OSC_IP_ADDRESS, config.OSC_PORT) + + @staticmethod + def oscStopSendTyping(): + send_typing(False, config.OSC_IP_ADDRESS, config.OSC_PORT) + + @staticmethod + def oscSendMessage(message): + send_message(message, config.OSC_IP_ADDRESS, config.OSC_PORT) + + @staticmethod + def oscCheck(): + def check_osc_receive(address, osc_arguments): + if config.ENABLE_OSC is False: + config.ENABLE_OSC = True + + # start receive osc + th_receive_osc_parameters = Thread(target=receive_osc_parameters, args=(check_osc_receive,)) + th_receive_osc_parameters.daemon = True + th_receive_osc_parameters.start() + + # check osc started + send_test_action() + + # check update + response = requests_get(config.GITHUB_URL) + tag_name = response.json()["tag_name"] + if tag_name != config.VERSION: + config.UPDATE_FLAG = True + + @staticmethod + def getListInputHost(): + return [host for host in get_input_device_list().keys()] + + @staticmethod + def getListInputDevice(): + return [device["name"] for device in get_input_device_list()[config.CHOICE_MIC_HOST]] + + @staticmethod + def getInputDefaultDevice(): + return [device["name"] for device in get_input_device_list()[config.CHOICE_MIC_HOST]][0] + + @staticmethod + def getListOutputDevice(): + return [device["name"] for device in get_output_device_list()] + + @staticmethod + def checkSpeakerStatus(choice=config.CHOICE_SPEAKER_DEVICE): + speaker_device = [device for device in get_output_device_list() if device["name"] == choice][0] + if get_default_output_device()["index"] == speaker_device["index"]: + return True + return False + + def startMicTranscript(self, log, send_log, system_log): + mic_audio_queue = Queue() + self.mic_audio_recorder = SelectedMicRecorder( + [device for device in get_input_device_list()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0], + config.INPUT_MIC_ENERGY_THRESHOLD, + config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, + config.INPUT_MIC_RECORD_TIMEOUT, + ) + self.mic_audio_recorder.record_into_queue(mic_audio_queue) + mic_transcriber = AudioTranscriber( + speaker=False, + source=self.mic_audio_recorder.source, + phrase_timeout=config.INPUT_MIC_PHRASE_TIMEOUT, + max_phrases=config.INPUT_MIC_MAX_PHRASES, + ) + def mic_transcript_to_chatbox(): + mic_transcriber.transcribe_audio_queue(mic_audio_queue, transcription_lang[config.INPUT_MIC_VOICE_LANGUAGE]) + message = mic_transcriber.get_transcript() + if len(message) > 0: + print(message) + # word filter + if self.checkKeywords(message): + print_textbox(log, f"Detect WordFilter :{message}", "INFO") + print_textbox(system_log, f"Detect WordFilter :{message}", "INFO") + return + + # translate + if config.ENABLE_TRANSLATION is False: + voice_message = f"{message}" + elif self.getTranslatorStatus() is False: + print_textbox(log, "Auth Key or language setting is incorrect", "ERROR") + print_textbox(system_log, "Auth Key or language setting is incorrect", "ERROR") + voice_message = f"{message}" + else: + voice_message = self.getInputTranslate(message) + + if config.ENABLE_TRANSCRIPTION_SEND is True: + if config.ENABLE_OSC is True: + # osc send message + model.oscSendMessage(voice_message) + else: + print_textbox(log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") + print_textbox(system_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") + # update textbox message log + print_textbox(log, f"{voice_message}", "SEND") + print_textbox(send_log, f"{voice_message}", "SEND") + + self.mic_print_transcript = thread_fnc(mic_transcript_to_chatbox) + self.mic_print_transcript.daemon = True + self.mic_print_transcript.start() + + def stopMicTranscript(self): + if isinstance(self.mic_print_transcript, thread_fnc): + self.mic_print_transcript.stop() + if self.mic_audio_recorder.stop != None: + self.mic_audio_recorder.stop() + self.mic_audio_recorder.stop = None + + def startCheckMicEnergy(self, progressBar): + def progressBarInputMicEnergyPlot(): + if mic_energy_queue.empty() is False: + energy = mic_energy_queue.get() + try: + progressBar.set(energy/config.MAX_MIC_ENERGY_THRESHOLD) + except: + pass + sleep(0.01) + mic_energy_queue = Queue() + mic_device = [device for device in get_input_device_list()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] + self.mic_energy_recorder = SelectedMicEnergyRecorder(mic_device) + self.mic_energy_recorder.record_into_queue(mic_energy_queue) + self.mic_energy_plot_progressbar = thread_fnc(progressBarInputMicEnergyPlot) + self.mic_energy_plot_progressbar.daemon = True + self.mic_energy_plot_progressbar.start() + + def stopCheckMicEnergy(self): + if self.mic_energy_recorder != None: + self.mic_energy_recorder.stop() + if self.mic_energy_plot_progressbar != None: + self.mic_energy_plot_progressbar.stop() + + def startSpeakerTranscript(self, log, receive_log, system_log): + spk_audio_queue = Queue() + spk_device = [device for device in get_output_device_list() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] + self.spk_audio_recorder = SelectedSpeakerRecorder( + spk_device, + config.INPUT_SPEAKER_ENERGY_THRESHOLD, + config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, + config.INPUT_SPEAKER_RECORD_TIMEOUT, + ) + self.spk_audio_recorder.record_into_queue(spk_audio_queue) + spk_transcriber = AudioTranscriber( + speaker=True, + source=self.spk_audio_recorder.source, + phrase_timeout=config.INPUT_SPEAKER_PHRASE_TIMEOUT, + max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, + ) + def spk_transcript_to_textbox(): + spk_transcriber.transcribe_audio_queue(spk_audio_queue, transcription_lang[config.INPUT_SPEAKER_VOICE_LANGUAGE]) + message = spk_transcriber.get_transcript() + if len(message) > 0: + # translate + if config.ENABLE_TRANSLATION is False: + voice_message = f"{message}" + elif model.getTranslatorStatus() is False: + print_textbox(log, "Auth Key or language setting is incorrect", "ERROR") + print_textbox(system_log, "Auth Key or language setting is incorrect", "ERROR") + voice_message = f"{message}" + else: + voice_message = model.getOutputTranslate(message) + + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + # update textbox message receive log + print_textbox(log, f"{voice_message}", "RECEIVE") + print_textbox(receive_log, f"{voice_message}", "RECEIVE") + if config.ENABLE_NOTICE_XSOVERLAY is True: + notification_xsoverlay_for_vrct(content=f"{voice_message}") + + self.spk_print_transcript = thread_fnc(spk_transcript_to_textbox) + self.spk_print_transcript.daemon = True + self.spk_print_transcript.start() + + def stopSpeakerTranscript(self): + if isinstance(self.spk_print_transcript, thread_fnc): + self.spk_print_transcript.stop() + if self.spk_audio_recorder.stop != None: + self.spk_audio_recorder.stop() + self.spk_audio_recorder.stop = None + + def startCheckSpeakerEnergy(self, progressBar): + def progressBar_input_speaker_energy_plot(): + if speaker_energy_queue.empty() is False: + energy = speaker_energy_queue.get() + try: + progressBar.set(energy/config.MAX_SPEAKER_ENERGY_THRESHOLD) + except: + pass + sleep(0.01) + + def progressBar_input_speaker_energy_get(): + with self.speaker_energy_recorder.source as source: + energy = self.speaker_energy_recorder.recorder.listen_energy(source) + self.speaker_energy_queue.put(energy) + + speaker_device = [device for device in get_output_device_list() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] + speaker_energy_queue = Queue() + self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(speaker_device) + self.speaker_energy_get_progressbar = thread_fnc(progressBar_input_speaker_energy_get) + self.speaker_energy_get_progressbar.daemon = True + self.speaker_energy_get_progressbar.start() + self.speaker_energy_plot_progressbar = thread_fnc(progressBar_input_speaker_energy_plot) + self.speaker_energy_plot_progressbar.daemon = True + self.speaker_energy_plot_progressbar.start() + + def stopCheckSpeakerEnergy(self): + if self.speaker_energy_get_progressbar != None: + self.speaker_energy_get_progressbar.stop() + if self.speaker_energy_plot_progressbar != None: + self.speaker_energy_plot_progressbar.stop() + +model = Model() diff --git a/window_config.py b/window_config.py index 8b28937e..b54429cf 100644 --- a/window_config.py +++ b/window_config.py @@ -1,17 +1,13 @@ -from time import sleep -from queue import Queue from os import path as os_path from tkinter import DoubleVar, IntVar from tkinter import font as tk_font import customtkinter from customtkinter import CTkToplevel, CTkTabview, CTkFont, CTkLabel, CTkSlider, CTkOptionMenu, StringVar, CTkEntry, CTkCheckBox, CTkProgressBar -from flashtext import KeywordProcessor from threading import Thread from config import config -from utils import print_textbox, thread_fnc, get_localized_text, get_key_by_value, widget_config_window_label_setter -from audio_utils import get_input_device_list, get_output_device_list, get_default_output_device -from audio_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder +from model import model +from utils import print_textbox, get_localized_text, get_key_by_value, widget_config_window_label_setter from languages import translation_lang, transcription_lang, selectable_languages from ctk_scrollable_dropdown import CTkScrollableDropdown @@ -33,15 +29,6 @@ class ToplevelWindowConfig(CTkToplevel): self.after(200, lambda: self.iconbitmap(os_path.join(os_path.dirname(__file__), "img", "app.ico"))) self.title("Config") - # init parameter - self.MAX_MIC_ENERGY_THRESHOLD = 2000 - self.MAX_SPEAKER_ENERGY_THRESHOLD = 4000 - self.mic_energy_recorder = None - self.mic_energy_plot_progressbar = None - self.speaker_energy_recorder = None - self.speaker_energy_get_progressbar = None - self.speaker_energy_plot_progressbar = None - # load ui language data language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") # add tabview config @@ -202,7 +189,7 @@ class ToplevelWindowConfig(CTkToplevel): def optionmenu_translation_translator_callback(self, choice): self.optionmenu_translation_translator.set(choice) - if self.parent.translator.authentication(choice, config.AUTH_KEYS[choice]) is False: + if model.authenticationTranslator(choice_translator=choice) is False: print_textbox(self.parent.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") print_textbox(self.parent.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") else: @@ -253,15 +240,15 @@ class ToplevelWindowConfig(CTkToplevel): def optionmenu_input_mic_host_callback(self, choice): self.optionmenu_input_mic_host.set(choice) + config.CHOICE_MIC_HOST = choice + config.CHOICE_MIC_DEVICE = model.getInputDefaultDevice() + self.optionmenu_input_mic_device.configure( - values=[device["name"] for device in get_input_device_list()[choice]], - variable=StringVar(value=[device["name"] for device in get_input_device_list()[choice]][0])) + values=model.getListInputDevice(), + variable=StringVar(value=model.getInputDefaultDevice())) if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_input_mic_device.configure(values=[device["name"] for device in get_input_device_list()[choice]]) - - config.CHOICE_MIC_HOST = choice - config.CHOICE_MIC_DEVICE = [device["name"] for device in get_input_device_list()[choice]][0] + self.scrollableDropdown_input_mic_device.configure(values=model.getListInputDevice()) def optionmenu_input_mic_device_callback(self, choice): self.optionmenu_input_mic_device.set(choice) @@ -273,31 +260,13 @@ class ToplevelWindowConfig(CTkToplevel): self.optionmenu_input_mic_voice_language.set(choice) config.INPUT_MIC_VOICE_LANGUAGE = choice - def progressBar_input_mic_energy_plot(self): - if self.mic_energy_queue.empty() is False: - energy = self.mic_energy_queue.get() - try: - self.progressBar_input_mic_energy_threshold.set(energy/self.MAX_MIC_ENERGY_THRESHOLD) - except: - pass - sleep(0.01) - def mic_threshold_check_start(self): - self.mic_energy_queue = Queue() - mic_device = [device for device in get_input_device_list()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] - self.mic_energy_recorder = SelectedMicEnergyRecorder(mic_device) - self.mic_energy_recorder.record_into_queue(self.mic_energy_queue) - self.mic_energy_plot_progressbar = thread_fnc(self.progressBar_input_mic_energy_plot) - self.mic_energy_plot_progressbar.daemon = True - self.mic_energy_plot_progressbar.start() + model.startCheckMicEnergy(self.progressBar_input_mic_energy_threshold) self.checkbox_input_mic_threshold_check.configure(state="normal") self.checkbox_input_speaker_threshold_check.configure(state="normal") def mic_threshold_check_stop(self): - if self.mic_energy_recorder != None: - self.mic_energy_recorder.stop() - if self.mic_energy_plot_progressbar != None: - self.mic_energy_plot_progressbar.stop() + model.stopCheckMicEnergy() self.progressBar_input_mic_energy_threshold.set(0) self.checkbox_input_mic_threshold_check.configure(state="normal") self.checkbox_input_speaker_threshold_check.configure(state="normal") @@ -338,13 +307,11 @@ class ToplevelWindowConfig(CTkToplevel): config.INPUT_MIC_WORD_FILTER = word_filter.split(",") else: config.INPUT_MIC_WORD_FILTER = [] - self.parent.keyword_processor = KeywordProcessor() - for f in self.parent.INPUT_MIC_WORD_FILTER: - self.parent.keyword_processor.add_keyword(f) + model.resetKeywordProcessor() + model.addKeywords() def optionmenu_input_speaker_device_callback(self, choice): - speaker_device = [device for device in get_output_device_list() if device["name"] == choice][0] - if get_default_output_device()["index"] == speaker_device["index"]: + if model.checkSpeakerStatus(choice): self.optionmenu_input_speaker_device.set(choice) config.CHOICE_SPEAKER_DEVICE = choice else: @@ -356,45 +323,13 @@ class ToplevelWindowConfig(CTkToplevel): self.optionmenu_input_speaker_voice_language.set(choice) config.INPUT_SPEAKER_VOICE_LANGUAGE = choice - def progressBar_input_speaker_energy_plot(self): - if self.speaker_energy_queue.empty() is False: - energy = self.speaker_energy_queue.get() - try: - self.progressBar_input_speaker_energy_threshold.set(energy/self.MAX_SPEAKER_ENERGY_THRESHOLD) - except: - pass - sleep(0.01) - - def progressBar_input_speaker_energy_get(self): - with self.speaker_energy_recorder.source as source: - energy = self.speaker_energy_recorder.recorder.listen_energy(source) - self.speaker_energy_queue.put(energy) - def speaker_threshold_check_start(self): - speaker_device = [device for device in get_output_device_list() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] - - if get_default_output_device()["index"] == speaker_device["index"]: - self.speaker_energy_queue = Queue() - self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(speaker_device) - self.speaker_energy_get_progressbar = thread_fnc(self.progressBar_input_speaker_energy_get) - self.speaker_energy_get_progressbar.daemon = True - self.speaker_energy_get_progressbar.start() - self.speaker_energy_plot_progressbar = thread_fnc(self.progressBar_input_speaker_energy_plot) - self.speaker_energy_plot_progressbar.daemon = True - self.speaker_energy_plot_progressbar.start() - else: - print_textbox(self.parent.textbox_message_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") - print_textbox(self.parent.textbox_message_system_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") - self.checkbox_input_speaker_threshold_check.deselect() + model.startCheckSpeakerEnergy(self.progressBar_input_speaker_energy_threshold) self.checkbox_input_mic_threshold_check.configure(state="normal") self.checkbox_input_speaker_threshold_check.configure(state="normal") def speaker_threshold_check_stop(self): - if self.speaker_energy_get_progressbar != None: - self.speaker_energy_get_progressbar.stop() - if self.speaker_energy_plot_progressbar != None: - self.speaker_energy_plot_progressbar.stop() - + model.stopCheckSpeakerEnergy() self.progressBar_input_speaker_energy_threshold.set(0) self.checkbox_input_mic_threshold_check.configure(state="normal") self.checkbox_input_speaker_threshold_check.configure(state="normal") @@ -404,9 +339,14 @@ class ToplevelWindowConfig(CTkToplevel): self.checkbox_input_speaker_threshold_check.configure(state="disabled") self.update() if self.checkbox_input_speaker_threshold_check.get(): - th_speaker_threshold_check_start = Thread(target=self.speaker_threshold_check_start) - th_speaker_threshold_check_start.daemon = True - th_speaker_threshold_check_start.start() + if model.checkSpeakerStatus(): + th_speaker_threshold_check_start = Thread(target=self.speaker_threshold_check_start) + th_speaker_threshold_check_start.daemon = True + th_speaker_threshold_check_start.start() + else: + print_textbox(self.parent.textbox_message_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") + print_textbox(self.parent.textbox_message_system_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") + self.checkbox_input_speaker_threshold_check.deselect() else: th_speaker_threshold_check_stop = Thread(target=self.speaker_threshold_check_stop) th_speaker_threshold_check_stop.daemon = True @@ -436,10 +376,7 @@ class ToplevelWindowConfig(CTkToplevel): def entry_authkey_callback(self, event): value = self.entry_authkey.get() if len(value) > 0: - if self.parent.translator.authentication("DeepL(auth)", value) is True: - auth_keys = config.AUTH_KEYS - auth_keys["DeepL(auth)"] = value - config.AUTH_KEYS = auth_keys + if model.authenticationTranslator(choice_translator="DeepL(auth)", auth_key=value) is True: print_textbox(self.parent.textbox_message_log, "Auth key update completed", "INFO") print_textbox(self.parent.textbox_message_system_log, "Auth key update completed", "INFO") else: @@ -683,7 +620,7 @@ class ToplevelWindowConfig(CTkToplevel): self.label_translation_translator.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.optionmenu_translation_translator = CTkOptionMenu( self.tabview_config.tab(config_tab_title_translation), - values=list(self.parent.translator.translator_status.keys()), + values=model.getListTranslatorName(), command=self.optionmenu_translation_translator_callback, variable=StringVar(value=config.CHOICE_TRANSLATOR), font=CTkFont(family=config.FONT_FAMILY), @@ -695,7 +632,7 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_translation_translator = CTkScrollableDropdown( self.optionmenu_translation_translator, - values=list(self.parent.translator.translator_status.keys()), + values=model.getListTranslatorName(), justify="left", button_color="transparent", command=self.optionmenu_translation_translator_callback, @@ -862,7 +799,7 @@ class ToplevelWindowConfig(CTkToplevel): self.label_input_mic_host.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.optionmenu_input_mic_host = CTkOptionMenu( self.tabview_config.tab(config_tab_title_transcription), - values=[host for host in get_input_device_list().keys()], + values=model.getListInputHost(), command=self.optionmenu_input_mic_host_callback, variable=StringVar(value=config.CHOICE_MIC_HOST), font=CTkFont(family=config.FONT_FAMILY), @@ -874,7 +811,7 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_input_mic_host = CTkScrollableDropdown( self.optionmenu_input_mic_host, - values=[host for host in get_input_device_list().keys()], + values=model.getListInputHost(), justify="left", button_color="transparent", command=self.optionmenu_input_mic_host_callback, @@ -896,7 +833,7 @@ class ToplevelWindowConfig(CTkToplevel): self.label_input_mic_device.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.optionmenu_input_mic_device = CTkOptionMenu( self.tabview_config.tab(config_tab_title_transcription), - values=[device["name"] for device in get_input_device_list()[config.CHOICE_MIC_HOST]], + values=model.getListInputDevice(), command=self.optionmenu_input_mic_device_callback, variable=StringVar(value=config.CHOICE_MIC_DEVICE), font=CTkFont(family=config.FONT_FAMILY), @@ -908,7 +845,7 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_input_mic_device = CTkScrollableDropdown( self.optionmenu_input_mic_device, - values=[device["name"] for device in get_input_device_list()[config.CHOICE_MIC_HOST]], + values=model.getListInputDevice(), justify="left", button_color="transparent", command=self.optionmenu_input_mic_device_callback, @@ -966,11 +903,11 @@ class ToplevelWindowConfig(CTkToplevel): self.slider_input_mic_energy_threshold = CTkSlider( self.tabview_config.tab(config_tab_title_transcription), from_=0, - to=self.MAX_MIC_ENERGY_THRESHOLD, + to=config.MAX_MIC_ENERGY_THRESHOLD, border_width=7, button_length=0, button_corner_radius=3, - number_of_steps=self.MAX_MIC_ENERGY_THRESHOLD, + number_of_steps=config.MAX_MIC_ENERGY_THRESHOLD, command=self.slider_input_mic_energy_threshold_callback, variable=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), ) @@ -1102,7 +1039,7 @@ class ToplevelWindowConfig(CTkToplevel): self.label_input_speaker_device.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") self.optionmenu_input_speaker_device = CTkOptionMenu( self.tabview_config.tab(config_tab_title_transcription), - values=[device["name"] for device in get_output_device_list()], + values=model.getListOutputDevice(), command=self.optionmenu_input_speaker_device_callback, variable=StringVar(value=config.CHOICE_SPEAKER_DEVICE), font=CTkFont(family=config.FONT_FAMILY), @@ -1114,7 +1051,7 @@ class ToplevelWindowConfig(CTkToplevel): if SCROLLABLE_DROPDOWN: self.scrollableDropdown_input_speaker_device = CTkScrollableDropdown( self.optionmenu_input_speaker_device, - values=[device["name"] for device in get_output_device_list()], + values=model.getListOutputDevice(), justify="left", button_color="transparent", command=self.optionmenu_input_speaker_device_callback, @@ -1173,11 +1110,11 @@ class ToplevelWindowConfig(CTkToplevel): self.slider_input_speaker_energy_threshold = CTkSlider( self.tabview_config.tab(config_tab_title_transcription), from_=0, - to=self.MAX_SPEAKER_ENERGY_THRESHOLD, + to=config.MAX_SPEAKER_ENERGY_THRESHOLD, border_width=7, button_length=0, button_corner_radius=3, - number_of_steps=self.MAX_SPEAKER_ENERGY_THRESHOLD, + number_of_steps=config.MAX_SPEAKER_ENERGY_THRESHOLD, command=self.slider_input_speaker_energy_threshold_callback, variable=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), ) From e3a37c576cd66631bb05b9a3d37716a0301d3d80 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Fri, 18 Aug 2023 15:46:52 +0900 Subject: [PATCH 013/355] [typo] remove print command --- model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/model.py b/model.py index c550f43e..348da461 100644 --- a/model.py +++ b/model.py @@ -160,7 +160,6 @@ class Model: mic_transcriber.transcribe_audio_queue(mic_audio_queue, transcription_lang[config.INPUT_MIC_VOICE_LANGUAGE]) message = mic_transcriber.get_transcript() if len(message) > 0: - print(message) # word filter if self.checkKeywords(message): print_textbox(log, f"Detect WordFilter :{message}", "INFO") From 31e786f2a83c91b074656560e729225e4b08c9dd Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sat, 19 Aug 2023 19:09:01 +0900 Subject: [PATCH 014/355] =?UTF-8?q?[Update]=20model.py=E3=81=8B=E3=82=89pr?= =?UTF-8?q?int=5Ftextbox=E3=82=92=E3=81=99=E3=81=B9=E3=81=A6=E5=BC=95?= =?UTF-8?q?=E3=81=8D=E5=89=A5=E3=81=8C=E3=81=97=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit UIの依存関係はなくなったはず --- VRCT.py | 187 ++++++++++++++++++++++++++++++++++++++---------------- config.py | 73 +++++++++++---------- model.py | 57 +++-------------- utils.py | 10 +-- 4 files changed, 184 insertions(+), 143 deletions(-) diff --git a/VRCT.py b/VRCT.py index af03393a..fbc71654 100644 --- a/VRCT.py +++ b/VRCT.py @@ -56,8 +56,7 @@ class App(CTk): # set translator if model.authenticationTranslator() is False: # error update Auth key - print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") - print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") + self.printLogAuthenticationError() # set word filter model.addKeywords() @@ -93,32 +92,27 @@ class App(CTk): def checkbox_translation_callback(self): config.ENABLE_TRANSLATION = self.checkbox_translation.get() if config.ENABLE_TRANSLATION is True: - print_textbox(self.textbox_message_log, "Start translation", "INFO") - print_textbox(self.textbox_message_system_log, "Start translation", "INFO") + self.printLogStartTranslation() else: - print_textbox(self.textbox_message_log, "Stop translation", "INFO") - print_textbox(self.textbox_message_system_log, "Stop translation", "INFO") + self.printLogStopTranslation() def transcription_send_start(self): - model.startMicTranscript(self.textbox_message_log, self.textbox_message_send_log, self.textbox_message_system_log) - print_textbox(self.textbox_message_log, "Start voice2chatbox", "INFO") - print_textbox(self.textbox_message_system_log, "Start voice2chatbox", "INFO") + model.startMicTranscript(self.sendMicMessage) + self.printLogStartVoice2chatbox() self.checkbox_transcription_send.configure(state="normal") self.checkbox_transcription_receive.configure(state="normal") self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) def transcription_send_stop(self): model.stopMicTranscript() - print_textbox(self.textbox_message_log, "Stop voice2chatbox", "INFO") - print_textbox(self.textbox_message_system_log, "Stop voice2chatbox", "INFO") + self.printLogStopVoice2chatbox() self.checkbox_transcription_send.configure(state="normal") self.checkbox_transcription_receive.configure(state="normal") self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) def transcription_send_stop_for_config(self): model.stopMicTranscript() - print_textbox(self.textbox_message_log, "Stop voice2chatbox", "INFO") - print_textbox(self.textbox_message_system_log, "Stop voice2chatbox", "INFO") + self.printLogStopVoice2chatbox() def checkbox_transcription_send_callback(self): config.ENABLE_TRANSCRIPTION_SEND = self.checkbox_transcription_send.get() @@ -135,25 +129,22 @@ class App(CTk): th_transcription_send_stop.start() def transcription_receive_start(self): - model.startSpeakerTranscript(self.textbox_message_log, self.textbox_message_receive_log, self.textbox_message_system_log) - print_textbox(self.textbox_message_log, "Start speaker2log", "INFO") - print_textbox(self.textbox_message_system_log, "Start speaker2log", "INFO") + model.startSpeakerTranscript(self.receiveSpeakerMessage) + self.printLogStartSpeaker2log() self.checkbox_transcription_send.configure(state="normal") self.checkbox_transcription_receive.configure(state="normal") self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) def transcription_receive_stop(self): model.stopSpeakerTranscript() - print_textbox(self.textbox_message_log, "Stop speaker2log", "INFO") - print_textbox(self.textbox_message_system_log, "Stop speaker2log", "INFO") + self.printLogStopSpeaker2log() self.checkbox_transcription_send.configure(state="normal") self.checkbox_transcription_receive.configure(state="normal") self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) def transcription_receive_stop_for_config(self): model.stopSpeakerTranscript() - print_textbox(self.textbox_message_log, "Stop speaker2log", "INFO") - print_textbox(self.textbox_message_system_log, "Stop speaker2log", "INFO") + self.printLogStopSpeaker2log() def checkbox_transcription_receive_callback(self): config.ENABLE_TRANSCRIPTION_RECEIVE = self.checkbox_transcription_receive.get() @@ -194,24 +185,20 @@ class App(CTk): config.ENABLE_FOREGROUND = self.checkbox_foreground.get() if config.ENABLE_FOREGROUND: self.attributes("-topmost", True) - print_textbox(self.textbox_message_log, "Start foreground", "INFO") - print_textbox(self.textbox_message_system_log, "Start foreground", "INFO") + self.printLogStartForeground() else: self.attributes("-topmost", False) - print_textbox(self.textbox_message_log, "Stop foreground", "INFO") - print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO") + self.printLogStopForeground() def foreground_start(self): if config.ENABLE_FOREGROUND: self.attributes("-topmost", True) - print_textbox(self.textbox_message_log, "Start foreground", "INFO") - print_textbox(self.textbox_message_system_log, "Start foreground", "INFO") + self.printLogStartForeground() def foreground_stop(self): if config.ENABLE_FOREGROUND: self.attributes("-topmost", False) - print_textbox(self.textbox_message_log, "Stop foreground", "INFO") - print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO") + self.printLogStopForeground() def entry_message_box_press_key_enter(self, event): # osc stop send typing @@ -221,31 +208,7 @@ class App(CTk): self.attributes("-topmost", True) message = self.entry_message_box.get() - if len(message) > 0: - # translate - if config.ENABLE_TRANSLATION is False: - chat_message = f"{message}" - elif model.getTranslatorStatus() is False: - print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") - print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") - chat_message = f"{message}" - else: - chat_message = model.getInputTranslate(message) - - # send OSC message - if config.ENABLE_OSC is True: - model.oscSendMessage(chat_message) - else: - print_textbox(self.textbox_message_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") - print_textbox(self.textbox_message_system_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") - - # update textbox message log - print_textbox(self.textbox_message_log, f"{chat_message}", "SEND") - print_textbox(self.textbox_message_send_log, f"{chat_message}", "SEND") - - # delete message in entry message box - if config.ENABLE_AUTO_CLEAR_CHATBOX is True: - self.entry_message_box.delete(0, customtkinter.END) + self.sendChatMessage(message) def entry_message_box_press_key_any(self, event): # osc start send typing @@ -405,6 +368,124 @@ class App(CTk): widget_main_window_label_setter(self, language_yaml_data) + def printLogAuthenticationError(self): + print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") + print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") + + def printLogStartTranslation(self): + print_textbox(self.textbox_message_log, "Start translation", "INFO") + print_textbox(self.textbox_message_system_log, "Start translation", "INFO") + + def printLogStopTranslation(self): + print_textbox(self.textbox_message_log, "Stop translation", "INFO") + print_textbox(self.textbox_message_system_log, "Stop translation", "INFO") + + def printLogStartVoice2chatbox(self): + print_textbox(self.textbox_message_log, "Start voice2chatbox", "INFO") + print_textbox(self.textbox_message_system_log, "Start voice2chatbox", "INFO") + + def printLogStopVoice2chatbox(self): + print_textbox(self.textbox_message_log, "Stop voice2chatbox", "INFO") + print_textbox(self.textbox_message_system_log, "Stop voice2chatbox", "INFO") + + def printLogStartSpeaker2log(self): + print_textbox(self.textbox_message_log, "Start speaker2log", "INFO") + print_textbox(self.textbox_message_system_log, "Start speaker2log", "INFO") + + def printLogStopSpeaker2log(self): + print_textbox(self.textbox_message_log, "Stop speaker2log", "INFO") + print_textbox(self.textbox_message_system_log, "Stop speaker2log", "INFO") + + def printLogStartForeground(self): + print_textbox(self.textbox_message_log, "Start foreground", "INFO") + print_textbox(self.textbox_message_system_log, "Start foreground", "INFO") + + def printLogStopForeground(self): + print_textbox(self.textbox_message_log, "Stop foreground", "INFO") + print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO") + + def printLogDetectWordFilter(self, message): + print_textbox(self.textbox_message_log, f"Detect WordFilter :{message}", "INFO") + print_textbox(self.textbox_message_system_log, f"Detect WordFilter :{message}", "INFO") + + def printLogOSCError(self): + print_textbox(self.textbox_message_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") + print_textbox(self.textbox_message_system_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") + + def printLogSendMessage(self, message): + print_textbox(self.textbox_message_log, f"{message}", "SEND") + print_textbox(self.textbox_message_send_log, f"{message}", "SEND") + + def printLogReceiveMessage(self, message): + print_textbox(self.textbox_message_log, f"{message}", "RECEIVE") + print_textbox(self.textbox_message_receive_log, f"{message}", "RECEIVE") + + def sendChatMessage(self, message): + if len(message) > 0: + # translate + if config.ENABLE_TRANSLATION is False: + chat_message = f"{message}" + elif model.getTranslatorStatus() is False: + self.printLogAuthenticationError() + chat_message = f"{message}" + else: + chat_message = model.getInputTranslate(message) + + # send OSC message + if config.ENABLE_OSC is True: + model.oscSendMessage(chat_message) + else: + self.printLogOSCError() + + # update textbox message log + self.printLogSendMessage(chat_message) + + # delete message in entry message box + if config.ENABLE_AUTO_CLEAR_CHATBOX is True: + self.entry_message_box.delete(0, customtkinter.END) + + def sendMicMessage(self, message): + if len(message) > 0: + # word filter + if model.checkKeywords(message): + self.printLogDetectWordFilter(message) + return + + # translate + if config.ENABLE_TRANSLATION is False: + voice_message = f"{message}" + elif model.getTranslatorStatus() is False: + self.printLogAuthenticationError() + voice_message = f"{message}" + else: + voice_message = model.getInputTranslate(message) + + if config.ENABLE_TRANSCRIPTION_SEND is True: + if config.ENABLE_OSC is True: + # osc send message + model.oscSendMessage(voice_message) + else: + self.printLogOSCError() + # update textbox message log + self.printLogSendMessage(voice_message) + + def receiveSpeakerMessage(self, message): + if len(message) > 0: + # translate + if config.ENABLE_TRANSLATION is False: + voice_message = f"{message}" + elif model.getTranslatorStatus() is False: + self.printLogAuthenticationError() + voice_message = f"{message}" + else: + voice_message = model.getOutputTranslate(message) + + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + # update textbox message receive log + self.printLogReceiveMessage(voice_message) + if config.ENABLE_NOTICE_XSOVERLAY is True: + model.notificationXsoverlay(voice_message) + if __name__ == "__main__": try: app = App() diff --git a/config.py b/config.py index 0cb6f299..c4edc384 100644 --- a/config.py +++ b/config.py @@ -1,13 +1,20 @@ +from json import load, dump import inspect from os import path as os_path from json import load as json_load from json import dump as json_dump import tkinter as tk from tkinter import font -from utils import save_json from languages import transcription_lang, translators, translation_lang, selectable_languages from audio_utils import get_input_device_list, get_output_device_list, get_default_input_device, get_default_output_device +def saveJson(path, key, value): + with open(path, "r") as fp: + json_data = load(fp) + json_data[key] = value + with open(path, "w") as fp: + dump(json_data, fp, indent=4) + class Config: _instance = None @@ -70,7 +77,7 @@ class Config: def TRANSPARENCY(self, value): if type(value) is int and 0 <= value <= 100: self._TRANSPARENCY = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def APPEARANCE_THEME(self): @@ -80,7 +87,7 @@ class Config: def APPEARANCE_THEME(self, value): if value in ["Light", "Dark", "System"]: self._APPEARANCE_THEME = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def UI_SCALING(self): @@ -90,7 +97,7 @@ class Config: def UI_SCALING(self, value): if value in ["80%", "90%", "100%", "110%", "120%"]: self._UI_SCALING = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def FONT_FAMILY(self): @@ -102,7 +109,7 @@ class Config: root.withdraw() if value in list(font.families()): self._FONT_FAMILY = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) root.destroy() @property @@ -113,7 +120,7 @@ class Config: def UI_LANGUAGE(self, value): if value in list(selectable_languages.keys()): self._UI_LANGUAGE = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def CHOICE_TRANSLATOR(self): @@ -123,7 +130,7 @@ class Config: def CHOICE_TRANSLATOR(self, value): if value in translators: self._CHOICE_TRANSLATOR = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_SOURCE_LANG(self): @@ -133,7 +140,7 @@ class Config: def INPUT_SOURCE_LANG(self, value): if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): self._INPUT_SOURCE_LANG = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_TARGET_LANG(self): @@ -143,7 +150,7 @@ class Config: def INPUT_TARGET_LANG(self, value): if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): self._INPUT_TARGET_LANG = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def OUTPUT_SOURCE_LANG(self): @@ -153,7 +160,7 @@ class Config: def OUTPUT_SOURCE_LANG(self, value): if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): self._OUTPUT_SOURCE_LANG = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def OUTPUT_TARGET_LANG(self): @@ -163,7 +170,7 @@ class Config: def OUTPUT_TARGET_LANG(self, value): if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): self._OUTPUT_TARGET_LANG = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def CHOICE_MIC_HOST(self): @@ -173,7 +180,7 @@ class Config: def CHOICE_MIC_HOST(self, value): if value in [host for host in get_input_device_list().keys()]: self._CHOICE_MIC_HOST = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def CHOICE_MIC_DEVICE(self): @@ -183,7 +190,7 @@ class Config: def CHOICE_MIC_DEVICE(self, value): if value in [device["name"] for device in get_input_device_list()[self.CHOICE_MIC_HOST]]: self._CHOICE_MIC_DEVICE = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_MIC_VOICE_LANGUAGE(self): @@ -193,7 +200,7 @@ class Config: def INPUT_MIC_VOICE_LANGUAGE(self, value): if value in list(transcription_lang.keys()): self._INPUT_MIC_VOICE_LANGUAGE = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_MIC_ENERGY_THRESHOLD(self): @@ -203,7 +210,7 @@ class Config: def INPUT_MIC_ENERGY_THRESHOLD(self, value): if type(value) is int: self._INPUT_MIC_ENERGY_THRESHOLD = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self): @@ -213,7 +220,7 @@ class Config: def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self, value): if type(value) is bool: self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_MIC_RECORD_TIMEOUT(self): @@ -223,7 +230,7 @@ class Config: def INPUT_MIC_RECORD_TIMEOUT(self, value): if type(value) is int: self._INPUT_MIC_RECORD_TIMEOUT = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_MIC_PHRASE_TIMEOUT(self): @@ -233,7 +240,7 @@ class Config: def INPUT_MIC_PHRASE_TIMEOUT(self, value): if type(value) is int: self._INPUT_MIC_PHRASE_TIMEOUT = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_MIC_MAX_PHRASES(self): @@ -243,7 +250,7 @@ class Config: def INPUT_MIC_MAX_PHRASES(self, value): if type(value) is int: self._INPUT_MIC_MAX_PHRASES = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_MIC_WORD_FILTER(self): @@ -253,7 +260,7 @@ class Config: def INPUT_MIC_WORD_FILTER(self, value): if type(value) is list: self._INPUT_MIC_WORD_FILTER = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def CHOICE_SPEAKER_DEVICE(self): @@ -265,7 +272,7 @@ class Config: speaker_device = [device for device in get_output_device_list() if device["name"] == value][0] if get_default_output_device()["index"] == speaker_device["index"]: self._CHOICE_SPEAKER_DEVICE = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_SPEAKER_VOICE_LANGUAGE(self): @@ -275,7 +282,7 @@ class Config: def INPUT_SPEAKER_VOICE_LANGUAGE(self, value): if value in list(transcription_lang.keys()): self._INPUT_SPEAKER_VOICE_LANGUAGE = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_SPEAKER_ENERGY_THRESHOLD(self): @@ -285,7 +292,7 @@ class Config: def INPUT_SPEAKER_ENERGY_THRESHOLD(self, value): if type(value) is int: self._INPUT_SPEAKER_ENERGY_THRESHOLD = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self): @@ -295,7 +302,7 @@ class Config: def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self, value): if type(value) is bool: self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_SPEAKER_RECORD_TIMEOUT(self): @@ -305,7 +312,7 @@ class Config: def INPUT_SPEAKER_RECORD_TIMEOUT(self, value): if type(value) is int: self._INPUT_SPEAKER_RECORD_TIMEOUT = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_SPEAKER_PHRASE_TIMEOUT(self): @@ -315,7 +322,7 @@ class Config: def INPUT_SPEAKER_PHRASE_TIMEOUT(self, value): if type(value) is int: self._INPUT_SPEAKER_PHRASE_TIMEOUT = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def INPUT_SPEAKER_MAX_PHRASES(self): @@ -325,7 +332,7 @@ class Config: def INPUT_SPEAKER_MAX_PHRASES(self, value): if type(value) is int: self._INPUT_SPEAKER_MAX_PHRASES = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def OSC_IP_ADDRESS(self): @@ -335,7 +342,7 @@ class Config: def OSC_IP_ADDRESS(self, value): if type(value) is str: self._OSC_IP_ADDRESS = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def OSC_PORT(self): @@ -345,7 +352,7 @@ class Config: def OSC_PORT(self, value): if type(value) is int: self._OSC_PORT = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def AUTH_KEYS(self): @@ -357,7 +364,7 @@ class Config: for key, value in value.items(): if type(value) is str: self._AUTH_KEYS[key] = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.AUTH_KEYS) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.AUTH_KEYS) @property def MESSAGE_FORMAT(self): @@ -367,7 +374,7 @@ class Config: def MESSAGE_FORMAT(self, value): if type(value) is str: self._MESSAGE_FORMAT = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def ENABLE_AUTO_CLEAR_CHATBOX(self): @@ -377,7 +384,7 @@ class Config: def ENABLE_AUTO_CLEAR_CHATBOX(self, value): if type(value) is bool: self._ENABLE_AUTO_CLEAR_CHATBOX = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def ENABLE_NOTICE_XSOVERLAY(self): @@ -387,7 +394,7 @@ class Config: def ENABLE_NOTICE_XSOVERLAY(self, value): if type(value) is bool: self._ENABLE_NOTICE_XSOVERLAY = value - save_json(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def ENABLE_OSC(self): diff --git a/model.py b/model.py index 348da461..1356c372 100644 --- a/model.py +++ b/model.py @@ -11,9 +11,9 @@ from audio_utils import get_input_device_list, get_output_device_list, get_defau from audio_recorder import SelectedMicRecorder, SelectedSpeakerRecorder from audio_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder from audio_transcriber import AudioTranscriber -from utils import print_textbox, thread_fnc -from config import config from notification import notification_xsoverlay_for_vrct +from utils import thread_fnc +from config import config class Model: _instance = None @@ -141,7 +141,7 @@ class Model: return True return False - def startMicTranscript(self, log, send_log, system_log): + def startMicTranscript(self, fnc): mic_audio_queue = Queue() self.mic_audio_recorder = SelectedMicRecorder( [device for device in get_input_device_list()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0], @@ -159,33 +159,7 @@ class Model: def mic_transcript_to_chatbox(): mic_transcriber.transcribe_audio_queue(mic_audio_queue, transcription_lang[config.INPUT_MIC_VOICE_LANGUAGE]) message = mic_transcriber.get_transcript() - if len(message) > 0: - # word filter - if self.checkKeywords(message): - print_textbox(log, f"Detect WordFilter :{message}", "INFO") - print_textbox(system_log, f"Detect WordFilter :{message}", "INFO") - return - - # translate - if config.ENABLE_TRANSLATION is False: - voice_message = f"{message}" - elif self.getTranslatorStatus() is False: - print_textbox(log, "Auth Key or language setting is incorrect", "ERROR") - print_textbox(system_log, "Auth Key or language setting is incorrect", "ERROR") - voice_message = f"{message}" - else: - voice_message = self.getInputTranslate(message) - - if config.ENABLE_TRANSCRIPTION_SEND is True: - if config.ENABLE_OSC is True: - # osc send message - model.oscSendMessage(voice_message) - else: - print_textbox(log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") - print_textbox(system_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") - # update textbox message log - print_textbox(log, f"{voice_message}", "SEND") - print_textbox(send_log, f"{voice_message}", "SEND") + fnc(message) self.mic_print_transcript = thread_fnc(mic_transcript_to_chatbox) self.mic_print_transcript.daemon = True @@ -221,7 +195,7 @@ class Model: if self.mic_energy_plot_progressbar != None: self.mic_energy_plot_progressbar.stop() - def startSpeakerTranscript(self, log, receive_log, system_log): + def startSpeakerTranscript(self, fnc): spk_audio_queue = Queue() spk_device = [device for device in get_output_device_list() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] self.spk_audio_recorder = SelectedSpeakerRecorder( @@ -240,23 +214,7 @@ class Model: def spk_transcript_to_textbox(): spk_transcriber.transcribe_audio_queue(spk_audio_queue, transcription_lang[config.INPUT_SPEAKER_VOICE_LANGUAGE]) message = spk_transcriber.get_transcript() - if len(message) > 0: - # translate - if config.ENABLE_TRANSLATION is False: - voice_message = f"{message}" - elif model.getTranslatorStatus() is False: - print_textbox(log, "Auth Key or language setting is incorrect", "ERROR") - print_textbox(system_log, "Auth Key or language setting is incorrect", "ERROR") - voice_message = f"{message}" - else: - voice_message = model.getOutputTranslate(message) - - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - # update textbox message receive log - print_textbox(log, f"{voice_message}", "RECEIVE") - print_textbox(receive_log, f"{voice_message}", "RECEIVE") - if config.ENABLE_NOTICE_XSOVERLAY is True: - notification_xsoverlay_for_vrct(content=f"{voice_message}") + fnc(message) self.spk_print_transcript = thread_fnc(spk_transcript_to_textbox) self.spk_print_transcript.daemon = True @@ -300,4 +258,7 @@ class Model: if self.speaker_energy_plot_progressbar != None: self.speaker_energy_plot_progressbar.stop() + def notificationXsoverlay(self, message): + notification_xsoverlay_for_vrct(content=f"{message}") + model = Model() diff --git a/utils.py b/utils.py index ac2eb35d..ffe7ade7 100644 --- a/utils.py +++ b/utils.py @@ -1,16 +1,8 @@ -from json import load, dump from os import path as os_path import yaml from datetime import datetime from threading import Thread, Event -def save_json(path, key, value): - with open(path, "r") as fp: - json_data = load(fp) - json_data[key] = value - with open(path, "w") as fp: - dump(json_data, fp, indent=4) - def print_textbox(textbox, message, tags=None): now = datetime.now() now = now.strftime('%H:%M:%S') @@ -58,7 +50,7 @@ def get_localized_text(language): return localized_text else: return None - + def get_key_by_value(dictionary, value): for key, val in dictionary.items(): if val == value: From 02d8fbdb1c4031812cd6e728aa4b40d0385b4ce3 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 20 Aug 2023 01:20:30 +0900 Subject: [PATCH 015/355] =?UTF-8?q?[Update]=20=E6=A9=9F=E8=83=BD=E9=83=A8?= =?UTF-8?q?=E5=88=86=E3=82=92models=E3=83=95=E3=82=A9=E3=83=AB=E3=83=80?= =?UTF-8?q?=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VRCT.py | 4 +- config.py | 10 +- languages.py | 336 ------------------ model.py | 53 +-- osc_tools.py => models/osc/osc_tools.py | 0 .../transcription/transcription_languages.py | 91 +++++ .../transcription/transcription_recorder.py | 0 .../transcription_transcriber.py | 3 +- .../transcription/transcription_utils.py | 0 models/translation/translation_languages.py | 243 +++++++++++++ .../translation/translation_translator.py | 4 +- .../xsoverlay/notification.py | 0 utils.py | 16 - window_config.py | 20 +- 14 files changed, 392 insertions(+), 388 deletions(-) rename osc_tools.py => models/osc/osc_tools.py (100%) create mode 100644 models/transcription/transcription_languages.py rename audio_recorder.py => models/transcription/transcription_recorder.py (100%) rename audio_transcriber.py => models/transcription/transcription_transcriber.py (97%) rename audio_utils.py => models/transcription/transcription_utils.py (100%) create mode 100644 models/translation/translation_languages.py rename translation.py => models/translation/translation_translator.py (95%) rename notification.py => models/xsoverlay/notification.py (100%) diff --git a/VRCT.py b/VRCT.py index fbc71654..565c4bdd 100644 --- a/VRCT.py +++ b/VRCT.py @@ -417,8 +417,8 @@ class App(CTk): print_textbox(self.textbox_message_send_log, f"{message}", "SEND") def printLogReceiveMessage(self, message): - print_textbox(self.textbox_message_log, f"{message}", "RECEIVE") - print_textbox(self.textbox_message_receive_log, f"{message}", "RECEIVE") + print_textbox(self.textbox_message_log, f"{message}", "RECEIVE") + print_textbox(self.textbox_message_receive_log, f"{message}", "RECEIVE") def sendChatMessage(self, message): if len(message) > 0: diff --git a/config.py b/config.py index c4edc384..ced57eab 100644 --- a/config.py +++ b/config.py @@ -5,8 +5,10 @@ from json import load as json_load from json import dump as json_dump import tkinter as tk from tkinter import font -from languages import transcription_lang, translators, translation_lang, selectable_languages -from audio_utils import get_input_device_list, get_output_device_list, get_default_input_device, get_default_output_device +from languages import selectable_languages +from models.translation.translation_languages import translatorEngine, translation_lang +from models.transcription.transcription_languages import transcription_lang +from models.transcription.transcription_utils import get_input_device_list, get_output_device_list, get_default_input_device, get_default_output_device def saveJson(path, key, value): with open(path, "r") as fp: @@ -128,7 +130,7 @@ class Config: @CHOICE_TRANSLATOR.setter def CHOICE_TRANSLATOR(self, value): - if value in translators: + if value in translatorEngine: self._CHOICE_TRANSLATOR = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @@ -442,7 +444,7 @@ class Config: self._UI_SCALING = "100%" self._FONT_FAMILY = "Yu Gothic UI" self._UI_LANGUAGE = "en" - self._CHOICE_TRANSLATOR = translators[0] + self._CHOICE_TRANSLATOR = translatorEngine[0] self._INPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[0] self._INPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[1] self._OUTPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[1] diff --git a/languages.py b/languages.py index 3862f4ff..bac2eb0f 100644 --- a/languages.py +++ b/languages.py @@ -1,339 +1,3 @@ -transcription_lang = { - "Japanese (Japan)":"ja-JP", - "English (United States)":"en-US", - "English (United Kingdom)":"en-GB", - "Afrikaans (South Africa)":"af-ZA", - "Arabic (Algeria)":"ar-DZ", - "Arabic (Bahrain)":"ar-BH", - "Arabic (Egypt)":"ar-EG", - "Arabic (Israel)":"ar-IL", - "Arabic (Iraq)":"ar-IQ", - "Arabic (Jordan)":"ar-JO", - "Arabic (Kuwait)":"ar-KW", - "Arabic (Lebanon)":"ar-LB", - "Arabic (Morocco)":"ar-MA", - "Arabic (Oman)":"ar-OM", - "Arabic (State of Palestine)":"ar-PS", - "Arabic (Qatar)":"ar-QA", - "Arabic (Saudi Arabia)":"ar-SA", - "Arabic (Tunisia)":"ar-TN", - "Arabic (United Arab Emirates)":"ar-AE", - "Basque (Spain)":"eu-ES", - "Bulgarian (Bulgaria)":"bg-BG", - "Catalan (Spain)":"ca-ES", - "Chinese, Mandarin (Simplified, China)":"cmn-Hans-CN", - "Chinese, Mandarin (Simplified, Hong Kong)":"cmn-Hans-HK", - "Chinese, Mandarin (Traditional, Taiwan)":"cmn-Hant-TW", - "Chinese, Cantonese (Traditional Hong Kong)":"yue-Hant-HK", - "Croatian (Croatia)":"hr-HR", - "Czech (Czech Republic)":"cs-CZ", - "Danish (Denmark)":"da-DK", - "English (Australia)":"en-AU", - "English (Canada)":"en-CA", - "English (India)":"en-IN", - "English (Ireland)":"en-IE", - "English (New Zealand)":"en-NZ", - "English (Philippines)":"en-PH", - "English (South Africa)":"en-ZA", - "Persian (Iran)":"fa-IR", - "French (France)":"fr-FR", - "Filipino (Philippines)":"fil-PH", - "Galician (Spain)":"gl-ES", - "German (Germany)":"de-DE", - "Greek (Greece)":"el-GR", - "Finnish (Finland)":"fi-FI", - "Hebrew (Israel)":"he-IL", - "Hindi (India)":"hi-IN", - "Hungarian (Hungary)":"hu-HU", - "Indonesian (Indonesia)":"id-ID", - "Icelandic (Iceland)":"is-IS", - "Italian (Italy)":"it-IT", - "Italian (Switzerland)":"it-CH", - "Korean (South Korea)":"ko-KR", - "Lithuanian (Lithuania)":"lt-LT", - "Malay (Malaysia)":"ms-MY", - "Dutch (Netherlands)":"nl-NL", - "Norwegian Bokmål (Norway)":"nb-NO", - "Polish (Poland)":"pl-PL", - "Portuguese (Brazil)":"pt-BR", - "Portuguese (Portugal)":"pt-PT", - "Romanian (Romania)":"ro-RO", - "Russian (Russia)":"ru-RU", - "Serbian (Serbia)":"sr-RS", - "Slovak (Slovakia)":"sk-SK", - "Slovenian (Slovenia)":"sl-SI", - "Spanish (Argentina)":"es-AR", - "Spanish (Bolivia)":"es-BO", - "Spanish (Chile)":"es-CL", - "Spanish (Colombia)":"es-CO", - "Spanish (Costa Rica)":"es-CR", - "Spanish (Dominican Republic)":"es-DO", - "Spanish (Ecuador)":"es-EC", - "Spanish (El Salvador)":"es-SV", - "Spanish (Guatemala)":"es-GT", - "Spanish (Honduras)":"es-HN", - "Spanish (Mexico)":"es-MX", - "Spanish (Nicaragua)":"es-NI", - "Spanish (Panama)":"es-PA", - "Spanish (Paraguay)":"es-PY", - "Spanish (Peru)":"es-PE", - "Spanish (Puerto Rico)":"es-PR", - "Spanish (Spain)":"es-ES", - "Spanish (Uruguay)":"es-UY", - "Spanish (United States)":"es-US", - "Spanish (Venezuela)":"es-VE", - "Swedish (Sweden)":"sv-SE", - "Thai (Thailand)":"th-TH", - "Turkish (Turkey)":"tr-TR", - "Ukrainian (Ukraine)":"uk-UA", - "Vietnamese (Vietnam)":"vi-VN", - "Zulu (South Africa)":"zu-ZA" -} - -translators = ["DeepL(web)", "DeepL(auth)", "Google(web)", "Bing(web)"] -translation_lang = {} -dict_deepl_web_languages = { - "Japanese":"JA", - "English":"EN", - "Korean":"KO", - "Bulgarian":"BG", - "Chinese":"ZH", - "Czech":"CS", - "Danish":"DA", - "Dutch":"NL", - "Estonian":"ET", - "Finnish":"FI", - "French":"FR", - "German":"DE", - "Greek":"EL", - "Hungarian":"HU", - "Italian":"IT", - "Latvian":"LV", - "Lithuanian":"LT", - "Polish":"PL", - "Portuguese":"PT", - "Romanian":"RO", - "Russian":"RU", - "Slovak":"SK", - "Slovenian":"SL", - "Spanish":"ES", - "Swedish":"SV", - "Indonesian":"ID", - "Ukrainian":"UK", - "Turkish":"TR", - "Norwegian":"NB", -} -translation_lang["DeepL(web)"] = { - "source":dict_deepl_web_languages, - "target":dict_deepl_web_languages, -} - -dict_deepl_auth_source_languages = { - "Japanese":"ja", - "English":"en", - "Bulgarian":"bg", - "Czech":"cs", - "Danish":"da", - "German":"de", - "Greek":"el", - "Spanish":"es", - "Estonian":"et", - "Finnish":"fi", - "French":"fr", - "Hungarian":"hu", - "Indonesian":"id", - "Italian":"it", - "Korean":"ko", - "Lithuanian":"lt", - "Latvian":"lv", - "Norwegian":"nb", - "Dutch":"nl", - "Polish":"pl", - "Portuguese":"pt", - "Romanian":"ro", - "Russian":"ru", - "Slovak":"sk", - "Slovenian":"sl", - "Swedish":"sv", - "Turkish":"tr", - "Ukrainian":"uk", - "Chinese":"zh" -} -dict_deepl_auth_target_languages = { - "Japanese":"ja", - "English American":"en-US", - "English British":"en-GB", - "Bulgarian":"bg", - "Czech":"cs", - "Danish":"da", - "German":"de", - "Greek":"el", - "English":"en", - "Spanish":"es", - "Estonian":"et", - "Finnish":"fi", - "French":"fr", - "Hungarian":"hu", - "Indonesian":"id", - "Italian":"it", - "Korean":"ko", - "Lithuanian":"lt", - "Latvian":"lv", - "Norwegian":"nb", - "Dutch":"nl", - "Polish":"pl", - "Portuguese Brazilian":"pt-BR", - "Portuguese European":"pt-PT", - "Romanian":"ro", - "Russian":"ru", - "Slovak":"sk", - "Slovenian":"sl", - "Swedish":"sv", - "Turkish":"tr", - "Ukrainian":"uk", - "Chinese":"zh" -} -translation_lang["DeepL(auth)"] = { - "source": dict_deepl_auth_source_languages, - "target": dict_deepl_auth_target_languages, -} - -dict_google_web_languages = { - "Japanese":"ja", - "English":"en", - "Chinese":"zh", - "Arabic":"ar", - "Russian":"ru", - "French":"fr", - "German":"de", - "Spanish":"es", - "Portuguese":"pt", - "Italian":"it", - "Korean":"ko", - "Greek":"el", - "Dutch":"nl", - "Hindi":"hi", - "Turkish":"tr", - "Malay":"ms", - "Thai":"th", - "Vietnamese":"vi", - "Indonesian":"id", - "Hebrew":"he", - "Polish":"pl", - "Mongolian":"mn", - "Czech":"cs", - "Hungarian":"hu", - "Estonian":"et", - "Bulgarian":"bg", - "Danish":"da", - "Finnish":"fi", - "Romanian":"ro", - "Swedish":"sv", - "Slovenian":"sl", - "Persian/Farsi":"fa", - "Bosnian":"bs", - "Serbian":"sr", - "Filipino":"tl", - "Haitiancreole":"ht", - "Catalan":"ca", - "Croatian":"hr", - "Latvian":"lv", - "Lithuanian":"lt", - "Urdu":"ur", - "Ukrainian":"uk", - "Welsh":"cy", - "Swahili":"sw", - "Samoan":"sm", - "Slovak":"sk", - "Afrikaans":"af", - "Norwegian":"no", - "Bengali":"bn", - "Malagasy":"mg", - "Maltese":"mt", - "Gujarati":"gu", - "Tamil":"ta", - "Telugu":"te", - "Punjabi":"pa", - "Amharic":"am", - "Azerbaijani":"az", - "Belarusian":"be", - "Cebuano":"ceb", - "Esperanto":"eo", - "Basque":"eu", - "Irish":"ga" -} -translation_lang["Google(web)"] = { - "source":dict_google_web_languages, - "target":dict_google_web_languages, -} - -dict_bing_web_languages = { - "Japanese":"ja", - "English":"en", - "Chinese":"zh", - "Arabic":"ar", - "Russian":"ru", - "French":"fr", - "German":"de", - "Spanish":"es", - "Portuguese":"pt", - "Italian":"it", - "Korean":"ko", - "Greek":"el", - "Dutch":"nl", - "Hindi":"hi", - "Turkish":"tr", - "Malay":"ms", - "Thai":"th", - "Vietnamese":"vi", - "Indonesian":"id", - "Hebrew":"he", - "Polish":"pl", - "Czech":"cs", - "Hungarian":"hu", - "Estonian":"et", - "Bulgarian":"bg", - "Danish":"da", - "Finnish":"fi", - "Romanian":"ro", - "Swedish":"sv", - "Slovenian":"sl", - "Persian/Farsi":"fa", - "Bosnian":"bs", - "Serbian":"sr", - "Fijian":"fj", - "Filipino":"tl", - "Haitiancreole":"ht", - "Catalan":"ca", - "Croatian":"hr", - "Latvian":"lv", - "Lithuanian":"lt", - "Urdu":"ur", - "Ukrainian":"uk", - "Welsh":"cy", - "Tahiti":"ty", - "Tongan":"to", - "Swahili":"sw", - "Samoan":"sm", - "Slovak":"sk", - "Afrikaans":"af", - "Norwegian":"no", - "Bengali":"bn", - "Malagasy":"mg", - "Maltese":"mt", - "Queretaro otomi":"otq", - "Klingon/tlhingan Hol":"tlh", - "Gujarati":"gu", - "Tamil":"ta", - "Telugu":"te", - "Punjabi":"pa", - "Irish":"ga" -} -translation_lang["Bing(web)"] = { - "source":dict_bing_web_languages, - "target":dict_bing_web_languages, -} - selectable_languages = { "en": "English", "ja": "日本語", diff --git a/model.py b/model.py index 1356c372..ef5057a4 100644 --- a/model.py +++ b/model.py @@ -1,20 +1,33 @@ from time import sleep from queue import Queue -from threading import Thread +from threading import Thread, Event from requests import get as requests_get -from translation import Translator from flashtext import KeywordProcessor -from osc_tools import send_typing, send_message, send_test_action, receive_osc_parameters -from languages import transcription_lang -from audio_utils import get_input_device_list, get_output_device_list, get_default_output_device -from audio_recorder import SelectedMicRecorder, SelectedSpeakerRecorder -from audio_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder -from audio_transcriber import AudioTranscriber -from notification import notification_xsoverlay_for_vrct -from utils import thread_fnc +from models.translation.translation_translator import Translator +from models.osc.osc_tools import send_typing, send_message, send_test_action, receive_osc_parameters +from models.transcription.transcription_utils import get_input_device_list, get_output_device_list, get_default_input_device, get_default_output_device +from models.transcription.transcription_recorder import SelectedMicRecorder, SelectedSpeakerRecorder +from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder +from models.transcription.transcription_transcriber import AudioTranscriber +from models.xsoverlay.notification import notification_xsoverlay_for_vrct from config import config +class thread_fnc(Thread): + def __init__(self, fnc, daemon=True, *args, **kwargs): + super(thread_fnc, self).__init__(daemon=daemon, *args, **kwargs) + self.fnc = fnc + self._stop = Event() + def stop(self): + self._stop.set() + def stopped(self): + return self._stop.isSet() + def run(self): + while True: + if self.stopped(): + return + self.fnc(*self._args, **self._kwargs) + class Model: _instance = None @@ -157,7 +170,7 @@ class Model: max_phrases=config.INPUT_MIC_MAX_PHRASES, ) def mic_transcript_to_chatbox(): - mic_transcriber.transcribe_audio_queue(mic_audio_queue, transcription_lang[config.INPUT_MIC_VOICE_LANGUAGE]) + mic_transcriber.transcribe_audio_queue(mic_audio_queue, config.INPUT_MIC_VOICE_LANGUAGE) message = mic_transcriber.get_transcript() fnc(message) @@ -172,14 +185,11 @@ class Model: self.mic_audio_recorder.stop() self.mic_audio_recorder.stop = None - def startCheckMicEnergy(self, progressBar): + def startCheckMicEnergy(self, fnc): def progressBarInputMicEnergyPlot(): if mic_energy_queue.empty() is False: energy = mic_energy_queue.get() - try: - progressBar.set(energy/config.MAX_MIC_ENERGY_THRESHOLD) - except: - pass + fnc(energy) sleep(0.01) mic_energy_queue = Queue() mic_device = [device for device in get_input_device_list()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] @@ -212,7 +222,7 @@ class Model: max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, ) def spk_transcript_to_textbox(): - spk_transcriber.transcribe_audio_queue(spk_audio_queue, transcription_lang[config.INPUT_SPEAKER_VOICE_LANGUAGE]) + spk_transcriber.transcribe_audio_queue(spk_audio_queue, config.INPUT_SPEAKER_VOICE_LANGUAGE) message = spk_transcriber.get_transcript() fnc(message) @@ -227,20 +237,17 @@ class Model: self.spk_audio_recorder.stop() self.spk_audio_recorder.stop = None - def startCheckSpeakerEnergy(self, progressBar): + def startCheckSpeakerEnergy(self, fnc): def progressBar_input_speaker_energy_plot(): if speaker_energy_queue.empty() is False: energy = speaker_energy_queue.get() - try: - progressBar.set(energy/config.MAX_SPEAKER_ENERGY_THRESHOLD) - except: - pass + fnc(energy) sleep(0.01) def progressBar_input_speaker_energy_get(): with self.speaker_energy_recorder.source as source: energy = self.speaker_energy_recorder.recorder.listen_energy(source) - self.speaker_energy_queue.put(energy) + speaker_energy_queue.put(energy) speaker_device = [device for device in get_output_device_list() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] speaker_energy_queue = Queue() diff --git a/osc_tools.py b/models/osc/osc_tools.py similarity index 100% rename from osc_tools.py rename to models/osc/osc_tools.py diff --git a/models/transcription/transcription_languages.py b/models/transcription/transcription_languages.py new file mode 100644 index 00000000..2cf6ebd3 --- /dev/null +++ b/models/transcription/transcription_languages.py @@ -0,0 +1,91 @@ +transcription_lang = { + "Japanese (Japan)":"ja-JP", + "English (United States)":"en-US", + "English (United Kingdom)":"en-GB", + "Afrikaans (South Africa)":"af-ZA", + "Arabic (Algeria)":"ar-DZ", + "Arabic (Bahrain)":"ar-BH", + "Arabic (Egypt)":"ar-EG", + "Arabic (Israel)":"ar-IL", + "Arabic (Iraq)":"ar-IQ", + "Arabic (Jordan)":"ar-JO", + "Arabic (Kuwait)":"ar-KW", + "Arabic (Lebanon)":"ar-LB", + "Arabic (Morocco)":"ar-MA", + "Arabic (Oman)":"ar-OM", + "Arabic (State of Palestine)":"ar-PS", + "Arabic (Qatar)":"ar-QA", + "Arabic (Saudi Arabia)":"ar-SA", + "Arabic (Tunisia)":"ar-TN", + "Arabic (United Arab Emirates)":"ar-AE", + "Basque (Spain)":"eu-ES", + "Bulgarian (Bulgaria)":"bg-BG", + "Catalan (Spain)":"ca-ES", + "Chinese, Mandarin (Simplified, China)":"cmn-Hans-CN", + "Chinese, Mandarin (Simplified, Hong Kong)":"cmn-Hans-HK", + "Chinese, Mandarin (Traditional, Taiwan)":"cmn-Hant-TW", + "Chinese, Cantonese (Traditional Hong Kong)":"yue-Hant-HK", + "Croatian (Croatia)":"hr-HR", + "Czech (Czech Republic)":"cs-CZ", + "Danish (Denmark)":"da-DK", + "English (Australia)":"en-AU", + "English (Canada)":"en-CA", + "English (India)":"en-IN", + "English (Ireland)":"en-IE", + "English (New Zealand)":"en-NZ", + "English (Philippines)":"en-PH", + "English (South Africa)":"en-ZA", + "Persian (Iran)":"fa-IR", + "French (France)":"fr-FR", + "Filipino (Philippines)":"fil-PH", + "Galician (Spain)":"gl-ES", + "German (Germany)":"de-DE", + "Greek (Greece)":"el-GR", + "Finnish (Finland)":"fi-FI", + "Hebrew (Israel)":"he-IL", + "Hindi (India)":"hi-IN", + "Hungarian (Hungary)":"hu-HU", + "Indonesian (Indonesia)":"id-ID", + "Icelandic (Iceland)":"is-IS", + "Italian (Italy)":"it-IT", + "Italian (Switzerland)":"it-CH", + "Korean (South Korea)":"ko-KR", + "Lithuanian (Lithuania)":"lt-LT", + "Malay (Malaysia)":"ms-MY", + "Dutch (Netherlands)":"nl-NL", + "Norwegian Bokmål (Norway)":"nb-NO", + "Polish (Poland)":"pl-PL", + "Portuguese (Brazil)":"pt-BR", + "Portuguese (Portugal)":"pt-PT", + "Romanian (Romania)":"ro-RO", + "Russian (Russia)":"ru-RU", + "Serbian (Serbia)":"sr-RS", + "Slovak (Slovakia)":"sk-SK", + "Slovenian (Slovenia)":"sl-SI", + "Spanish (Argentina)":"es-AR", + "Spanish (Bolivia)":"es-BO", + "Spanish (Chile)":"es-CL", + "Spanish (Colombia)":"es-CO", + "Spanish (Costa Rica)":"es-CR", + "Spanish (Dominican Republic)":"es-DO", + "Spanish (Ecuador)":"es-EC", + "Spanish (El Salvador)":"es-SV", + "Spanish (Guatemala)":"es-GT", + "Spanish (Honduras)":"es-HN", + "Spanish (Mexico)":"es-MX", + "Spanish (Nicaragua)":"es-NI", + "Spanish (Panama)":"es-PA", + "Spanish (Paraguay)":"es-PY", + "Spanish (Peru)":"es-PE", + "Spanish (Puerto Rico)":"es-PR", + "Spanish (Spain)":"es-ES", + "Spanish (Uruguay)":"es-UY", + "Spanish (United States)":"es-US", + "Spanish (Venezuela)":"es-VE", + "Swedish (Sweden)":"sv-SE", + "Thai (Thailand)":"th-TH", + "Turkish (Turkey)":"tr-TR", + "Ukrainian (Ukraine)":"uk-UA", + "Vietnamese (Vietnam)":"vi-VN", + "Zulu (South Africa)":"zu-ZA" +} \ No newline at end of file diff --git a/audio_recorder.py b/models/transcription/transcription_recorder.py similarity index 100% rename from audio_recorder.py rename to models/transcription/transcription_recorder.py diff --git a/audio_transcriber.py b/models/transcription/transcription_transcriber.py similarity index 97% rename from audio_transcriber.py rename to models/transcription/transcription_transcriber.py index 94c858e7..316dd22b 100644 --- a/audio_transcriber.py +++ b/models/transcription/transcription_transcriber.py @@ -4,6 +4,7 @@ import wave from speech_recognition import Recognizer, AudioData, AudioFile from datetime import timedelta from pyaudiowpatch import get_sample_size, paInt16 +from .transcription_languages import transcription_lang PHRASE_TIMEOUT = 3 MAX_PHRASES = 10 @@ -36,7 +37,7 @@ class AudioTranscriber: # fd, path = tempfile.mkstemp(suffix=".wav") # os.close(fd) audio_data = self.audio_sources["process_data_func"]() - text = self.audio_recognizer.recognize_google(audio_data, language=language) + text = self.audio_recognizer.recognize_google(audio_data, language=transcription_lang[language]) except Exception as e: pass finally: diff --git a/audio_utils.py b/models/transcription/transcription_utils.py similarity index 100% rename from audio_utils.py rename to models/transcription/transcription_utils.py diff --git a/models/translation/translation_languages.py b/models/translation/translation_languages.py new file mode 100644 index 00000000..1b68bf81 --- /dev/null +++ b/models/translation/translation_languages.py @@ -0,0 +1,243 @@ +translatorEngine = ["DeepL(web)", "DeepL(auth)", "Google(web)", "Bing(web)"] +translation_lang = {} +dict_deepl_web_languages = { + "Japanese":"JA", + "English":"EN", + "Korean":"KO", + "Bulgarian":"BG", + "Chinese":"ZH", + "Czech":"CS", + "Danish":"DA", + "Dutch":"NL", + "Estonian":"ET", + "Finnish":"FI", + "French":"FR", + "German":"DE", + "Greek":"EL", + "Hungarian":"HU", + "Italian":"IT", + "Latvian":"LV", + "Lithuanian":"LT", + "Polish":"PL", + "Portuguese":"PT", + "Romanian":"RO", + "Russian":"RU", + "Slovak":"SK", + "Slovenian":"SL", + "Spanish":"ES", + "Swedish":"SV", + "Indonesian":"ID", + "Ukrainian":"UK", + "Turkish":"TR", + "Norwegian":"NB", +} +translation_lang["DeepL(web)"] = { + "source":dict_deepl_web_languages, + "target":dict_deepl_web_languages, +} + +dict_deepl_auth_source_languages = { + "Japanese":"ja", + "English":"en", + "Bulgarian":"bg", + "Czech":"cs", + "Danish":"da", + "German":"de", + "Greek":"el", + "Spanish":"es", + "Estonian":"et", + "Finnish":"fi", + "French":"fr", + "Hungarian":"hu", + "Indonesian":"id", + "Italian":"it", + "Korean":"ko", + "Lithuanian":"lt", + "Latvian":"lv", + "Norwegian":"nb", + "Dutch":"nl", + "Polish":"pl", + "Portuguese":"pt", + "Romanian":"ro", + "Russian":"ru", + "Slovak":"sk", + "Slovenian":"sl", + "Swedish":"sv", + "Turkish":"tr", + "Ukrainian":"uk", + "Chinese":"zh" +} +dict_deepl_auth_target_languages = { + "Japanese":"ja", + "English American":"en-US", + "English British":"en-GB", + "Bulgarian":"bg", + "Czech":"cs", + "Danish":"da", + "German":"de", + "Greek":"el", + "English":"en", + "Spanish":"es", + "Estonian":"et", + "Finnish":"fi", + "French":"fr", + "Hungarian":"hu", + "Indonesian":"id", + "Italian":"it", + "Korean":"ko", + "Lithuanian":"lt", + "Latvian":"lv", + "Norwegian":"nb", + "Dutch":"nl", + "Polish":"pl", + "Portuguese Brazilian":"pt-BR", + "Portuguese European":"pt-PT", + "Romanian":"ro", + "Russian":"ru", + "Slovak":"sk", + "Slovenian":"sl", + "Swedish":"sv", + "Turkish":"tr", + "Ukrainian":"uk", + "Chinese":"zh" +} +translation_lang["DeepL(auth)"] = { + "source": dict_deepl_auth_source_languages, + "target": dict_deepl_auth_target_languages, +} + +dict_google_web_languages = { + "Japanese":"ja", + "English":"en", + "Chinese":"zh", + "Arabic":"ar", + "Russian":"ru", + "French":"fr", + "German":"de", + "Spanish":"es", + "Portuguese":"pt", + "Italian":"it", + "Korean":"ko", + "Greek":"el", + "Dutch":"nl", + "Hindi":"hi", + "Turkish":"tr", + "Malay":"ms", + "Thai":"th", + "Vietnamese":"vi", + "Indonesian":"id", + "Hebrew":"he", + "Polish":"pl", + "Mongolian":"mn", + "Czech":"cs", + "Hungarian":"hu", + "Estonian":"et", + "Bulgarian":"bg", + "Danish":"da", + "Finnish":"fi", + "Romanian":"ro", + "Swedish":"sv", + "Slovenian":"sl", + "Persian/Farsi":"fa", + "Bosnian":"bs", + "Serbian":"sr", + "Filipino":"tl", + "Haitiancreole":"ht", + "Catalan":"ca", + "Croatian":"hr", + "Latvian":"lv", + "Lithuanian":"lt", + "Urdu":"ur", + "Ukrainian":"uk", + "Welsh":"cy", + "Swahili":"sw", + "Samoan":"sm", + "Slovak":"sk", + "Afrikaans":"af", + "Norwegian":"no", + "Bengali":"bn", + "Malagasy":"mg", + "Maltese":"mt", + "Gujarati":"gu", + "Tamil":"ta", + "Telugu":"te", + "Punjabi":"pa", + "Amharic":"am", + "Azerbaijani":"az", + "Belarusian":"be", + "Cebuano":"ceb", + "Esperanto":"eo", + "Basque":"eu", + "Irish":"ga" +} +translation_lang["Google(web)"] = { + "source":dict_google_web_languages, + "target":dict_google_web_languages, +} + +dict_bing_web_languages = { + "Japanese":"ja", + "English":"en", + "Chinese":"zh", + "Arabic":"ar", + "Russian":"ru", + "French":"fr", + "German":"de", + "Spanish":"es", + "Portuguese":"pt", + "Italian":"it", + "Korean":"ko", + "Greek":"el", + "Dutch":"nl", + "Hindi":"hi", + "Turkish":"tr", + "Malay":"ms", + "Thai":"th", + "Vietnamese":"vi", + "Indonesian":"id", + "Hebrew":"he", + "Polish":"pl", + "Czech":"cs", + "Hungarian":"hu", + "Estonian":"et", + "Bulgarian":"bg", + "Danish":"da", + "Finnish":"fi", + "Romanian":"ro", + "Swedish":"sv", + "Slovenian":"sl", + "Persian/Farsi":"fa", + "Bosnian":"bs", + "Serbian":"sr", + "Fijian":"fj", + "Filipino":"tl", + "Haitiancreole":"ht", + "Catalan":"ca", + "Croatian":"hr", + "Latvian":"lv", + "Lithuanian":"lt", + "Urdu":"ur", + "Ukrainian":"uk", + "Welsh":"cy", + "Tahiti":"ty", + "Tongan":"to", + "Swahili":"sw", + "Samoan":"sm", + "Slovak":"sk", + "Afrikaans":"af", + "Norwegian":"no", + "Bengali":"bn", + "Malagasy":"mg", + "Maltese":"mt", + "Queretaro otomi":"otq", + "Klingon/tlhingan Hol":"tlh", + "Gujarati":"gu", + "Tamil":"ta", + "Telugu":"te", + "Punjabi":"pa", + "Irish":"ga" +} +translation_lang["Bing(web)"] = { + "source":dict_bing_web_languages, + "target":dict_bing_web_languages, +} \ No newline at end of file diff --git a/translation.py b/models/translation/translation_translator.py similarity index 95% rename from translation.py rename to models/translation/translation_translator.py index e251c8a9..b386835e 100644 --- a/translation.py +++ b/models/translation/translation_translator.py @@ -1,13 +1,13 @@ from deepl import Translator as deepl_Translator from deepl_translate import translate as deepl_web_Translator from translators import translate_text as other_web_Translator -from languages import translators, translation_lang +from .translation_languages import translatorEngine, translation_lang # Translator class Translator(): def __init__(self): self.translator_status = {} - for translator in translators: + for translator in translatorEngine: self.translator_status[translator] = False self.deepl_client = None diff --git a/notification.py b/models/xsoverlay/notification.py similarity index 100% rename from notification.py rename to models/xsoverlay/notification.py diff --git a/utils.py b/utils.py index ffe7ade7..2bee5814 100644 --- a/utils.py +++ b/utils.py @@ -1,7 +1,6 @@ from os import path as os_path import yaml from datetime import datetime -from threading import Thread, Event def print_textbox(textbox, message, tags=None): now = datetime.now() @@ -19,21 +18,6 @@ def print_textbox(textbox, message, tags=None): textbox.configure(state='disabled') textbox.see("end") -class thread_fnc(Thread): - def __init__(self, fnc, daemon=True, *args, **kwargs): - super(thread_fnc, self).__init__(daemon=daemon, *args, **kwargs) - self.fnc = fnc - self._stop = Event() - def stop(self): - self._stop.set() - def stopped(self): - return self._stop.isSet() - def run(self): - while True: - if self.stopped(): - return - self.fnc(*self._args, **self._kwargs) - def get_localized_text(language): file_path = os_path.join(os_path.dirname(__file__), "locales.yml") diff --git a/window_config.py b/window_config.py index b54429cf..326a7a11 100644 --- a/window_config.py +++ b/window_config.py @@ -8,8 +8,9 @@ from threading import Thread from config import config from model import model from utils import print_textbox, get_localized_text, get_key_by_value, widget_config_window_label_setter -from languages import translation_lang, transcription_lang, selectable_languages - +from languages import selectable_languages +from models.translation.translation_languages import translation_lang +from models.transcription.transcription_languages import transcription_lang from ctk_scrollable_dropdown import CTkScrollableDropdown SCROLLABLE_DROPDOWN = False @@ -261,7 +262,12 @@ class ToplevelWindowConfig(CTkToplevel): config.INPUT_MIC_VOICE_LANGUAGE = choice def mic_threshold_check_start(self): - model.startCheckMicEnergy(self.progressBar_input_mic_energy_threshold) + def plotProgressBar(energy): + try: + self.progressBar_input_mic_energy_threshold.set(energy/config.MAX_MIC_ENERGY_THRESHOLD) + except: + pass + model.startCheckMicEnergy(plotProgressBar) self.checkbox_input_mic_threshold_check.configure(state="normal") self.checkbox_input_speaker_threshold_check.configure(state="normal") @@ -324,7 +330,13 @@ class ToplevelWindowConfig(CTkToplevel): config.INPUT_SPEAKER_VOICE_LANGUAGE = choice def speaker_threshold_check_start(self): - model.startCheckSpeakerEnergy(self.progressBar_input_speaker_energy_threshold) + def plotProgressBar(energy): + try: + print(energy) + self.progressBar_input_speaker_energy_threshold.set(energy/config.MAX_MIC_ENERGY_THRESHOLD) + except: + pass + model.startCheckSpeakerEnergy(plotProgressBar) self.checkbox_input_mic_threshold_check.configure(state="normal") self.checkbox_input_speaker_threshold_check.configure(state="normal") From 1470187c02cdc279c7fe58d198089e4157302c55 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 20 Aug 2023 01:33:43 +0900 Subject: [PATCH 016/355] [Update] change osc_tools.py func name --- model.py | 30 +++++++++++++++--------------- models/osc/osc_tools.py | 8 ++++---- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/model.py b/model.py index ef5057a4..5f2409cb 100644 --- a/model.py +++ b/model.py @@ -5,17 +5,17 @@ from requests import get as requests_get from flashtext import KeywordProcessor from models.translation.translation_translator import Translator -from models.osc.osc_tools import send_typing, send_message, send_test_action, receive_osc_parameters from models.transcription.transcription_utils import get_input_device_list, get_output_device_list, get_default_input_device, get_default_output_device +from models.osc.osc_tools import sendTyping, sendMessage, sendTestAction, receiveOscParameters from models.transcription.transcription_recorder import SelectedMicRecorder, SelectedSpeakerRecorder from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder from models.transcription.transcription_transcriber import AudioTranscriber from models.xsoverlay.notification import notification_xsoverlay_for_vrct from config import config -class thread_fnc(Thread): +class threadFnc(Thread): def __init__(self, fnc, daemon=True, *args, **kwargs): - super(thread_fnc, self).__init__(daemon=daemon, *args, **kwargs) + super(threadFnc, self).__init__(daemon=daemon, *args, **kwargs) self.fnc = fnc self._stop = Event() def stop(self): @@ -101,15 +101,15 @@ class Model: @staticmethod def oscStartSendTyping(): - send_typing(True, config.OSC_IP_ADDRESS, config.OSC_PORT) + sendTyping(True, config.OSC_IP_ADDRESS, config.OSC_PORT) @staticmethod def oscStopSendTyping(): - send_typing(False, config.OSC_IP_ADDRESS, config.OSC_PORT) + sendTyping(False, config.OSC_IP_ADDRESS, config.OSC_PORT) @staticmethod def oscSendMessage(message): - send_message(message, config.OSC_IP_ADDRESS, config.OSC_PORT) + sendMessage(message, config.OSC_IP_ADDRESS, config.OSC_PORT) @staticmethod def oscCheck(): @@ -118,12 +118,12 @@ class Model: config.ENABLE_OSC = True # start receive osc - th_receive_osc_parameters = Thread(target=receive_osc_parameters, args=(check_osc_receive,)) + th_receive_osc_parameters = Thread(target=receiveOscParameters, args=(check_osc_receive,)) th_receive_osc_parameters.daemon = True th_receive_osc_parameters.start() # check osc started - send_test_action() + sendTestAction() # check update response = requests_get(config.GITHUB_URL) @@ -174,12 +174,12 @@ class Model: message = mic_transcriber.get_transcript() fnc(message) - self.mic_print_transcript = thread_fnc(mic_transcript_to_chatbox) + self.mic_print_transcript = threadFnc(mic_transcript_to_chatbox) self.mic_print_transcript.daemon = True self.mic_print_transcript.start() def stopMicTranscript(self): - if isinstance(self.mic_print_transcript, thread_fnc): + if isinstance(self.mic_print_transcript, threadFnc): self.mic_print_transcript.stop() if self.mic_audio_recorder.stop != None: self.mic_audio_recorder.stop() @@ -195,7 +195,7 @@ class Model: mic_device = [device for device in get_input_device_list()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] self.mic_energy_recorder = SelectedMicEnergyRecorder(mic_device) self.mic_energy_recorder.record_into_queue(mic_energy_queue) - self.mic_energy_plot_progressbar = thread_fnc(progressBarInputMicEnergyPlot) + self.mic_energy_plot_progressbar = threadFnc(progressBarInputMicEnergyPlot) self.mic_energy_plot_progressbar.daemon = True self.mic_energy_plot_progressbar.start() @@ -226,12 +226,12 @@ class Model: message = spk_transcriber.get_transcript() fnc(message) - self.spk_print_transcript = thread_fnc(spk_transcript_to_textbox) + self.spk_print_transcript = threadFnc(spk_transcript_to_textbox) self.spk_print_transcript.daemon = True self.spk_print_transcript.start() def stopSpeakerTranscript(self): - if isinstance(self.spk_print_transcript, thread_fnc): + if isinstance(self.spk_print_transcript, threadFnc): self.spk_print_transcript.stop() if self.spk_audio_recorder.stop != None: self.spk_audio_recorder.stop() @@ -252,10 +252,10 @@ class Model: speaker_device = [device for device in get_output_device_list() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] speaker_energy_queue = Queue() self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(speaker_device) - self.speaker_energy_get_progressbar = thread_fnc(progressBar_input_speaker_energy_get) + self.speaker_energy_get_progressbar = threadFnc(progressBar_input_speaker_energy_get) self.speaker_energy_get_progressbar.daemon = True self.speaker_energy_get_progressbar.start() - self.speaker_energy_plot_progressbar = thread_fnc(progressBar_input_speaker_energy_plot) + self.speaker_energy_plot_progressbar = threadFnc(progressBar_input_speaker_energy_plot) self.speaker_energy_plot_progressbar.daemon = True self.speaker_energy_plot_progressbar.start() diff --git a/models/osc/osc_tools.py b/models/osc/osc_tools.py index 7c7d9504..b44e17cb 100644 --- a/models/osc/osc_tools.py +++ b/models/osc/osc_tools.py @@ -6,7 +6,7 @@ from pythonosc import dispatcher from pythonosc import osc_server # send OSC message typing -def send_typing(flag=False, ip_address="127.0.0.1", port=9000): +def sendTyping(flag=False, ip_address="127.0.0.1", port=9000): typing = osc_message_builder.OscMessageBuilder(address="/chatbox/typing") typing.add_arg(flag) b_typing = typing.build() @@ -14,7 +14,7 @@ def send_typing(flag=False, ip_address="127.0.0.1", port=9000): client.send(b_typing) # send OSC message -def send_message(message=None, ip_address="127.0.0.1", port=9000): +def sendMessage(message=None, ip_address="127.0.0.1", port=9000): if message != None: msg = osc_message_builder.OscMessageBuilder(address="/chatbox/input") msg.add_arg(f"{message}") @@ -24,13 +24,13 @@ def send_message(message=None, ip_address="127.0.0.1", port=9000): client = udp_client.SimpleUDPClient(ip_address, port) client.send(b_msg) -def send_test_action(ip_address="127.0.0.1", port=9000): +def sendTestAction(ip_address="127.0.0.1", port=9000): client = udp_client.SimpleUDPClient(ip_address, port) client.send_message("/input/Vertical", 1) sleep(0.01) client.send_message("/input/Vertical", False) -def receive_osc_parameters(target, filter="/*", ip_address="127.0.0.1", port=9001): +def receiveOscParameters(target, filter="/*", ip_address="127.0.0.1", port=9001): _dispatcher = dispatcher.Dispatcher() _dispatcher.map(filter, target) server = osc_server.ThreadingOSCUDPServer((ip_address, port), _dispatcher) From 2be54f9aee7315cfe64a7b75e4e239f8a380a0a7 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 20 Aug 2023 01:40:01 +0900 Subject: [PATCH 017/355] [Update] change transcription_utils.py func name --- config.py | 18 ++++++++--------- model.py | 22 ++++++++++----------- models/transcription/transcription_utils.py | 8 ++++---- window_config.py | 1 - 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/config.py b/config.py index ced57eab..2b99e4dc 100644 --- a/config.py +++ b/config.py @@ -8,7 +8,7 @@ from tkinter import font from languages import selectable_languages from models.translation.translation_languages import translatorEngine, translation_lang from models.transcription.transcription_languages import transcription_lang -from models.transcription.transcription_utils import get_input_device_list, get_output_device_list, get_default_input_device, get_default_output_device +from models.transcription.transcription_utils import getInputDevices, getOutputDevices, getDefaultInputDevice, getDefaultOutputDevice def saveJson(path, key, value): with open(path, "r") as fp: @@ -180,7 +180,7 @@ class Config: @CHOICE_MIC_HOST.setter def CHOICE_MIC_HOST(self, value): - if value in [host for host in get_input_device_list().keys()]: + if value in [host for host in getInputDevices().keys()]: self._CHOICE_MIC_HOST = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @@ -190,7 +190,7 @@ class Config: @CHOICE_MIC_DEVICE.setter def CHOICE_MIC_DEVICE(self, value): - if value in [device["name"] for device in get_input_device_list()[self.CHOICE_MIC_HOST]]: + if value in [device["name"] for device in getInputDevices()[self.CHOICE_MIC_HOST]]: self._CHOICE_MIC_DEVICE = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @@ -270,9 +270,9 @@ class Config: @CHOICE_SPEAKER_DEVICE.setter def CHOICE_SPEAKER_DEVICE(self, value): - if value in [device["name"] for device in get_output_device_list()]: - speaker_device = [device for device in get_output_device_list() if device["name"] == value][0] - if get_default_output_device()["index"] == speaker_device["index"]: + if value in [device["name"] for device in getOutputDevices()]: + speaker_device = [device for device in getOutputDevices() if device["name"] == value][0] + if getDefaultOutputDevice()["index"] == speaker_device["index"]: self._CHOICE_SPEAKER_DEVICE = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @@ -449,8 +449,8 @@ class Config: self._INPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[1] self._OUTPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[1] self._OUTPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[0] - self._CHOICE_MIC_HOST = get_default_input_device()["host"]["name"] - self._CHOICE_MIC_DEVICE = get_default_input_device()["device"]["name"] + self._CHOICE_MIC_HOST = getDefaultInputDevice()["host"]["name"] + self._CHOICE_MIC_DEVICE = getDefaultInputDevice()["device"]["name"] self._INPUT_MIC_VOICE_LANGUAGE = list(transcription_lang.keys())[0] self._INPUT_MIC_ENERGY_THRESHOLD = 300 self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = True @@ -458,7 +458,7 @@ class Config: self._INPUT_MIC_PHRASE_TIMEOUT = 3 self._INPUT_MIC_MAX_PHRASES = 10 self._INPUT_MIC_WORD_FILTER = [] - self._CHOICE_SPEAKER_DEVICE = get_default_output_device()["name"] + self._CHOICE_SPEAKER_DEVICE = getDefaultOutputDevice()["name"] self._INPUT_SPEAKER_VOICE_LANGUAGE = list(transcription_lang.keys())[1] self._INPUT_SPEAKER_ENERGY_THRESHOLD = 300 self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = True diff --git a/model.py b/model.py index 5f2409cb..37a488c1 100644 --- a/model.py +++ b/model.py @@ -5,7 +5,7 @@ from requests import get as requests_get from flashtext import KeywordProcessor from models.translation.translation_translator import Translator -from models.transcription.transcription_utils import get_input_device_list, get_output_device_list, get_default_input_device, get_default_output_device +from models.transcription.transcription_utils import getInputDevices, getOutputDevices, getDefaultInputDevice, getDefaultOutputDevice from models.osc.osc_tools import sendTyping, sendMessage, sendTestAction, receiveOscParameters from models.transcription.transcription_recorder import SelectedMicRecorder, SelectedSpeakerRecorder from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder @@ -133,31 +133,31 @@ class Model: @staticmethod def getListInputHost(): - return [host for host in get_input_device_list().keys()] + return [host for host in getInputDevices().keys()] @staticmethod def getListInputDevice(): - return [device["name"] for device in get_input_device_list()[config.CHOICE_MIC_HOST]] + return [device["name"] for device in getInputDevices()[config.CHOICE_MIC_HOST]] @staticmethod def getInputDefaultDevice(): - return [device["name"] for device in get_input_device_list()[config.CHOICE_MIC_HOST]][0] + return [device["name"] for device in getInputDevices()[config.CHOICE_MIC_HOST]][0] @staticmethod def getListOutputDevice(): - return [device["name"] for device in get_output_device_list()] + return [device["name"] for device in getOutputDevices()] @staticmethod def checkSpeakerStatus(choice=config.CHOICE_SPEAKER_DEVICE): - speaker_device = [device for device in get_output_device_list() if device["name"] == choice][0] - if get_default_output_device()["index"] == speaker_device["index"]: + speaker_device = [device for device in getOutputDevices() if device["name"] == choice][0] + if getDefaultOutputDevice()["index"] == speaker_device["index"]: return True return False def startMicTranscript(self, fnc): mic_audio_queue = Queue() self.mic_audio_recorder = SelectedMicRecorder( - [device for device in get_input_device_list()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0], + [device for device in getInputDevices()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0], config.INPUT_MIC_ENERGY_THRESHOLD, config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, config.INPUT_MIC_RECORD_TIMEOUT, @@ -192,7 +192,7 @@ class Model: fnc(energy) sleep(0.01) mic_energy_queue = Queue() - mic_device = [device for device in get_input_device_list()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] + mic_device = [device for device in getInputDevices()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] self.mic_energy_recorder = SelectedMicEnergyRecorder(mic_device) self.mic_energy_recorder.record_into_queue(mic_energy_queue) self.mic_energy_plot_progressbar = threadFnc(progressBarInputMicEnergyPlot) @@ -207,7 +207,7 @@ class Model: def startSpeakerTranscript(self, fnc): spk_audio_queue = Queue() - spk_device = [device for device in get_output_device_list() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] + spk_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] self.spk_audio_recorder = SelectedSpeakerRecorder( spk_device, config.INPUT_SPEAKER_ENERGY_THRESHOLD, @@ -249,7 +249,7 @@ class Model: energy = self.speaker_energy_recorder.recorder.listen_energy(source) speaker_energy_queue.put(energy) - speaker_device = [device for device in get_output_device_list() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] + speaker_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] speaker_energy_queue = Queue() self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(speaker_device) self.speaker_energy_get_progressbar = threadFnc(progressBar_input_speaker_energy_get) diff --git a/models/transcription/transcription_utils.py b/models/transcription/transcription_utils.py index b66172c2..4c72a8fa 100644 --- a/models/transcription/transcription_utils.py +++ b/models/transcription/transcription_utils.py @@ -1,6 +1,6 @@ from pyaudiowpatch import PyAudio, paWASAPI -def get_input_device_list(): +def getInputDevices(): devices = {} with PyAudio() as p: for host_index in range(0, p.get_host_api_count()): @@ -14,7 +14,7 @@ def get_input_device_list(): devices[host["name"]] = [device] return devices -def get_output_device_list(): +def getOutputDevices(): devices =[] with PyAudio() as p: wasapi_info = p.get_host_api_info_by_type(paWASAPI) @@ -23,7 +23,7 @@ def get_output_device_list(): devices.append(device) return devices -def get_default_input_device(): +def getDefaultInputDevice(): with PyAudio() as p: api_info = p.get_default_host_api_info() defaultInputDevice = api_info["defaultInputDevice"] @@ -35,7 +35,7 @@ def get_default_input_device(): if device["index"] == defaultInputDevice: return {"host":host, "device": device} -def get_default_output_device(): +def getDefaultOutputDevice(): with PyAudio() as p: wasapi_info = p.get_host_api_info_by_type(paWASAPI) defaultOutputDevice = wasapi_info["defaultOutputDevice"] diff --git a/window_config.py b/window_config.py index 326a7a11..b45a61c1 100644 --- a/window_config.py +++ b/window_config.py @@ -332,7 +332,6 @@ class ToplevelWindowConfig(CTkToplevel): def speaker_threshold_check_start(self): def plotProgressBar(energy): try: - print(energy) self.progressBar_input_speaker_energy_threshold.set(energy/config.MAX_MIC_ENERGY_THRESHOLD) except: pass From b7d6477228964c1919b4e67cf6d9bc58fb696089 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 20 Aug 2023 02:28:17 +0900 Subject: [PATCH 018/355] [Update] change transcription_recorder.py/transcription_transcriber.py/notification.py func name --- model.py | 40 +++++++++---------- .../transcription/transcription_recorder.py | 20 +++++----- .../transcription_transcriber.py | 20 +++++----- models/xsoverlay/notification.py | 8 ++-- 4 files changed, 44 insertions(+), 44 deletions(-) diff --git a/model.py b/model.py index 37a488c1..67f782f4 100644 --- a/model.py +++ b/model.py @@ -10,7 +10,7 @@ from models.osc.osc_tools import sendTyping, sendMessage, sendTestAction, receiv from models.transcription.transcription_recorder import SelectedMicRecorder, SelectedSpeakerRecorder from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder from models.transcription.transcription_transcriber import AudioTranscriber -from models.xsoverlay.notification import notification_xsoverlay_for_vrct +from models.xsoverlay.notification import xsoverlayForVRCT from config import config class threadFnc(Thread): @@ -162,19 +162,19 @@ class Model: config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, config.INPUT_MIC_RECORD_TIMEOUT, ) - self.mic_audio_recorder.record_into_queue(mic_audio_queue) + self.mic_audio_recorder.recordIntoQueue(mic_audio_queue) mic_transcriber = AudioTranscriber( speaker=False, source=self.mic_audio_recorder.source, phrase_timeout=config.INPUT_MIC_PHRASE_TIMEOUT, max_phrases=config.INPUT_MIC_MAX_PHRASES, ) - def mic_transcript_to_chatbox(): - mic_transcriber.transcribe_audio_queue(mic_audio_queue, config.INPUT_MIC_VOICE_LANGUAGE) - message = mic_transcriber.get_transcript() + def sendMicTranscript(): + mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.INPUT_MIC_VOICE_LANGUAGE) + message = mic_transcriber.getTranscript() fnc(message) - self.mic_print_transcript = threadFnc(mic_transcript_to_chatbox) + self.mic_print_transcript = threadFnc(sendMicTranscript) self.mic_print_transcript.daemon = True self.mic_print_transcript.start() @@ -186,7 +186,7 @@ class Model: self.mic_audio_recorder.stop = None def startCheckMicEnergy(self, fnc): - def progressBarInputMicEnergyPlot(): + def sendMicEnergy(): if mic_energy_queue.empty() is False: energy = mic_energy_queue.get() fnc(energy) @@ -194,8 +194,8 @@ class Model: mic_energy_queue = Queue() mic_device = [device for device in getInputDevices()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] self.mic_energy_recorder = SelectedMicEnergyRecorder(mic_device) - self.mic_energy_recorder.record_into_queue(mic_energy_queue) - self.mic_energy_plot_progressbar = threadFnc(progressBarInputMicEnergyPlot) + self.mic_energy_recorder.recordIntoQueue(mic_energy_queue) + self.mic_energy_plot_progressbar = threadFnc(sendMicEnergy) self.mic_energy_plot_progressbar.daemon = True self.mic_energy_plot_progressbar.start() @@ -214,19 +214,19 @@ class Model: config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, config.INPUT_SPEAKER_RECORD_TIMEOUT, ) - self.spk_audio_recorder.record_into_queue(spk_audio_queue) + self.spk_audio_recorder.recordIntoQueue(spk_audio_queue) spk_transcriber = AudioTranscriber( speaker=True, source=self.spk_audio_recorder.source, phrase_timeout=config.INPUT_SPEAKER_PHRASE_TIMEOUT, max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, ) - def spk_transcript_to_textbox(): - spk_transcriber.transcribe_audio_queue(spk_audio_queue, config.INPUT_SPEAKER_VOICE_LANGUAGE) - message = spk_transcriber.get_transcript() + def sendSpkTranscript(): + spk_transcriber.transcribeAudioQueue(spk_audio_queue, config.INPUT_SPEAKER_VOICE_LANGUAGE) + message = spk_transcriber.getTranscript() fnc(message) - self.spk_print_transcript = threadFnc(spk_transcript_to_textbox) + self.spk_print_transcript = threadFnc(sendSpkTranscript) self.spk_print_transcript.daemon = True self.spk_print_transcript.start() @@ -238,13 +238,13 @@ class Model: self.spk_audio_recorder.stop = None def startCheckSpeakerEnergy(self, fnc): - def progressBar_input_speaker_energy_plot(): + def sendSpeakerEnergy(): if speaker_energy_queue.empty() is False: energy = speaker_energy_queue.get() fnc(energy) sleep(0.01) - def progressBar_input_speaker_energy_get(): + def getSpeakerEnergy(): with self.speaker_energy_recorder.source as source: energy = self.speaker_energy_recorder.recorder.listen_energy(source) speaker_energy_queue.put(energy) @@ -252,10 +252,10 @@ class Model: speaker_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] speaker_energy_queue = Queue() self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(speaker_device) - self.speaker_energy_get_progressbar = threadFnc(progressBar_input_speaker_energy_get) + self.speaker_energy_get_progressbar = threadFnc(getSpeakerEnergy) self.speaker_energy_get_progressbar.daemon = True self.speaker_energy_get_progressbar.start() - self.speaker_energy_plot_progressbar = threadFnc(progressBar_input_speaker_energy_plot) + self.speaker_energy_plot_progressbar = threadFnc(sendSpeakerEnergy) self.speaker_energy_plot_progressbar.daemon = True self.speaker_energy_plot_progressbar.start() @@ -265,7 +265,7 @@ class Model: if self.speaker_energy_plot_progressbar != None: self.speaker_energy_plot_progressbar.stop() - def notificationXsoverlay(self, message): - notification_xsoverlay_for_vrct(content=f"{message}") + def notificationXSOverlay(self, message): + xsoverlayForVRCT(content=f"{message}") model = Model() diff --git a/models/transcription/transcription_recorder.py b/models/transcription/transcription_recorder.py index 5d968062..cf2cec04 100644 --- a/models/transcription/transcription_recorder.py +++ b/models/transcription/transcription_recorder.py @@ -15,11 +15,11 @@ class BaseRecorder: self.source = source - def adjust_for_noise(self): + def adjustForNoise(self): with self.source: self.recorder.adjust_for_ambient_noise(self.source) - def record_into_queue(self, audio_queue): + def recordIntoQueue(self, audio_queue): def record_callback(_, audio): audio_queue.put((audio.get_raw_data(), datetime.now())) @@ -32,7 +32,7 @@ class SelectedMicRecorder(BaseRecorder): sample_rate=int(device["defaultSampleRate"]), ) super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout) - # self.adjust_for_noise() + # self.adjustForNoise() class SelectedSpeakerRecorder(BaseRecorder): def __init__(self, device, energy_threshold, dynamic_energy_threshold, record_timeout): @@ -44,7 +44,7 @@ class SelectedSpeakerRecorder(BaseRecorder): channels=device["maxInputChannels"] ) super().__init__(source=source, energy_threshold=energy_threshold, dynamic_energy_threshold=dynamic_energy_threshold, record_timeout=record_timeout) - # self.adjust_for_noise() + # self.adjustForNoise() class BaseEnergyRecorder: def __init__(self, source): @@ -59,15 +59,15 @@ class BaseEnergyRecorder: self.source = source - def adjust_for_noise(self): + def adjustForNoise(self): with self.source: self.recorder.adjust_for_ambient_noise(self.source) - def record_into_queue(self, energy_queue): - def record_callback(_, energy): + def recordIntoQueue(self, energy_queue): + def recordCallback(_, energy): energy_queue.put(energy) - self.stop = self.recorder.listen_energy_in_background(self.source, record_callback) + self.stop = self.recorder.listen_energy_in_background(self.source, recordCallback) class SelectedMicEnergyRecorder(BaseEnergyRecorder): def __init__(self, device): @@ -76,7 +76,7 @@ class SelectedMicEnergyRecorder(BaseEnergyRecorder): sample_rate=int(device["defaultSampleRate"]), ) super().__init__(source=source) - # self.adjust_for_noise() + # self.adjustForNoise() class SelectedSpeakeEnergyRecorder(BaseEnergyRecorder): def __init__(self, device): @@ -88,4 +88,4 @@ class SelectedSpeakeEnergyRecorder(BaseEnergyRecorder): channels=device["maxInputChannels"] ) super().__init__(source=source) - # self.adjust_for_noise() \ No newline at end of file + # self.adjustForNoise() \ No newline at end of file diff --git a/models/transcription/transcription_transcriber.py b/models/transcription/transcription_transcriber.py index 316dd22b..e0b0eb6f 100644 --- a/models/transcription/transcription_transcriber.py +++ b/models/transcription/transcription_transcriber.py @@ -24,13 +24,13 @@ class AudioTranscriber: "last_sample": bytes(), "last_spoken": None, "new_phrase": True, - "process_data_func": self.process_speaker_data if speaker else self.process_speaker_data + "process_data_func": self.processSpeakerData if speaker else self.processSpeakerData } - def transcribe_audio_queue(self, audio_queue, language): + def transcribeAudioQueue(self, audio_queue, language): # while True: audio, time_spoken = audio_queue.get() - self.update_last_sample_and_phrase_status(audio, time_spoken) + self.updateLastSampleAndPhraseStatus(audio, time_spoken) text = '' try: @@ -45,9 +45,9 @@ class AudioTranscriber: # os.unlink(path) if text != '': - self.update_transcript(text) + self.updateTranscript(text) - def update_last_sample_and_phrase_status(self, data, time_spoken): + def updateLastSampleAndPhraseStatus(self, data, time_spoken): source_info = self.audio_sources if source_info["last_spoken"] and time_spoken - source_info["last_spoken"] > timedelta(seconds=self.phrase_timeout): source_info["last_sample"] = bytes() @@ -58,11 +58,11 @@ class AudioTranscriber: source_info["last_sample"] += data source_info["last_spoken"] = time_spoken - def process_mic_data(self): + def processMicData(self): audio_data = AudioData(self.audio_sources["last_sample"], self.audio_sources["sample_rate"], self.audio_sources["sample_width"]) return audio_data - def process_speaker_data(self): + def processSpeakerData(self): temp_file = BytesIO() with wave.open(temp_file, 'wb') as wf: wf.setnchannels(self.audio_sources["channels"]) @@ -74,7 +74,7 @@ class AudioTranscriber: audio = self.audio_recognizer.record(source) return audio - def update_transcript(self, text): + def updateTranscript(self, text): source_info = self.audio_sources transcript = self.transcript_data @@ -85,14 +85,14 @@ class AudioTranscriber: else: transcript[0] = text - def get_transcript(self): + def getTranscript(self): if len(self.transcript_data) > 0: text = self.transcript_data.pop(-1) else: text = "" return text - def clear_transcript_data(self): + def clearTranscriptData(self): self.transcript_data.clear() self.audio_sources["last_sample"] = bytes() self.audio_sources["new_phrase"] = True \ No newline at end of file diff --git a/models/xsoverlay/notification.py b/models/xsoverlay/notification.py index 61e18725..f18adf60 100644 --- a/models/xsoverlay/notification.py +++ b/models/xsoverlay/notification.py @@ -19,7 +19,7 @@ import socket import json import base64 -def notification_xsoverlay( +def XSOverlay( endpoint:tuple=("127.0.0.1", 42069), messageType:int=1, index:int=0, timeout:float=2, height:float=120.0, opacity:float=1.0, volume:float=0.0, audioPath:str="", title:str="", content:str="", useBase64Icon:bool=False, icon:str="default", sourceApp:str="" @@ -58,8 +58,8 @@ def notification_xsoverlay( sock.close() return response -def notification_xsoverlay_for_vrct(content:str="") -> int: - response = notification_xsoverlay( +def xsoverlayForVRCT(content:str="") -> int: + response = XSOverlay( title="VRCT", content=content, useBase64Icon=True, @@ -69,4 +69,4 @@ def notification_xsoverlay_for_vrct(content:str="") -> int: return response if __name__ == "__main__": - notification_xsoverlay_for_vrct(content="notification test") \ No newline at end of file + xsoverlayForVRCT(content="notification test") \ No newline at end of file From b2021aeaab4c9658a6723369b8d32f2769848862 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 20 Aug 2023 09:00:56 +0900 Subject: [PATCH 019/355] =?UTF-8?q?[bugfix]=20=E3=82=BD=E3=83=95=E3=83=88?= =?UTF-8?q?=E3=82=A6=E3=82=A7=E3=82=A2=E3=81=AE=E6=9B=B4=E6=96=B0=E3=83=81?= =?UTF-8?q?=E3=82=A7=E3=83=83=E3=82=AF=E5=87=A6=E7=90=86=E3=81=8C=E5=88=86?= =?UTF-8?q?=E9=9B=A2=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=8B?= =?UTF-8?q?=E3=81=A3=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- VRCT.py | 5 ++++- model.py | 8 +++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/VRCT.py b/VRCT.py index 565c4bdd..ff7e41c8 100644 --- a/VRCT.py +++ b/VRCT.py @@ -62,7 +62,10 @@ class App(CTk): model.addKeywords() # check OSC started - model.oscCheck() + model.checkOSCStarted() + + # check Software Updated + model.checkSoftwareUpdated() def button_config_callback(self): self.foreground_stop() diff --git a/model.py b/model.py index 67f782f4..b61ee831 100644 --- a/model.py +++ b/model.py @@ -112,19 +112,21 @@ class Model: sendMessage(message, config.OSC_IP_ADDRESS, config.OSC_PORT) @staticmethod - def oscCheck(): - def check_osc_receive(address, osc_arguments): + def checkOSCStarted(): + def checkOscReceive(address, osc_arguments): if config.ENABLE_OSC is False: config.ENABLE_OSC = True # start receive osc - th_receive_osc_parameters = Thread(target=receiveOscParameters, args=(check_osc_receive,)) + th_receive_osc_parameters = Thread(target=receiveOscParameters, args=(checkOscReceive,)) th_receive_osc_parameters.daemon = True th_receive_osc_parameters.start() # check osc started sendTestAction() + @staticmethod + def checkSoftwareUpdated(): # check update response = requests_get(config.GITHUB_URL) tag_name = response.json()["tag_name"] From cd4e431be2de48af15ba4bacd1382c35e51902f9 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 27 Aug 2023 04:54:25 +0900 Subject: [PATCH 020/355] [Add] model test code MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 既存のVRCT 1.3.2のUIをmodelのテストコードとして移動 --- test_model/config.py | 511 ++++++++++ test_model/img/app.ico | Bin 0 -> 67646 bytes test_model/img/config-icon-white.png | Bin 0 -> 5490 bytes test_model/img/info-icon-white.png | Bin 0 -> 4945 bytes test_model/img/xsoverlay.png | Bin 0 -> 1209 bytes test_model/languages.py | 6 + test_model/locales.yml | 193 ++++ test_model/main.py | 503 ++++++++++ test_model/utils.py | 106 ++ test_model/window_config.py | 1348 ++++++++++++++++++++++++++ test_model/window_information.py | 161 +++ 11 files changed, 2828 insertions(+) create mode 100644 test_model/config.py create mode 100644 test_model/img/app.ico create mode 100644 test_model/img/config-icon-white.png create mode 100644 test_model/img/info-icon-white.png create mode 100644 test_model/img/xsoverlay.png create mode 100644 test_model/languages.py create mode 100644 test_model/locales.yml create mode 100644 test_model/main.py create mode 100644 test_model/utils.py create mode 100644 test_model/window_config.py create mode 100644 test_model/window_information.py diff --git a/test_model/config.py b/test_model/config.py new file mode 100644 index 00000000..cea811a5 --- /dev/null +++ b/test_model/config.py @@ -0,0 +1,511 @@ +import os +import sys +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) + +from json import load, dump +import inspect +from os import path as os_path +from json import load as json_load +from json import dump as json_dump +import tkinter as tk +from tkinter import font +from languages import selectable_languages +from models.translation.translation_languages import translatorEngine, translation_lang +from models.transcription.transcription_languages import transcription_lang +from models.transcription.transcription_utils import getInputDevices, getOutputDevices, getDefaultInputDevice, getDefaultOutputDevice + +def saveJson(path, key, value): + with open(path, "r") as fp: + json_data = load(fp) + json_data[key] = value + with open(path, "w") as fp: + dump(json_data, fp, indent=4) + +class Config: + _instance = None + + def __new__(cls): + if cls._instance is None: + cls._instance = super(Config, cls).__new__(cls) + cls._instance.init_config() + cls._instance.load_config() + return cls._instance + + @property + def VERSION(self): + return self._VERSION + + @property + def PATH_CONFIG(self): + return self._PATH_CONFIG + + @property + def ENABLE_TRANSLATION(self): + return self._ENABLE_TRANSLATION + + @ENABLE_TRANSLATION.setter + def ENABLE_TRANSLATION(self, value): + if type(value) is bool: + self._ENABLE_TRANSLATION = value + + @property + def ENABLE_TRANSCRIPTION_SEND(self): + return self._ENABLE_TRANSCRIPTION_SEND + + @ENABLE_TRANSCRIPTION_SEND.setter + def ENABLE_TRANSCRIPTION_SEND(self, value): + if type(value) is bool: + self._ENABLE_TRANSCRIPTION_SEND = value + + @property + def ENABLE_TRANSCRIPTION_RECEIVE(self): + return self._ENABLE_TRANSCRIPTION_RECEIVE + + @ENABLE_TRANSCRIPTION_RECEIVE.setter + def ENABLE_TRANSCRIPTION_RECEIVE(self, value): + if type(value) is bool: + self._ENABLE_TRANSCRIPTION_RECEIVE = value + + @property + def ENABLE_FOREGROUND(self): + return self._ENABLE_FOREGROUND + + @ENABLE_FOREGROUND.setter + def ENABLE_FOREGROUND(self, value): + if type(value) is bool: + self._ENABLE_FOREGROUND = value + + @property + def TRANSPARENCY(self): + return self._TRANSPARENCY + + @TRANSPARENCY.setter + def TRANSPARENCY(self, value): + if type(value) is int and 0 <= value <= 100: + self._TRANSPARENCY = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def APPEARANCE_THEME(self): + return self._APPEARANCE_THEME + + @APPEARANCE_THEME.setter + def APPEARANCE_THEME(self, value): + if value in ["Light", "Dark", "System"]: + self._APPEARANCE_THEME = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def UI_SCALING(self): + return self._UI_SCALING + + @UI_SCALING.setter + def UI_SCALING(self, value): + if value in ["80%", "90%", "100%", "110%", "120%"]: + self._UI_SCALING = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def FONT_FAMILY(self): + return self._FONT_FAMILY + + @FONT_FAMILY.setter + def FONT_FAMILY(self, value): + root = tk.Tk() + root.withdraw() + if value in list(font.families()): + self._FONT_FAMILY = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + root.destroy() + + @property + def UI_LANGUAGE(self): + return self._UI_LANGUAGE + + @UI_LANGUAGE.setter + def UI_LANGUAGE(self, value): + if value in list(selectable_languages.keys()): + self._UI_LANGUAGE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def CHOICE_TRANSLATOR(self): + return self._CHOICE_TRANSLATOR + + @CHOICE_TRANSLATOR.setter + def CHOICE_TRANSLATOR(self, value): + if value in translatorEngine: + self._CHOICE_TRANSLATOR = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SOURCE_LANG(self): + return self._INPUT_SOURCE_LANG + + @INPUT_SOURCE_LANG.setter + def INPUT_SOURCE_LANG(self, value): + if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): + self._INPUT_SOURCE_LANG = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_TARGET_LANG(self): + return self._INPUT_TARGET_LANG + + @INPUT_TARGET_LANG.setter + def INPUT_TARGET_LANG(self, value): + if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): + self._INPUT_TARGET_LANG = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def OUTPUT_SOURCE_LANG(self): + return self._OUTPUT_SOURCE_LANG + + @OUTPUT_SOURCE_LANG.setter + def OUTPUT_SOURCE_LANG(self, value): + if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): + self._OUTPUT_SOURCE_LANG = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def OUTPUT_TARGET_LANG(self): + return self._OUTPUT_TARGET_LANG + + @OUTPUT_TARGET_LANG.setter + def OUTPUT_TARGET_LANG(self, value): + if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): + self._OUTPUT_TARGET_LANG = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def CHOICE_MIC_HOST(self): + return self._CHOICE_MIC_HOST + + @CHOICE_MIC_HOST.setter + def CHOICE_MIC_HOST(self, value): + if value in [host for host in getInputDevices().keys()]: + self._CHOICE_MIC_HOST = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def CHOICE_MIC_DEVICE(self): + return self._CHOICE_MIC_DEVICE + + @CHOICE_MIC_DEVICE.setter + def CHOICE_MIC_DEVICE(self, value): + if value in [device["name"] for device in getInputDevices()[self.CHOICE_MIC_HOST]]: + self._CHOICE_MIC_DEVICE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_VOICE_LANGUAGE(self): + return self._INPUT_MIC_VOICE_LANGUAGE + + @INPUT_MIC_VOICE_LANGUAGE.setter + def INPUT_MIC_VOICE_LANGUAGE(self, value): + if value in list(transcription_lang.keys()): + self._INPUT_MIC_VOICE_LANGUAGE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_ENERGY_THRESHOLD(self): + return self._INPUT_MIC_ENERGY_THRESHOLD + + @INPUT_MIC_ENERGY_THRESHOLD.setter + def INPUT_MIC_ENERGY_THRESHOLD(self, value): + if type(value) is int: + self._INPUT_MIC_ENERGY_THRESHOLD = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self): + return self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD + + @INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD.setter + def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self, value): + if type(value) is bool: + self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_RECORD_TIMEOUT(self): + return self._INPUT_MIC_RECORD_TIMEOUT + + @INPUT_MIC_RECORD_TIMEOUT.setter + def INPUT_MIC_RECORD_TIMEOUT(self, value): + if type(value) is int: + self._INPUT_MIC_RECORD_TIMEOUT = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_PHRASE_TIMEOUT(self): + return self._INPUT_MIC_PHRASE_TIMEOUT + + @INPUT_MIC_PHRASE_TIMEOUT.setter + def INPUT_MIC_PHRASE_TIMEOUT(self, value): + if type(value) is int: + self._INPUT_MIC_PHRASE_TIMEOUT = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_MAX_PHRASES(self): + return self._INPUT_MIC_MAX_PHRASES + + @INPUT_MIC_MAX_PHRASES.setter + def INPUT_MIC_MAX_PHRASES(self, value): + if type(value) is int: + self._INPUT_MIC_MAX_PHRASES = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_MIC_WORD_FILTER(self): + return self._INPUT_MIC_WORD_FILTER + + @INPUT_MIC_WORD_FILTER.setter + def INPUT_MIC_WORD_FILTER(self, value): + if type(value) is list: + self._INPUT_MIC_WORD_FILTER = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def CHOICE_SPEAKER_DEVICE(self): + return self._CHOICE_SPEAKER_DEVICE + + @CHOICE_SPEAKER_DEVICE.setter + def CHOICE_SPEAKER_DEVICE(self, value): + if value in [device["name"] for device in getOutputDevices()]: + speaker_device = [device for device in getOutputDevices() if device["name"] == value][0] + if getDefaultOutputDevice()["index"] == speaker_device["index"]: + self._CHOICE_SPEAKER_DEVICE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SPEAKER_VOICE_LANGUAGE(self): + return self._INPUT_SPEAKER_VOICE_LANGUAGE + + @INPUT_SPEAKER_VOICE_LANGUAGE.setter + def INPUT_SPEAKER_VOICE_LANGUAGE(self, value): + if value in list(transcription_lang.keys()): + self._INPUT_SPEAKER_VOICE_LANGUAGE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SPEAKER_ENERGY_THRESHOLD(self): + return self._INPUT_SPEAKER_ENERGY_THRESHOLD + + @INPUT_SPEAKER_ENERGY_THRESHOLD.setter + def INPUT_SPEAKER_ENERGY_THRESHOLD(self, value): + if type(value) is int: + self._INPUT_SPEAKER_ENERGY_THRESHOLD = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self): + return self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD + + @INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD.setter + def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self, value): + if type(value) is bool: + self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SPEAKER_RECORD_TIMEOUT(self): + return self._INPUT_SPEAKER_RECORD_TIMEOUT + + @INPUT_SPEAKER_RECORD_TIMEOUT.setter + def INPUT_SPEAKER_RECORD_TIMEOUT(self, value): + if type(value) is int: + self._INPUT_SPEAKER_RECORD_TIMEOUT = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SPEAKER_PHRASE_TIMEOUT(self): + return self._INPUT_SPEAKER_PHRASE_TIMEOUT + + @INPUT_SPEAKER_PHRASE_TIMEOUT.setter + def INPUT_SPEAKER_PHRASE_TIMEOUT(self, value): + if type(value) is int: + self._INPUT_SPEAKER_PHRASE_TIMEOUT = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def INPUT_SPEAKER_MAX_PHRASES(self): + return self._INPUT_SPEAKER_MAX_PHRASES + + @INPUT_SPEAKER_MAX_PHRASES.setter + def INPUT_SPEAKER_MAX_PHRASES(self, value): + if type(value) is int: + self._INPUT_SPEAKER_MAX_PHRASES = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def OSC_IP_ADDRESS(self): + return self._OSC_IP_ADDRESS + + @OSC_IP_ADDRESS.setter + def OSC_IP_ADDRESS(self, value): + if type(value) is str: + self._OSC_IP_ADDRESS = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def OSC_PORT(self): + return self._OSC_PORT + + @OSC_PORT.setter + def OSC_PORT(self, value): + if type(value) is int: + self._OSC_PORT = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def AUTH_KEYS(self): + return self._AUTH_KEYS + + @AUTH_KEYS.setter + def AUTH_KEYS(self, value): + if type(value) is dict and set(value.keys()) == set(self.AUTH_KEYS.keys()): + for key, value in value.items(): + if type(value) is str: + self._AUTH_KEYS[key] = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.AUTH_KEYS) + + @property + def MESSAGE_FORMAT(self): + return self._MESSAGE_FORMAT + + @MESSAGE_FORMAT.setter + def MESSAGE_FORMAT(self, value): + if type(value) is str: + self._MESSAGE_FORMAT = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def ENABLE_AUTO_CLEAR_CHATBOX(self): + return self._ENABLE_AUTO_CLEAR_CHATBOX + + @ENABLE_AUTO_CLEAR_CHATBOX.setter + def ENABLE_AUTO_CLEAR_CHATBOX(self, value): + if type(value) is bool: + self._ENABLE_AUTO_CLEAR_CHATBOX = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def ENABLE_NOTICE_XSOVERLAY(self): + return self._ENABLE_NOTICE_XSOVERLAY + + @ENABLE_NOTICE_XSOVERLAY.setter + def ENABLE_NOTICE_XSOVERLAY(self, value): + if type(value) is bool: + self._ENABLE_NOTICE_XSOVERLAY = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def ENABLE_OSC(self): + return self._ENABLE_OSC + + @ENABLE_OSC.setter + def ENABLE_OSC(self, value): + if type(value) is bool: + self._ENABLE_OSC = value + + @property + def UPDATE_FLAG(self): + return self._UPDATE_FLAG + + @UPDATE_FLAG.setter + def UPDATE_FLAG(self, value): + if type(value) is bool: + self._UPDATE_FLAG = value + + @property + def GITHUB_URL(self): + return self._GITHUB_URL + + @property + def BREAK_KEYSYM_LIST(self): + return self._BREAK_KEYSYM_LIST + + @property + def MAX_MIC_ENERGY_THRESHOLD(self): + return self._MAX_MIC_ENERGY_THRESHOLD + + @property + def MAX_SPEAKER_ENERGY_THRESHOLD(self): + return self._MAX_SPEAKER_ENERGY_THRESHOLD + + def init_config(self): + self._VERSION = "1.3.2" + self._PATH_CONFIG = os.path.join(os.path.dirname(__file__), 'config.json') + self._ENABLE_TRANSLATION = False + self._ENABLE_TRANSCRIPTION_SEND = False + self._ENABLE_TRANSCRIPTION_RECEIVE = False + self._ENABLE_FOREGROUND = False + self._TRANSPARENCY = 100 + self._APPEARANCE_THEME = "System" + self._UI_SCALING = "100%" + self._FONT_FAMILY = "Yu Gothic UI" + self._UI_LANGUAGE = "en" + self._CHOICE_TRANSLATOR = translatorEngine[0] + self._INPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[0] + self._INPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[1] + self._OUTPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[1] + self._OUTPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[0] + self._CHOICE_MIC_HOST = getDefaultInputDevice()["host"]["name"] + self._CHOICE_MIC_DEVICE = getDefaultInputDevice()["device"]["name"] + self._INPUT_MIC_VOICE_LANGUAGE = list(transcription_lang.keys())[0] + self._INPUT_MIC_ENERGY_THRESHOLD = 300 + self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = True + self._INPUT_MIC_RECORD_TIMEOUT = 3 + self._INPUT_MIC_PHRASE_TIMEOUT = 3 + self._INPUT_MIC_MAX_PHRASES = 10 + self._INPUT_MIC_WORD_FILTER = [] + self._CHOICE_SPEAKER_DEVICE = getDefaultOutputDevice()["name"] + self._INPUT_SPEAKER_VOICE_LANGUAGE = list(transcription_lang.keys())[1] + self._INPUT_SPEAKER_ENERGY_THRESHOLD = 300 + self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = True + self._INPUT_SPEAKER_RECORD_TIMEOUT = 3 + self._INPUT_SPEAKER_PHRASE_TIMEOUT = 3 + self._INPUT_SPEAKER_MAX_PHRASES = 10 + self._OSC_IP_ADDRESS = "127.0.0.1" + self._OSC_PORT = 9000 + self._AUTH_KEYS = { + "DeepL(web)": None, + "DeepL(auth)": None, + "Bing(web)": None, + "Google(web)": None, + } + self._MESSAGE_FORMAT = "[message]([translation])" + self._ENABLE_AUTO_CLEAR_CHATBOX = False + self._ENABLE_NOTICE_XSOVERLAY = False + self._ENABLE_OSC = False + self._UPDATE_FLAG = False + self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" + self._BREAK_KEYSYM_LIST = [ + "Delete", "Select", "Up", "Down", "Next", "End", "Print", + "Prior","Insert","Home", "Left", "Clear", "Right", "Linefeed" + ] + self._MAX_MIC_ENERGY_THRESHOLD = 2000 + self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 + + def load_config(self): + if os_path.isfile(self.PATH_CONFIG) is not False: + with open(self.PATH_CONFIG, 'r') as fp: + config = json_load(fp) + + for key in config.keys(): + setattr(self, key, config[key]) + + with open(self.PATH_CONFIG, 'w') as fp: + setter_methods = [ + name for name, obj in vars(type(self)).items() + if isinstance(obj, property) and obj.fset is not None + ] + config = {} + for method in setter_methods: + config[method] = getattr(self, method) + json_dump(config, fp, indent=4) + +config = Config() \ No newline at end of file diff --git a/test_model/img/app.ico b/test_model/img/app.ico new file mode 100644 index 0000000000000000000000000000000000000000..eca32ce73e86bb5d9cf41456997c2c74c77c7c1a GIT binary patch literal 67646 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 literal 0 HcmV?d00001 diff --git a/test_model/img/info-icon-white.png b/test_model/img/info-icon-white.png new file mode 100644 index 0000000000000000000000000000000000000000..210613e03a30370a1821eb81a53c181084186de5 GIT binary patch literal 4945 zcmeHKX;2f{7ES6`%#eBb7kXf@w1?@lw>_RQM1%R;*IXl?L7`_mCk{%ymA0^WFhe12R# zwXyVv5t++Y`S7gW{2(>D za|{!I2)6mYot+wEw6T6pg!w+SYv~^ivGtkF6bdyH4+sbs2LybK2Sib{I7b+Aa`MR9 zd9$);-}U!h>8aSoXcCD=h7Qe&*-|<5`vmwPNy6uFtVuJ0iLad~P#rk>9(8Kc_4W92<)G4wZ zn?jh%X8zIE+jpMK&5TVhey{8BO5Q;A&*7aTPtuIgrr#>b`uC?s&(3vf(Y1&(7$Kc! z6K>q&){9p3+cX?Oeo1a|*cbIQ=1@j-UHGcll_yu1&OEw4A^LVrZFJe?s3)5`mmRO! zpL<_$(ec#2Osian(c6|IY;ObX$_b&8I@z{dt<%kR4IeeLIU3x4fa_FOfDvd4*fa z91|sCYI00p2!*`YUX!4WH;1E;GqHF~1yprlSJr1+28$)(y%r`4VsVwmYz4^vOq0Zw zpUC>mHW!gvOvdKOQ4Zh7n=kJJC06T5R4vC$Pyn2P0~|IV<^Tj49YYW{ovq-> z=rWAUqH_^0hs~D3as?Od12IF3gRDg3`+8-9QkbBW2#d=BP_Tl}r?WW<9-YtgQP8<^ zmYgd`5tOTxnV}SNK@g!;q2M@i6&i~%HR@Qiz(lxUO1M}^Lm2RNwnZ&H+$H2N~N-83NOoJ;5?f9phuPe#}4d z;b&Ip(_(;K_Znzjpc68CTVW4hrl#{ZK0W8~H|_wSzi#qH{C=hDD_vj2z!xch&91L> zeGvm+r2I9z{%>^Ie!5IyYVZ$853Wi?(6(T3&9at-1_zoiT$Hx~hnIknjV2^oN1@ma zF}+kuVUY`1>`#g%qW%y1xec1=l&KrCkV3KO6bDX`&Nn{$sUcK4)9K8y$jVcrDzj|q zW6zAbJbPF^J!`%5_6d=%n3*m?qo3t!3s&!5s)=mcRoU>btt8c;~pPkW&5f57U~~cN}-A zIMlY4Qe@$gW`FLEw`SPGTZ5PDDbdgj&E53m&BAuNl~i0k`cZy~WqI*|v1QZVc|0oc zPK=7EC{c^6%HR6tszY75r|)i9nIimsPe@>~MP%ALq~;I9RL8E`h?<$MAI@!mW8XL{ zeUGp8FM3aE(x zgRnWz52Ww+bUR!9Dz$2``=Yq%?{0-naBkS($@KDVc>N~3X2kGOsYkAM&Ye`SvG}C+ zN;UI8r+>V*?+WL1&H75k4Iigk+w1p^-=m~%n9`7a@*2@zwt~}jaNN^*jU(OeI?VC2 zH{@?W;FWE;IDhj4o2yah^Nm^C@8_TQpgV8ez4@Q6q<`!hmQ_qc@^y#n=|Ndtj_~@7 zT=$`!IqVT>uMdkx*=|^Ng6&u4ywRp`eP`Ij2L>X`zc6O^!V4#t8wRCTJ=|gCwl`t1 z%^ky{tX6B+Z_Z9$lUwZ-#PFT~# z6`bZljgEBzlLywb3;z_{9Ba( literal 0 HcmV?d00001 diff --git a/test_model/img/xsoverlay.png b/test_model/img/xsoverlay.png new file mode 100644 index 0000000000000000000000000000000000000000..35c793eaed87c7b132f6b5400b732341c3e74660 GIT binary patch literal 1209 zcmV;q1V;ObP)EX>4Tx04UFukvmAkP!xv$rWPMm9PFUt5TQERMMWG-6pLV?v=v%)FuCaqnlvOS zE{=k0!NJF3)xpJCR|i)?5PX2Rx;QDiNQvhrg%+WL7Y_I1zxV$+_gp}zmzidDMFCB> z&16gxGuc(K`xOBUQ;nflW|k38EvC_Neci*y_qzzs!v9vknzI<-6N#hDFm2)u;@M5x z;Ji;9VntacJ|~_usX^jLt}7nDaW1$l@XU~zPRr|tNVF__0AVNVEC6r+!Lc30ig(RIz9sDD%KS3^qTqQ7a zET94nvg-%`gWt2Y^3zjZQcwZ{FOKsu4s`DVjhf?pA3ILt1n@rtS9;4|sRMJLq}N(n z_y`!-1}?5!nz9F6?f^qihHT2NMD1tTjqx>_{=000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0007zNklKpZw0w%6M~B1PFzS{!IeUb z+V%4@9U}oh&A@9A_IkbZXf*oE zXf%?!H7SZ>jK||Ayq2Zq9vzd-W>@@Wt!%?er9#_8FHRR= z$IMom%_jM><^!qSZcmu4bUK|eV&al7*X#9jW?#JxGnov_PznZ#^mKA(#|pO4dg z96Op!Ci?lyO90lFVo8$3YPHHW8Vzo-Sja4_3%*pV)kd_oSfT|B>!KKWbPQdKg;i~o zz&3Sw7cA?Lz}^#}g7OMHbGclTSS)r&jcy>P(チャットボックス" + checkbox_transcription_receive: "スピーカー->ログ" + checkbox_foreground: "最前面表示" + + # main tabview + main_tab_title_log: "ログ" + main_tab_title_send: "送信" + main_tab_title_receive: "受信" + main_tab_title_system: "システム" + + + # configure window + # config tabview + config_tab_title_ui: "UI" + config_tab_title_translation: "翻訳方法" + config_tab_title_transcription: "音声認識" + config_tab_title_parameter: "パラメーター" + config_tab_title_others: "その他" + + # tab UI + label_transparency: "透過度" + label_appearance_theme: "外観テーマを選択" + label_ui_scaling: "UIの拡大縮小" + label_font_family: "使用フォントの変更" + label_ui_language: "UI 言語" + + # tab Translation + label_translation_translator: "翻訳エンジンの選択" + label_translation_input_language: "送信言語-->翻訳言語" + label_translation_output_language: "受信言語-->翻訳言語" + + # tab Transcription + label_input_mic_host: "マイク入力ホスト" + label_input_mic_device: "マイク入力デバイス" + label_input_mic_voice_language: "マイクで話す言語" + label_input_mic_energy_threshold: "音声取得のしきい値" + checkbox_input_mic_threshold_check: "音声取得のしきい値の視覚化" + label_input_mic_dynamic_energy_threshold: "音声取得のしきい値の自動調整" + label_input_mic_record_timeout: "マイク音声の区切りの無音時間" + label_input_mic_phrase_timeout: "文字起こしする音声時間の上限" + label_input_mic_max_phrases: "保留する単語の上限(マイク)" + label_input_mic_word_filter: "ワードフィルタ" + + label_input_speaker_device: "スピーカー(聞き取りたいデバイス)" + label_input_speaker_voice_language: "聞き取る音声の言語" + label_input_speaker_energy_threshold: "音声取得のしきい値" + checkbox_input_speaker_threshold_check: "音声取得のしきい値の視覚化" + label_input_speaker_dynamic_energy_threshold: "音声取得のしきい値の自動調整" + label_input_speaker_record_timeout: "スピーカー音声の区切りの無音時間" + label_input_speaker_phrase_timeout: "文字起こしする音声時間の上限" + label_input_speaker_max_phrases: "保留する単語の上限(スピーカー)" + + # tab Parameter + # label_ip_address: "" + # label_port: "" + # label_authkey: "" + label_message_format: "送信するメッセージのフォーマット" + + # tab Others + label_checkbox_auto_clear_chatbox: "送信後はチャットボックスを空にする" + label_checkbox_notice_xsoverlay: "XSOverlayの通知機能を有効" + + +ko: + # main window + checkbox_translation: "번역" + checkbox_transcription_send: "마이크 -> 챗박스" + checkbox_transcription_receive: "스피커 -> 로그" + checkbox_foreground: "항상 위로" + + # main tabview + main_tab_title_log: "로그" + main_tab_title_send: "전송" + main_tab_title_receive: "수신" + main_tab_title_system: "시스템" + + + # configure window + # config tabview + config_tab_title_ui: "UI" + config_tab_title_translation: "번역" + config_tab_title_transcription: "음성인식" + config_tab_title_parameter: "파라미터" + config_tab_title_others: "기타" + # tab UI + label_transparency: "투명도" + label_appearance_theme: "테마" + label_ui_scaling: "UI 크기" + label_font_family: "폰트" + label_ui_language: "UI 언어" + + # tab Translation + label_translation_translator: "번역기 선택" + label_translation_input_language: "전송시 번역 언어" + label_translation_output_language: "수신시 번역 언어" + + # tab Transcription + label_input_mic_host: "마이크 호스트" + label_input_mic_device: "마이크 장치" + label_input_mic_voice_language: "입력 언어" + label_input_mic_energy_threshold: "음성 입력 최소 볼륨" + checkbox_input_mic_threshold_check: "임계점 확인" + label_input_mic_dynamic_energy_threshold: "동적 임계값" + label_input_mic_record_timeout: "최대 무음 시간" + label_input_mic_phrase_timeout: "최대 인식 시간" + label_input_mic_max_phrases: "최대 입력 절(phrases) 수" + label_input_mic_word_filter: "단어 필터" + + label_input_speaker_device: "스피커 장치" + label_input_speaker_voice_language: "입력 언어" + label_input_speaker_energy_threshold: "음성 입력 최소 볼륨" + checkbox_input_speaker_threshold_check: "임계점 확인" + label_input_speaker_dynamic_energy_threshold: "동적 임계값" + label_input_speaker_record_timeout: "최대 무음 시간" + label_input_speaker_phrase_timeout: "최대 인식 시간" + label_input_speaker_max_phrases: "최대 입력 절(phrases) 수" + + # tab Parameter + label_ip_address: "OSC IP 주소" + label_port: "OSC 포트" + label_authkey: "DeepL 인증키" + label_message_format: "전송 형식" + + # tab Others + label_checkbox_auto_clear_chatbox: "챗박스 자동 삭제" \ No newline at end of file diff --git a/test_model/main.py b/test_model/main.py new file mode 100644 index 00000000..dd4ca55b --- /dev/null +++ b/test_model/main.py @@ -0,0 +1,503 @@ +import os +import sys +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) + +from time import sleep +from os import path as os_path + +import customtkinter +from customtkinter import CTk, CTkFrame, CTkCheckBox, CTkFont, CTkButton, CTkImage, CTkTabview, CTkTextbox, CTkEntry +from PIL.Image import open as Image_open + +from threading import Thread +from utils import print_textbox, get_localized_text, widget_main_window_label_setter +from window_config import ToplevelWindowConfig +from window_information import ToplevelWindowInformation +from config import config +from model import model + +class App(CTk): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + ## set UI theme + customtkinter.set_appearance_mode(config.APPEARANCE_THEME) + customtkinter.set_default_color_theme("blue") + + # init main window + self.iconbitmap(os_path.join(os_path.dirname(__file__), "img", "app.ico")) + self.title("VRCT") + self.geometry(f"{400}x{175}") + self.minsize(400, 175) + self.grid_columnconfigure(1, weight=1) + self.grid_rowconfigure(0, weight=1) + self.wm_attributes("-alpha", config.TRANSPARENCY/100) + customtkinter.set_widget_scaling(int(config.UI_SCALING.replace("%", "")) / 100) + self.protocol("WM_DELETE_WINDOW", self.delete_window) + + # add sidebar + self.add_sidebar() + + # add entry message box + self.entry_message_box = CTkEntry( + self, + placeholder_text="message", + font=CTkFont(family=config.FONT_FAMILY), + ) + self.entry_message_box.grid(row=1, column=1, columnspan=2, padx=5, pady=(5, 10), sticky="nsew") + self.entry_message_box.bind("", self.entry_message_box_press_key_enter) + self.entry_message_box.bind("", self.entry_message_box_press_key_any) + self.entry_message_box.bind("", self.entry_message_box_leave) + + # add tabview textbox + self.add_tabview_logs(get_localized_text(f"{config.UI_LANGUAGE}")) + + self.config_window = ToplevelWindowConfig(self) + self.information_window = ToplevelWindowInformation(self) + self.init_process() + + def init_process(self): + # set translator + if model.authenticationTranslator() is False: + # error update Auth key + self.printLogAuthenticationError() + + # set word filter + model.addKeywords() + + # check OSC started + model.checkOSCStarted() + + # check Software Updated + model.checkSoftwareUpdated() + + def button_config_callback(self): + self.foreground_stop() + self.transcription_stop() + self.checkbox_translation.configure(state="disabled") + self.checkbox_transcription_send.configure(state="disabled") + self.checkbox_transcription_receive.configure(state="disabled") + self.checkbox_foreground.configure(state="disabled") + self.tabview_logs.configure(state="disabled") + self.textbox_message_log.configure(state="disabled") + self.textbox_message_send_log.configure(state="disabled") + self.textbox_message_receive_log.configure(state="disabled") + self.textbox_message_system_log.configure(state="disabled") + self.entry_message_box.configure(state="disabled") + self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) + self.button_information.configure(state="disabled", fg_color=["gray92", "gray14"]) + self.config_window.deiconify() + self.config_window.focus_set() + self.config_window.focus() + self.config_window.grab_set() + + def button_information_callback(self): + self.information_window.deiconify() + self.information_window.focus_set() + self.information_window.focus() + + def checkbox_translation_callback(self): + config.ENABLE_TRANSLATION = self.checkbox_translation.get() + if config.ENABLE_TRANSLATION is True: + self.printLogStartTranslation() + else: + self.printLogStopTranslation() + + def transcription_send_start(self): + model.startMicTranscript(self.sendMicMessage) + self.printLogStartVoice2chatbox() + self.checkbox_transcription_send.configure(state="normal") + self.checkbox_transcription_receive.configure(state="normal") + self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) + + def transcription_send_stop(self): + model.stopMicTranscript() + self.printLogStopVoice2chatbox() + self.checkbox_transcription_send.configure(state="normal") + self.checkbox_transcription_receive.configure(state="normal") + self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) + + def transcription_send_stop_for_config(self): + model.stopMicTranscript() + self.printLogStopVoice2chatbox() + + def checkbox_transcription_send_callback(self): + config.ENABLE_TRANSCRIPTION_SEND = self.checkbox_transcription_send.get() + self.checkbox_transcription_send.configure(state="disabled") + self.checkbox_transcription_receive.configure(state="disabled") + self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) + if config.ENABLE_TRANSCRIPTION_SEND is True: + th_transcription_send_start = Thread(target=self.transcription_send_start) + th_transcription_send_start.daemon = True + th_transcription_send_start.start() + else: + th_transcription_send_stop = Thread(target=self.transcription_send_stop) + th_transcription_send_stop.daemon = True + th_transcription_send_stop.start() + + def transcription_receive_start(self): + model.startSpeakerTranscript(self.receiveSpeakerMessage) + self.printLogStartSpeaker2log() + self.checkbox_transcription_send.configure(state="normal") + self.checkbox_transcription_receive.configure(state="normal") + self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) + + def transcription_receive_stop(self): + model.stopSpeakerTranscript() + self.printLogStopSpeaker2log() + self.checkbox_transcription_send.configure(state="normal") + self.checkbox_transcription_receive.configure(state="normal") + self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) + + def transcription_receive_stop_for_config(self): + model.stopSpeakerTranscript() + self.printLogStopSpeaker2log() + + def checkbox_transcription_receive_callback(self): + config.ENABLE_TRANSCRIPTION_RECEIVE = self.checkbox_transcription_receive.get() + self.checkbox_transcription_send.configure(state="disabled") + self.checkbox_transcription_receive.configure(state="disabled") + self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + th_transcription_receive_start = Thread(target=self.transcription_receive_start) + th_transcription_receive_start.daemon = True + th_transcription_receive_start.start() + else: + th_transcription_receive_stop = Thread(target=self.transcription_receive_stop) + th_transcription_receive_stop.daemon = True + th_transcription_receive_stop.start() + + def transcription_start(self): + if config.ENABLE_TRANSCRIPTION_SEND is True: + th_transcription_send_start = Thread(target=self.transcription_send_start) + th_transcription_send_start.daemon = True + th_transcription_send_start.start() + sleep(2) + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + th_transcription_receive_start = Thread(target=self.transcription_receive_start) + th_transcription_receive_start.daemon = True + th_transcription_receive_start.start() + + def transcription_stop(self): + if config.ENABLE_TRANSCRIPTION_SEND is True: + th_transcription_send_stop = Thread(target=self.transcription_send_stop_for_config) + th_transcription_send_stop.daemon = True + th_transcription_send_stop.start() + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + th_transcription_receive_stop = Thread(target=self.transcription_receive_stop_for_config) + th_transcription_receive_stop.daemon = True + th_transcription_receive_stop.start() + + def checkbox_foreground_callback(self): + config.ENABLE_FOREGROUND = self.checkbox_foreground.get() + if config.ENABLE_FOREGROUND: + self.attributes("-topmost", True) + self.printLogStartForeground() + else: + self.attributes("-topmost", False) + self.printLogStopForeground() + + def foreground_start(self): + if config.ENABLE_FOREGROUND: + self.attributes("-topmost", True) + self.printLogStartForeground() + + def foreground_stop(self): + if config.ENABLE_FOREGROUND: + self.attributes("-topmost", False) + self.printLogStopForeground() + + def entry_message_box_press_key_enter(self, event): + # osc stop send typing + model.oscStopSendTyping() + + if config.ENABLE_FOREGROUND: + self.attributes("-topmost", True) + + message = self.entry_message_box.get() + self.sendChatMessage(message) + + def entry_message_box_press_key_any(self, event): + # osc start send typing + model.oscStartSendTyping() + if config.ENABLE_FOREGROUND: + self.attributes("-topmost", False) + + if event.keysym != "??": + if len(event.char) != 0 and event.keysym in config.BREAK_KEYSYM_LIST: + self.entry_message_box.insert("end", event.char) + return "break" + + def entry_message_box_leave(self, event): + # osc stop send typing + model.oscStopSendTyping() + if config.ENABLE_FOREGROUND: + self.attributes("-topmost", True) + + def delete_window(self): + self.quit() + self.destroy() + + def add_sidebar(self): + init_lang_text = "Loading..." + self.sidebar_frame = CTkFrame(master=self, corner_radius=0) + + # add checkbox translation + self.checkbox_translation = CTkCheckBox( + self.sidebar_frame, + text=init_lang_text, + onvalue=True, + offvalue=False, + command=self.checkbox_translation_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + + # add checkbox transcription send + self.checkbox_transcription_send = CTkCheckBox( + self.sidebar_frame, + text=init_lang_text, + onvalue=True, + offvalue=False, + command=self.checkbox_transcription_send_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + + # add checkbox transcription receive + self.checkbox_transcription_receive = CTkCheckBox( + self.sidebar_frame, + text=init_lang_text, + onvalue=True, + offvalue=False, + command=self.checkbox_transcription_receive_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + + # add checkbox foreground + self.checkbox_foreground = CTkCheckBox( + self.sidebar_frame, + text=init_lang_text, + onvalue=True, + offvalue=False, + command=self.checkbox_foreground_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + + # add button information + self.button_information = CTkButton( + self.sidebar_frame, + text=None, + width=36, + command=self.button_information_callback, + image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "info-icon-white.png"))) + ) + + # add button config + self.button_config = CTkButton( + self.sidebar_frame, + text=None, + width=36, + command=self.button_config_callback, + image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "config-icon-white.png"))) + ) + + self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsw") + self.sidebar_frame.grid_rowconfigure(5, weight=1) + self.checkbox_translation.grid(row=0, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") + self.checkbox_transcription_send.grid(row=1, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") + self.checkbox_transcription_receive.grid(row=2, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") + self.checkbox_foreground.grid(row=3, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") + self.button_information.grid(row=5, column=0, padx=(10, 5), pady=(5, 5), sticky="wse") + self.button_config.grid(row=5, column=1, padx=(5, 10), pady=(5, 5), sticky="wse") + + def delete_tabview_logs(self, pre_language_yaml_data): + self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_log"]) + self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_send"]) + self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_receive"]) + self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_system"]) + + def add_tabview_logs(self, language_yaml_data): + main_tab_title_log = language_yaml_data["main_tab_title_log"] + main_tab_title_send = language_yaml_data["main_tab_title_send"] + main_tab_title_receive = language_yaml_data["main_tab_title_receive"] + main_tab_title_system = language_yaml_data["main_tab_title_system"] + + # add tabview textbox + self.tabview_logs = CTkTabview(master=self) + self.tabview_logs.add(main_tab_title_log) + self.tabview_logs.add(main_tab_title_send) + self.tabview_logs.add(main_tab_title_receive) + self.tabview_logs.add(main_tab_title_system) + self.tabview_logs.grid(row=0, column=1, padx=0, pady=0, sticky="nsew") + self.tabview_logs._segmented_button.configure(font=CTkFont(family=config.FONT_FAMILY)) + self.tabview_logs._segmented_button.grid(sticky="W") + self.tabview_logs.tab(main_tab_title_log).grid_rowconfigure(0, weight=1) + self.tabview_logs.tab(main_tab_title_log).grid_columnconfigure(0, weight=1) + self.tabview_logs.tab(main_tab_title_send).grid_rowconfigure(0, weight=1) + self.tabview_logs.tab(main_tab_title_send).grid_columnconfigure(0, weight=1) + self.tabview_logs.tab(main_tab_title_receive).grid_rowconfigure(0, weight=1) + self.tabview_logs.tab(main_tab_title_receive).grid_columnconfigure(0, weight=1) + self.tabview_logs.tab(main_tab_title_system).grid_rowconfigure(0, weight=1) + self.tabview_logs.tab(main_tab_title_system).grid_columnconfigure(0, weight=1) + self.tabview_logs.configure(fg_color="transparent") + + # add textbox message log + self.textbox_message_log = CTkTextbox( + self.tabview_logs.tab(main_tab_title_log), + font=CTkFont(family=config.FONT_FAMILY) + ) + + # add textbox message send log + self.textbox_message_send_log = CTkTextbox( + self.tabview_logs.tab(main_tab_title_send), + font=CTkFont(family=config.FONT_FAMILY) + ) + + # add textbox message receive log + self.textbox_message_receive_log = CTkTextbox( + self.tabview_logs.tab(main_tab_title_receive), + font=CTkFont(family=config.FONT_FAMILY) + ) + + # add textbox message system log + self.textbox_message_system_log = CTkTextbox( + self.tabview_logs.tab(main_tab_title_system), + font=CTkFont(family=config.FONT_FAMILY) + ) + + self.textbox_message_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") + self.textbox_message_send_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") + self.textbox_message_receive_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") + self.textbox_message_system_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") + self.textbox_message_log.configure(state='disabled') + self.textbox_message_send_log.configure(state='disabled') + self.textbox_message_receive_log.configure(state='disabled') + self.textbox_message_system_log.configure(state='disabled') + + widget_main_window_label_setter(self, language_yaml_data) + + def printLogAuthenticationError(self): + print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") + print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") + + def printLogStartTranslation(self): + print_textbox(self.textbox_message_log, "Start translation", "INFO") + print_textbox(self.textbox_message_system_log, "Start translation", "INFO") + + def printLogStopTranslation(self): + print_textbox(self.textbox_message_log, "Stop translation", "INFO") + print_textbox(self.textbox_message_system_log, "Stop translation", "INFO") + + def printLogStartVoice2chatbox(self): + print_textbox(self.textbox_message_log, "Start voice2chatbox", "INFO") + print_textbox(self.textbox_message_system_log, "Start voice2chatbox", "INFO") + + def printLogStopVoice2chatbox(self): + print_textbox(self.textbox_message_log, "Stop voice2chatbox", "INFO") + print_textbox(self.textbox_message_system_log, "Stop voice2chatbox", "INFO") + + def printLogStartSpeaker2log(self): + print_textbox(self.textbox_message_log, "Start speaker2log", "INFO") + print_textbox(self.textbox_message_system_log, "Start speaker2log", "INFO") + + def printLogStopSpeaker2log(self): + print_textbox(self.textbox_message_log, "Stop speaker2log", "INFO") + print_textbox(self.textbox_message_system_log, "Stop speaker2log", "INFO") + + def printLogStartForeground(self): + print_textbox(self.textbox_message_log, "Start foreground", "INFO") + print_textbox(self.textbox_message_system_log, "Start foreground", "INFO") + + def printLogStopForeground(self): + print_textbox(self.textbox_message_log, "Stop foreground", "INFO") + print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO") + + def printLogDetectWordFilter(self, message): + print_textbox(self.textbox_message_log, f"Detect WordFilter :{message}", "INFO") + print_textbox(self.textbox_message_system_log, f"Detect WordFilter :{message}", "INFO") + + def printLogOSCError(self): + print_textbox(self.textbox_message_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") + print_textbox(self.textbox_message_system_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") + + def printLogSendMessage(self, message): + print_textbox(self.textbox_message_log, f"{message}", "SEND") + print_textbox(self.textbox_message_send_log, f"{message}", "SEND") + + def printLogReceiveMessage(self, message): + print_textbox(self.textbox_message_log, f"{message}", "RECEIVE") + print_textbox(self.textbox_message_receive_log, f"{message}", "RECEIVE") + + def sendChatMessage(self, message): + if len(message) > 0: + # translate + if config.ENABLE_TRANSLATION is False: + chat_message = f"{message}" + elif model.getTranslatorStatus() is False: + self.printLogAuthenticationError() + chat_message = f"{message}" + else: + chat_message = model.getInputTranslate(message) + + # send OSC message + if config.ENABLE_OSC is True: + model.oscSendMessage(chat_message) + else: + self.printLogOSCError() + + # update textbox message log + self.printLogSendMessage(chat_message) + + # delete message in entry message box + if config.ENABLE_AUTO_CLEAR_CHATBOX is True: + self.entry_message_box.delete(0, customtkinter.END) + + def sendMicMessage(self, message): + if len(message) > 0: + # word filter + if model.checkKeywords(message): + self.printLogDetectWordFilter(message) + return + + # translate + if config.ENABLE_TRANSLATION is False: + voice_message = f"{message}" + elif model.getTranslatorStatus() is False: + self.printLogAuthenticationError() + voice_message = f"{message}" + else: + voice_message = model.getInputTranslate(message) + + if config.ENABLE_TRANSCRIPTION_SEND is True: + if config.ENABLE_OSC is True: + # osc send message + model.oscSendMessage(voice_message) + else: + self.printLogOSCError() + # update textbox message log + self.printLogSendMessage(voice_message) + + def receiveSpeakerMessage(self, message): + if len(message) > 0: + # translate + if config.ENABLE_TRANSLATION is False: + voice_message = f"{message}" + elif model.getTranslatorStatus() is False: + self.printLogAuthenticationError() + voice_message = f"{message}" + else: + voice_message = model.getOutputTranslate(message) + + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + # update textbox message receive log + self.printLogReceiveMessage(voice_message) + if config.ENABLE_NOTICE_XSOVERLAY is True: + model.notificationXsoverlay(voice_message) + +if __name__ == "__main__": + try: + app = App() + app.mainloop() + except Exception as e: + import traceback + with open(os.path.join(os.path.dirname(__file__), 'error.log'), 'a') as f: + traceback.print_exc(file=f) \ No newline at end of file diff --git a/test_model/utils.py b/test_model/utils.py new file mode 100644 index 00000000..2bee5814 --- /dev/null +++ b/test_model/utils.py @@ -0,0 +1,106 @@ +from os import path as os_path +import yaml +from datetime import datetime + +def print_textbox(textbox, message, tags=None): + now = datetime.now() + now = now.strftime('%H:%M:%S') + + textbox.tag_config("ERROR", foreground="#FF0000") + textbox.tag_config("INFO", foreground="#1BFF00") + textbox.tag_config("SEND", foreground="#0378e2") + textbox.tag_config("RECEIVE", foreground="#ffa500") + + textbox.configure(state='normal') + textbox.insert("end", f"[{now}][") + textbox.insert("end", f"{tags}", tags) + textbox.insert("end", f"]{message}\n") + textbox.configure(state='disabled') + textbox.see("end") + +def get_localized_text(language): + file_path = os_path.join(os_path.dirname(__file__), "locales.yml") + + with open(file_path, encoding="utf-8") as file: + languages_yaml_data = yaml.safe_load(file) + default_language = "en" + if language in languages_yaml_data: + localized_text = languages_yaml_data[language] + if default_language in languages_yaml_data: + default_text = languages_yaml_data[default_language] + merged_text = {**default_text, **localized_text} + return merged_text + else: + return localized_text + else: + return None + +def get_key_by_value(dictionary, value): + for key, val in dictionary.items(): + if val == value: + return key + return None + +def widget_config_window_label_setter(self, language_yaml_data): + widget_names = [ + # tab UI + "label_transparency", + "label_appearance_theme", + "label_ui_scaling", + "label_font_family", + "label_ui_language", + + # tab Translation + "label_translation_translator", + "label_translation_input_language", + "label_translation_output_language", + + # tab Transcription + "label_input_mic_host", + "label_input_mic_device", + "label_input_mic_voice_language", + "label_input_mic_energy_threshold", + "checkbox_input_mic_threshold_check", + "label_input_mic_dynamic_energy_threshold", + "label_input_mic_record_timeout", + "label_input_mic_phrase_timeout", + "label_input_mic_max_phrases", + "label_input_mic_word_filter", + + "label_input_speaker_device", + "label_input_speaker_voice_language", + "label_input_speaker_energy_threshold", + "checkbox_input_speaker_threshold_check", + "label_input_speaker_dynamic_energy_threshold", + "label_input_speaker_record_timeout", + "label_input_speaker_phrase_timeout", + "label_input_speaker_max_phrases", + + # tab Parameter + "label_ip_address", + "label_port", + "label_authkey", + "label_message_format", + + # tab Others + "label_checkbox_auto_clear_chatbox", + "label_checkbox_notice_xsoverlay", + ] + for name in widget_names: + widget = getattr(self, name) + text_value = language_yaml_data.get(name) + if widget is not None and text_value is not None: + widget.configure(text=text_value + ":") + +def widget_main_window_label_setter(self, language_yaml_data): + widget_names = [ + "checkbox_translation", + "checkbox_transcription_send", + "checkbox_transcription_receive", + "checkbox_foreground", + ] + for name in widget_names: + widget = getattr(self, name) + text_value = language_yaml_data.get(name) + if widget is not None and text_value is not None: + widget.configure(text=text_value) \ No newline at end of file diff --git a/test_model/window_config.py b/test_model/window_config.py new file mode 100644 index 00000000..89dcf6ce --- /dev/null +++ b/test_model/window_config.py @@ -0,0 +1,1348 @@ +import os +import sys +sys.path.append(os.path.join(os.path.dirname(__file__), '..')) + +from os import path as os_path +from tkinter import DoubleVar, IntVar +from tkinter import font as tk_font +import customtkinter +from customtkinter import CTkToplevel, CTkTabview, CTkFont, CTkLabel, CTkSlider, CTkOptionMenu, StringVar, CTkEntry, CTkCheckBox, CTkProgressBar + +from threading import Thread +from config import config +from model import model +from utils import print_textbox, get_localized_text, get_key_by_value, widget_config_window_label_setter +from languages import selectable_languages +from models.translation.translation_languages import translation_lang +from models.transcription.transcription_languages import transcription_lang +from ctk_scrollable_dropdown import CTkScrollableDropdown + +SCROLLABLE_DROPDOWN = False + +class ToplevelWindowConfig(CTkToplevel): + + def __init__(self, parent, *args, **kwargs): + super().__init__(parent, *args, **kwargs) + + self.withdraw() + self.parent = parent + # self.geometry(f"{350}x{270}") + # self.resizable(False, False) + self.grid_columnconfigure(0, weight=1) + self.grid_rowconfigure(0, weight=1) + + self.after(200, lambda: self.iconbitmap(os_path.join(os_path.dirname(__file__), "img", "app.ico"))) + self.title("Config") + + # load ui language data + language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") + # add tabview config + self.add_tabview_config(language_yaml_data, selectable_languages) + # set all config window labels + widget_config_window_label_setter(self, language_yaml_data) + + self.protocol("WM_DELETE_WINDOW", self.delete_window) + + def slider_transparency_callback(self, value): + self.parent.wm_attributes("-alpha", value/100) + config.TRANSPARENCY = value + + def optionmenu_appearance_theme_callback(self, choice): + self.optionmenu_appearance_theme.set(choice) + + customtkinter.set_appearance_mode(choice) + config.APPEARANCE_THEME = choice + + def optionmenu_ui_scaling_callback(self, choice): + self.optionmenu_ui_scaling.set(choice) + + new_scaling_float = int(choice.replace("%", "")) / 100 + customtkinter.set_widget_scaling(new_scaling_float) + config.UI_SCALING = choice + + def optionmenu_font_family_callback(self, choice): + self.optionmenu_font_family.set(choice) + + # tab menu + self.tabview_config._segmented_button.configure(font=CTkFont(family=choice)) + + # tab UI + self.label_transparency.configure(font=CTkFont(family=choice)) + self.label_appearance_theme.configure(font=CTkFont(family=choice)) + self.optionmenu_appearance_theme.configure(font=CTkFont(family=choice)) + self.optionmenu_appearance_theme._dropdown_menu.configure(font=CTkFont(family=choice)) + self.label_ui_scaling.configure(font=CTkFont(family=choice)) + self.optionmenu_ui_scaling.configure(font=CTkFont(family=choice)) + self.optionmenu_ui_scaling._dropdown_menu.configure(font=CTkFont(family=choice)) + self.label_font_family.configure(font=CTkFont(family=choice)) + self.optionmenu_font_family.configure(font=CTkFont(family=choice)) + self.optionmenu_font_family._dropdown_menu.configure(font=CTkFont(family=choice)) + self.label_ui_language.configure(font=CTkFont(family=choice)) + self.optionmenu_ui_language.configure(font=CTkFont(family=choice)) + self.optionmenu_ui_language._dropdown_menu.configure(font=CTkFont(family=choice)) + + # tab Translation + self.label_translation_translator.configure(font=CTkFont(family=choice)) + self.optionmenu_translation_translator.configure(font=CTkFont(family=choice)) + self.optionmenu_translation_translator._dropdown_menu.configure(font=CTkFont(family=choice)) + self.label_translation_input_language.configure(font=CTkFont(family=choice)) + self.optionmenu_translation_input_source_language.configure(font=CTkFont(family=choice)) + self.optionmenu_translation_input_source_language._dropdown_menu.configure(font=CTkFont(family=choice)) + self.label_translation_input_arrow.configure(font=CTkFont(family=choice)) + self.optionmenu_translation_input_target_language.configure(font=CTkFont(family=choice)) + self.optionmenu_translation_input_target_language._dropdown_menu.configure(font=CTkFont(family=choice)) + self.label_translation_output_language.configure(font=CTkFont(family=choice)) + self.optionmenu_translation_output_source_language.configure(font=CTkFont(family=choice)) + self.optionmenu_translation_output_source_language._dropdown_menu.configure(font=CTkFont(family=choice)) + self.label_translation_output_arrow.configure(font=CTkFont(family=choice)) + self.optionmenu_translation_output_target_language.configure(font=CTkFont(family=choice)) + self.optionmenu_translation_output_target_language._dropdown_menu.configure(font=CTkFont(family=choice)) + + # tab Transcription + self.label_input_mic_host.configure(font=CTkFont(family=choice)) + self.optionmenu_input_mic_host.configure(font=CTkFont(family=choice)) + self.optionmenu_input_mic_host._dropdown_menu.configure(font=CTkFont(family=choice)) + self.label_input_mic_device.configure(font=CTkFont(family=choice)) + self.optionmenu_input_mic_device.configure(font=CTkFont(family=choice)) + self.optionmenu_input_mic_device._dropdown_menu.configure(font=CTkFont(family=choice)) + self.label_input_mic_voice_language.configure(font=CTkFont(family=choice)) + self.optionmenu_input_mic_voice_language.configure(font=CTkFont(family=choice)) + self.optionmenu_input_mic_voice_language._dropdown_menu.configure(font=CTkFont(family=choice)) + self.checkbox_input_mic_threshold_check.configure(font=CTkFont(family=choice)) + self.label_input_mic_energy_threshold.configure(font=CTkFont(family=choice)) + self.label_input_mic_dynamic_energy_threshold.configure(font=CTkFont(family=choice)) + self.label_input_mic_record_timeout.configure(font=CTkFont(family=choice)) + self.entry_input_mic_record_timeout.configure(font=CTkFont(family=choice)) + self.label_input_mic_phrase_timeout.configure(font=CTkFont(family=choice)) + self.entry_input_mic_phrase_timeout.configure(font=CTkFont(family=choice)) + self.label_input_mic_max_phrases.configure(font=CTkFont(family=choice)) + self.entry_input_mic_max_phrases.configure(font=CTkFont(family=choice)) + self.label_input_mic_word_filter.configure(font=CTkFont(family=choice)) + self.entry_input_mic_word_filter.configure(font=CTkFont(family=choice)) + self.label_input_speaker_device.configure(font=CTkFont(family=choice)) + self.optionmenu_input_speaker_device.configure(font=CTkFont(family=choice)) + self.optionmenu_input_speaker_device._dropdown_menu.configure(font=CTkFont(family=choice)) + self.label_input_speaker_voice_language.configure(font=CTkFont(family=choice)) + self.optionmenu_input_speaker_voice_language.configure(font=CTkFont(family=choice)) + self.optionmenu_input_speaker_voice_language._dropdown_menu.configure(font=CTkFont(family=choice)) + self.checkbox_input_speaker_threshold_check.configure(font=CTkFont(family=choice)) + self.label_input_speaker_energy_threshold.configure(font=CTkFont(family=choice)) + self.label_input_speaker_dynamic_energy_threshold.configure(font=CTkFont(family=choice)) + self.label_input_speaker_record_timeout.configure(font=CTkFont(family=choice)) + self.entry_input_speaker_record_timeout.configure(font=CTkFont(family=choice)) + self.label_input_speaker_phrase_timeout.configure(font=CTkFont(family=choice)) + self.entry_input_speaker_phrase_timeout.configure(font=CTkFont(family=choice)) + self.label_input_speaker_max_phrases.configure(font=CTkFont(family=choice)) + self.entry_input_speaker_max_phrases.configure(font=CTkFont(family=choice)) + + # tab Parameter + self.label_ip_address.configure(font=CTkFont(family=choice)) + self.entry_ip_address.configure(font=CTkFont(family=choice)) + self.label_port.configure(font=CTkFont(family=choice)) + self.entry_port.configure(font=CTkFont(family=choice)) + self.label_authkey.configure(font=CTkFont(family=choice)) + self.entry_authkey.configure(font=CTkFont(family=choice)) + self.label_message_format.configure(font=CTkFont(family=choice)) + self.entry_message_format.configure(font=CTkFont(family=choice)) + + # tab Others + self.label_checkbox_auto_clear_chatbox.configure(font=CTkFont(family=choice)) + + # main window + self.parent.checkbox_translation.configure(font=CTkFont(family=choice)) + self.parent.checkbox_transcription_send.configure(font=CTkFont(family=choice)) + self.parent.checkbox_transcription_receive.configure(font=CTkFont(family=choice)) + self.parent.checkbox_foreground.configure(font=CTkFont(family=choice)) + self.parent.textbox_message_log.configure(font=CTkFont(family=choice)) + self.parent.textbox_message_send_log.configure(font=CTkFont(family=choice)) + self.parent.textbox_message_receive_log.configure(font=CTkFont(family=choice)) + self.parent.textbox_message_system_log.configure(font=CTkFont(family=choice)) + self.parent.entry_message_box.configure(font=CTkFont(family=choice)) + self.parent.tabview_logs._segmented_button.configure(font=CTkFont(family=choice)) + + # window information + try: + self.parent.information_window.textbox_information.configure(font=CTkFont(family=choice)) + except: + pass + + config.FONT_FAMILY = choice + + def optionmenu_ui_language_callback(self, choice): + self.optionmenu_ui_language.set(choice) + + self.withdraw() + pre_language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") + config.UI_LANGUAGE = get_key_by_value(selectable_languages, choice) + language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") + + # delete + self.parent.delete_tabview_logs(pre_language_yaml_data) + self.delete_tabview_config(pre_language_yaml_data) + # add tabview textbox + self.parent.add_tabview_logs(language_yaml_data) + self.add_tabview_config(language_yaml_data, selectable_languages) + + # 翻訳予定 + # window information + # try: + # self.parent.information_window.textbox_information.configure(font=customtkinter.CTkFont(family=choice)) + # except: + # pass + self.deiconify() + + def optionmenu_translation_translator_callback(self, choice): + self.optionmenu_translation_translator.set(choice) + + if model.authenticationTranslator(choice_translator=choice) is False: + print_textbox(self.parent.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") + print_textbox(self.parent.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") + else: + self.optionmenu_translation_input_source_language.configure( + values=list(translation_lang[choice]["source"].keys()), + variable=StringVar(value=list(translation_lang[choice]["source"].keys())[0])) + self.optionmenu_translation_input_target_language.configure( + values=list(translation_lang[choice]["target"].keys()), + variable=StringVar(value=list(translation_lang[choice]["target"].keys())[1])) + self.optionmenu_translation_output_source_language.configure( + values=list(translation_lang[choice]["source"].keys()), + variable=StringVar(value=list(translation_lang[choice]["source"].keys())[1])) + self.optionmenu_translation_output_target_language.configure( + values=list(translation_lang[choice]["target"].keys()), + variable=StringVar(value=list(translation_lang[choice]["target"].keys())[0])) + + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_translation_input_source_language.configure( + values=list(translation_lang[choice]["source"].keys())) + self.scrollableDropdown_translation_input_target_language.configure( + values=list(translation_lang[choice]["target"].keys())) + self.scrollableDropdown_translation_output_source_language.configure( + values=list(translation_lang[choice]["source"].keys())) + self.scrollableDropdown_translation_output_target_language.configure( + values=list(translation_lang[choice]["target"].keys())) + + config.CHOICE_TRANSLATOR = choice + config.INPUT_SOURCE_LANG = list(translation_lang[choice]["source"].keys())[0] + config.INPUT_TARGET_LANG = list(translation_lang[choice]["target"].keys())[1] + config.OUTPUT_SOURCE_LANG = list(translation_lang[choice]["source"].keys())[1] + config.OUTPUT_TARGET_LANG = list(translation_lang[choice]["target"].keys())[0] + + def optionmenu_translation_input_source_language_callback(self, choice): + self.optionmenu_translation_input_source_language.set(choice) + config.INPUT_SOURCE_LANG = choice + + def optionmenu_translation_input_target_language_callback(self, choice): + self.optionmenu_translation_input_target_language.set(choice) + config.INPUT_TARGET_LANG = choice + + def optionmenu_translation_output_source_language_callback(self, choice): + self.optionmenu_translation_output_source_language.set(choice) + config.OUTPUT_SOURCE_LANG = choice + + def optionmenu_translation_output_target_language_callback(self, choice): + self.optionmenu_translation_output_target_language.set(choice) + config.OUTPUT_TARGET_LANG = choice + + def optionmenu_input_mic_host_callback(self, choice): + self.optionmenu_input_mic_host.set(choice) + config.CHOICE_MIC_HOST = choice + config.CHOICE_MIC_DEVICE = model.getInputDefaultDevice() + + self.optionmenu_input_mic_device.configure( + values=model.getListInputDevice(), + variable=StringVar(value=model.getInputDefaultDevice())) + + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_input_mic_device.configure(values=model.getListInputDevice()) + + def optionmenu_input_mic_device_callback(self, choice): + self.optionmenu_input_mic_device.set(choice) + config.CHOICE_MIC_DEVICE = choice + self.checkbox_input_mic_threshold_check.deselect() + self.checkbox_input_mic_threshold_check_callback() + + def optionmenu_input_mic_voice_language_callback(self, choice): + self.optionmenu_input_mic_voice_language.set(choice) + config.INPUT_MIC_VOICE_LANGUAGE = choice + + def mic_threshold_check_start(self): + def plotProgressBar(energy): + try: + self.progressBar_input_mic_energy_threshold.set(energy/config.MAX_MIC_ENERGY_THRESHOLD) + except: + pass + model.startCheckMicEnergy(plotProgressBar) + self.checkbox_input_mic_threshold_check.configure(state="normal") + self.checkbox_input_speaker_threshold_check.configure(state="normal") + + def mic_threshold_check_stop(self): + model.stopCheckMicEnergy() + self.progressBar_input_mic_energy_threshold.set(0) + self.checkbox_input_mic_threshold_check.configure(state="normal") + self.checkbox_input_speaker_threshold_check.configure(state="normal") + + def checkbox_input_mic_threshold_check_callback(self): + self.checkbox_input_mic_threshold_check.configure(state="disabled") + self.checkbox_input_speaker_threshold_check.configure(state="disabled") + self.update() + if self.checkbox_input_mic_threshold_check.get(): + th_mic_threshold_check_start = Thread(target=self.mic_threshold_check_start) + th_mic_threshold_check_start.daemon = True + th_mic_threshold_check_start.start() + else: + th_mic_threshold_check_stop = Thread(target=self.mic_threshold_check_stop) + th_mic_threshold_check_stop.daemon = True + th_mic_threshold_check_stop.start() + + def slider_input_mic_energy_threshold_callback(self, value): + config.INPUT_MIC_ENERGY_THRESHOLD = int(value) + + def checkbox_input_mic_dynamic_energy_threshold_callback(self): + config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = self.checkbox_input_mic_dynamic_energy_threshold.get() + + def entry_input_mic_record_timeout_callback(self, event): + config.INPUT_MIC_RECORD_TIMEOUT = int(self.entry_input_mic_record_timeout.get()) + + def entry_input_mic_phrase_timeout_callback(self, event): + config.INPUT_MIC_PHRASE_TIMEOUT = int(self.entry_input_mic_phrase_timeout.get()) + + def entry_input_mic_max_phrases_callback(self, event): + config.INPUT_MIC_MAX_PHRASES = int(self.entry_input_mic_max_phrases.get()) + + def entry_input_mic_word_filters_callback(self, event): + word_filter = self.entry_input_mic_word_filter.get() + word_filter = [w.strip() for w in word_filter.split(",") if len(w.strip()) > 0] + word_filter = ",".join(word_filter) + if len(word_filter) > 0: + config.INPUT_MIC_WORD_FILTER = word_filter.split(",") + else: + config.INPUT_MIC_WORD_FILTER = [] + model.resetKeywordProcessor() + model.addKeywords() + + def optionmenu_input_speaker_device_callback(self, choice): + if model.checkSpeakerStatus(choice): + self.optionmenu_input_speaker_device.set(choice) + config.CHOICE_SPEAKER_DEVICE = choice + else: + print_textbox(self.parent.textbox_message_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") + print_textbox(self.parent.textbox_message_system_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") + self.optionmenu_input_speaker_device.configure(variable=StringVar(value=config.CHOICE_SPEAKER_DEVICE)) + + def optionmenu_input_speaker_voice_language_callback(self, choice): + self.optionmenu_input_speaker_voice_language.set(choice) + config.INPUT_SPEAKER_VOICE_LANGUAGE = choice + + def speaker_threshold_check_start(self): + def plotProgressBar(energy): + try: + self.progressBar_input_speaker_energy_threshold.set(energy/config.MAX_MIC_ENERGY_THRESHOLD) + except: + pass + model.startCheckSpeakerEnergy(plotProgressBar) + self.checkbox_input_mic_threshold_check.configure(state="normal") + self.checkbox_input_speaker_threshold_check.configure(state="normal") + + def speaker_threshold_check_stop(self): + model.stopCheckSpeakerEnergy() + self.progressBar_input_speaker_energy_threshold.set(0) + self.checkbox_input_mic_threshold_check.configure(state="normal") + self.checkbox_input_speaker_threshold_check.configure(state="normal") + + def checkbox_input_speaker_threshold_check_callback(self): + self.checkbox_input_mic_threshold_check.configure(state="disabled") + self.checkbox_input_speaker_threshold_check.configure(state="disabled") + self.update() + if self.checkbox_input_speaker_threshold_check.get(): + if model.checkSpeakerStatus(): + th_speaker_threshold_check_start = Thread(target=self.speaker_threshold_check_start) + th_speaker_threshold_check_start.daemon = True + th_speaker_threshold_check_start.start() + else: + print_textbox(self.parent.textbox_message_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") + print_textbox(self.parent.textbox_message_system_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") + self.checkbox_input_speaker_threshold_check.deselect() + else: + th_speaker_threshold_check_stop = Thread(target=self.speaker_threshold_check_stop) + th_speaker_threshold_check_stop.daemon = True + th_speaker_threshold_check_stop.start() + + def slider_input_speaker_energy_threshold_callback(self, value): + config.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) + + def checkbox_input_speaker_dynamic_energy_threshold_callback(self): + config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = self.checkbox_input_speaker_dynamic_energy_threshold.get() + + def entry_input_speaker_record_timeout_callback(self, event): + config.INPUT_SPEAKER_RECORD_TIMEOUT = int(self.entry_input_speaker_record_timeout.get()) + + def entry_input_speaker_phrase_timeout_callback(self, event): + config.INPUT_SPEAKER_PHRASE_TIMEOUT = int(self.entry_input_speaker_phrase_timeout.get()) + + def entry_input_speaker_max_phrases_callback(self, event): + config.INPUT_SPEAKER_MAX_PHRASES = int(self.entry_input_speaker_max_phrases.get()) + + def entry_ip_address_callback(self, event): + config.OSC_IP_ADDRESS = self.entry_ip_address.get() + + def entry_port_callback(self, event): + config.OSC_PORT = self.entry_port.get() + + def entry_authkey_callback(self, event): + value = self.entry_authkey.get() + if len(value) > 0: + if model.authenticationTranslator(choice_translator="DeepL(auth)", auth_key=value) is True: + print_textbox(self.parent.textbox_message_log, "Auth key update completed", "INFO") + print_textbox(self.parent.textbox_message_system_log, "Auth key update completed", "INFO") + else: + pass + + def checkbox_auto_clear_chatbox_callback(self): + config.ENABLE_AUTO_CLEAR_CHATBOX = self.checkbox_auto_clear_chatbox.get() + + def checkbox_notice_xsoverlay_callback(self): + config.ENABLE_NOTICE_XSOVERLAY = self.checkbox_notice_xsoverlay.get() + + def delete_window(self): + self.checkbox_input_mic_threshold_check.deselect() + self.checkbox_input_speaker_threshold_check.deselect() + self.checkbox_input_mic_threshold_check_callback() + self.checkbox_input_speaker_threshold_check_callback() + self.parent.transcription_start() + self.parent.foreground_start() + self.parent.checkbox_translation.configure(state="normal") + self.parent.checkbox_transcription_send.configure(state="normal") + self.parent.checkbox_transcription_receive.configure(state="normal") + self.parent.checkbox_foreground.configure(state="normal") + self.parent.tabview_logs.configure(state="normal") + self.parent.textbox_message_log.configure(state="normal") + self.parent.textbox_message_send_log.configure(state="normal") + self.parent.textbox_message_receive_log.configure(state="normal") + self.parent.textbox_message_system_log.configure(state="normal") + self.parent.entry_message_box.configure(state="normal") + self.parent.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) + self.parent.button_information.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) + self.withdraw() + self.grab_release() + + def entry_message_format_callback(self, event): + value = self.entry_message_format.get() + if len(value) > 0: + config.MESSAGE_FORMAT = value + + def delete_tabview_config(self, pre_language_yaml_data): + self.tabview_config.delete(pre_language_yaml_data["config_tab_title_ui"]) + self.tabview_config.delete(pre_language_yaml_data["config_tab_title_translation"]) + self.tabview_config.delete(pre_language_yaml_data["config_tab_title_transcription"]) + self.tabview_config.delete(pre_language_yaml_data["config_tab_title_parameter"]) + self.tabview_config.delete(pre_language_yaml_data["config_tab_title_others"]) + + def add_tabview_config(self, language_yaml_data, selectable_languages): + config_tab_title_ui = language_yaml_data["config_tab_title_ui"] + config_tab_title_translation = language_yaml_data["config_tab_title_translation"] + config_tab_title_transcription = language_yaml_data["config_tab_title_transcription"] + config_tab_title_parameter = language_yaml_data["config_tab_title_parameter"] + config_tab_title_others = language_yaml_data["config_tab_title_others"] + + init_lang_text = "Loading..." + + # tabwiew config + self.tabview_config = CTkTabview(self) + self.tabview_config.grid(row=0, column=0, padx=5, pady=5, sticky="nsew") + self.tabview_config.add(config_tab_title_ui) + self.tabview_config.add(config_tab_title_translation) + self.tabview_config.add(config_tab_title_transcription) + self.tabview_config.add(config_tab_title_parameter) + self.tabview_config.add(config_tab_title_others) + self.tabview_config.tab(config_tab_title_ui).grid_columnconfigure(1, weight=1) + self.tabview_config.tab(config_tab_title_translation).grid_columnconfigure([1,2,3], weight=1) + self.tabview_config.tab(config_tab_title_transcription).grid_columnconfigure(1, weight=1) + self.tabview_config.tab(config_tab_title_parameter).grid_columnconfigure(1, weight=1) + self.tabview_config.tab(config_tab_title_others).grid_columnconfigure(1, weight=1) + self.tabview_config._segmented_button.configure(font=CTkFont(family=config.FONT_FAMILY)) + self.tabview_config._segmented_button.grid(sticky="W") + + # tab UI + ## slider transparency + row = 0 + padx = 5 + pady = 1 + self.label_transparency = CTkLabel( + self.tabview_config.tab(config_tab_title_ui), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_transparency.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.slider_transparency = CTkSlider( + self.tabview_config.tab(config_tab_title_ui), + from_=50, + to=100, + command=self.slider_transparency_callback, + variable=DoubleVar(value=config.TRANSPARENCY), + ) + self.slider_transparency.grid(row=row, column=1, columnspan=1, padx=padx, pady=10, sticky="nsew") + + ## optionmenu theme + row += 1 + self.label_appearance_theme = CTkLabel( + self.tabview_config.tab(config_tab_title_ui), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_appearance_theme.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.optionmenu_appearance_theme = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_ui), + values=["Light", "Dark", "System"], + command=self.optionmenu_appearance_theme_callback, + variable=StringVar(value=config.APPEARANCE_THEME), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_appearance_theme.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown appearance theme + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_appearance_theme = CTkScrollableDropdown( + self.optionmenu_appearance_theme, + values=["Light", "Dark", "System"], + justify="left", + button_color="transparent", + command=self.optionmenu_appearance_theme_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_appearance_theme.bind( + "", + lambda e: self.scrollableDropdown_appearance_theme._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_appearance_theme.frame._parent_frame)) else None, + ) + + ## optionmenu UI scaling + row += 1 + self.label_ui_scaling = CTkLabel( + self.tabview_config.tab(config_tab_title_ui), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_ui_scaling.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.optionmenu_ui_scaling = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_ui), + values=["80%", "90%", "100%", "110%", "120%"], + command=self.optionmenu_ui_scaling_callback, + variable=StringVar(value=config.UI_SCALING), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_ui_scaling.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown ui scaling + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_ui_scaling = CTkScrollableDropdown( + self.optionmenu_ui_scaling, + values=["80%", "90%", "100%", "110%", "120%"], + justify="left", + button_color="transparent", + command=self.optionmenu_ui_scaling_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_ui_scaling.bind( + "", + lambda e: self.scrollableDropdown_ui_scaling._iconify() if not str(e.widget).startswith(str(self.scrollableDropdown_ui_scaling.frame._parent_frame)) else None, + ) + + ## optionmenu font family + row += 1 + self.label_font_family = CTkLabel( + self.tabview_config.tab(config_tab_title_ui), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_font_family.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + font_families = list(tk_font.families()) + self.optionmenu_font_family = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_ui), + values=font_families, + command=self.optionmenu_font_family_callback, + variable=StringVar(value=config.FONT_FAMILY), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_font_family.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown font family + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_font_family = CTkScrollableDropdown( + self.optionmenu_font_family, + values=font_families, + justify="left", + button_color="transparent", + command=self.optionmenu_font_family_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_font_family.bind( + "", + lambda e: self.scrollableDropdown_font_family._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_font_family.frame._parent_frame)) else None, + ) + + ## optionmenu ui language + row += 1 + self.label_ui_language = CTkLabel( + self.tabview_config.tab(config_tab_title_ui), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_ui_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + selectable_languages_values = list(selectable_languages.values()) + self.optionmenu_ui_language = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_ui), + values=selectable_languages_values, + command=self.optionmenu_ui_language_callback, + variable=StringVar(value=selectable_languages[config.UI_LANGUAGE]), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_ui_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown ui language + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_ui_language = CTkScrollableDropdown( + self.optionmenu_ui_language, + values=selectable_languages_values, + justify="left", + button_color="transparent", + command=self.optionmenu_ui_language_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_ui_language.bind( + "", + lambda e: self.scrollableDropdown_ui_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_ui_language.frame._parent_frame)) else None, + ) + + # tab Translation + ## optionmenu translation translator + row = 0 + padx = 5 + pady = 1 + self.label_translation_translator = CTkLabel( + self.tabview_config.tab(config_tab_title_translation), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY), + ) + self.label_translation_translator.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.optionmenu_translation_translator = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_translation), + values=model.getListTranslatorName(), + command=self.optionmenu_translation_translator_callback, + variable=StringVar(value=config.CHOICE_TRANSLATOR), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_translation_translator.grid(row=row, column=1, columnspan=3, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown translation translator + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_translation_translator = CTkScrollableDropdown( + self.optionmenu_translation_translator, + values=model.getListTranslatorName(), + justify="left", + button_color="transparent", + command=self.optionmenu_translation_translator_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_translation_translator.bind( + "", + lambda e: self.scrollableDropdown_translation_translator._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_translator.frame._parent_frame)) else None, + ) + + ## optionmenu translation input language + row +=1 + self.label_translation_input_language = CTkLabel( + self.tabview_config.tab(config_tab_title_translation), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_translation_input_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + + ## select translation input source language + self.optionmenu_translation_input_source_language = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_translation), + command=self.optionmenu_translation_input_source_language_callback, + values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), + variable=StringVar(value=config.INPUT_SOURCE_LANG), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_translation_input_source_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown translation input source language + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_translation_input_source_language = CTkScrollableDropdown( + self.optionmenu_translation_input_source_language, + values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), + justify="left", + button_color="transparent", + command=self.optionmenu_translation_input_source_language_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_translation_input_source_language.bind( + "", + lambda e: self.scrollableDropdown_translation_input_source_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_input_source_language.frame._parent_frame)) else None, + ) + + ## label translation input arrow + self.label_translation_input_arrow = CTkLabel( + self.tabview_config.tab(config_tab_title_translation), + text="-->", + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_translation_input_arrow.grid(row=row, column=2, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## select translation input target language + self.optionmenu_translation_input_target_language = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_translation), + command=self.optionmenu_translation_input_target_language_callback, + values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), + variable=StringVar(value=config.INPUT_TARGET_LANG), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_translation_input_target_language.grid(row=row, column=3, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown translation input target language + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_translation_input_target_language = CTkScrollableDropdown( + self.optionmenu_translation_input_target_language, + values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), + justify="left", + button_color="transparent", + command=self.optionmenu_translation_input_target_language_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_translation_input_target_language.bind( + "", + lambda e: self.scrollableDropdown_translation_input_target_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_input_target_language.frame._parent_frame)) else None, + ) + + ## optionmenu translation output language + row +=1 + self.label_translation_output_language = CTkLabel( + self.tabview_config.tab(config_tab_title_translation), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_translation_output_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + + ## select translation output source language + self.optionmenu_translation_output_source_language = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_translation), + command=self.optionmenu_translation_output_source_language_callback, + values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), + variable=StringVar(value=config.OUTPUT_SOURCE_LANG), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_translation_output_source_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown translation output source language + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_translation_output_source_language = CTkScrollableDropdown( + self.optionmenu_translation_output_source_language, + values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), + justify="left", + button_color="transparent", + command=self.optionmenu_translation_output_source_language_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_translation_output_source_language.bind( + "", + lambda e: self.scrollableDropdown_translation_output_source_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_output_source_language.frame._parent_frame)) else None, + ) + + ## label translation output arrow + self.label_translation_output_arrow = CTkLabel( + self.tabview_config.tab(config_tab_title_translation), + text="-->", + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_translation_output_arrow.grid(row=row, column=2, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## select translation output target language + self.optionmenu_translation_output_target_language = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_translation), + command=self.optionmenu_translation_output_target_language_callback, + values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), + variable=StringVar(value=config.OUTPUT_TARGET_LANG), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_translation_output_target_language.grid(row=row, column=3, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown translation output target language + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_translation_output_target_language = CTkScrollableDropdown( + self.optionmenu_translation_output_target_language, + values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), + justify="left", + button_color="transparent", + command=self.optionmenu_translation_output_target_language_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_translation_output_target_language.bind( + "", + lambda e: self.scrollableDropdown_translation_output_target_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_output_target_language.frame._parent_frame)) else None, + ) + + # tab Transcription + ## optionmenu input mic device's host + row = 0 + padx = 5 + pady = 1 + self.label_input_mic_host = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_mic_host.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.optionmenu_input_mic_host = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_transcription), + values=model.getListInputHost(), + command=self.optionmenu_input_mic_host_callback, + variable=StringVar(value=config.CHOICE_MIC_HOST), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_input_mic_host.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown input mic device's host + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_input_mic_host = CTkScrollableDropdown( + self.optionmenu_input_mic_host, + values=model.getListInputHost(), + justify="left", + button_color="transparent", + command=self.optionmenu_input_mic_host_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_input_mic_host.bind( + "", + lambda e: self.scrollableDropdown_input_mic_host._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_mic_host.frame._parent_frame)) else None, + ) + + ## optionmenu input mic device + row += 1 + self.label_input_mic_device = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_mic_device.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.optionmenu_input_mic_device = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_transcription), + values=model.getListInputDevice(), + command=self.optionmenu_input_mic_device_callback, + variable=StringVar(value=config.CHOICE_MIC_DEVICE), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_input_mic_device.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown input mic device + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_input_mic_device = CTkScrollableDropdown( + self.optionmenu_input_mic_device, + values=model.getListInputDevice(), + justify="left", + button_color="transparent", + command=self.optionmenu_input_mic_device_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_input_mic_device.bind( + "", + lambda e: self.scrollableDropdown_input_mic_device._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_mic_device.frame._parent_frame)) else None, + ) + + ## optionmenu input mic voice language + row +=1 + self.label_input_mic_voice_language = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_mic_voice_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.optionmenu_input_mic_voice_language = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_transcription), + values=list(transcription_lang.keys()), + command=self.optionmenu_input_mic_voice_language_callback, + variable=StringVar(value=config.INPUT_MIC_VOICE_LANGUAGE), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_input_mic_voice_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown input mic voice language + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_input_voice_language = CTkScrollableDropdown( + self.optionmenu_input_mic_voice_language, + values=list(transcription_lang.keys()), + justify="left", + button_color="transparent", + command=self.optionmenu_input_mic_voice_language_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_input_voice_language.bind( + "", + lambda e: self.scrollableDropdown_input_voice_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_voice_language.frame._parent_frame)) else None, + ) + + ## slider input mic energy threshold + row +=1 + self.label_input_mic_energy_threshold = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_mic_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + + self.slider_input_mic_energy_threshold = CTkSlider( + self.tabview_config.tab(config_tab_title_transcription), + from_=0, + to=config.MAX_MIC_ENERGY_THRESHOLD, + border_width=7, + button_length=0, + button_corner_radius=3, + number_of_steps=config.MAX_MIC_ENERGY_THRESHOLD, + command=self.slider_input_mic_energy_threshold_callback, + variable=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), + ) + self.slider_input_mic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=0, pady=5, sticky="nsew") + + ## progressBar input mic energy threshold + row +=1 + self.checkbox_input_mic_threshold_check = CTkCheckBox( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + onvalue=True, + offvalue=False, + command=self.checkbox_input_mic_threshold_check_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + self.checkbox_input_mic_threshold_check.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + + self.progressBar_input_mic_energy_threshold = CTkProgressBar( + self.tabview_config.tab(config_tab_title_transcription), + corner_radius=0 + ) + self.progressBar_input_mic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=5, sticky="nsew") + self.progressBar_input_mic_energy_threshold.set(0) + + ## checkbox input mic dynamic energy threshold + row +=1 + self.label_input_mic_dynamic_energy_threshold = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_mic_dynamic_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.checkbox_input_mic_dynamic_energy_threshold = CTkCheckBox( + self.tabview_config.tab(config_tab_title_transcription), + text="", + onvalue=True, + offvalue=False, + command=self.checkbox_input_mic_dynamic_energy_threshold_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + self.checkbox_input_mic_dynamic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + if config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD is True: + self.checkbox_input_mic_dynamic_energy_threshold.select() + else: + self.checkbox_input_mic_dynamic_energy_threshold.deselect() + + ## entry input mic record timeout + row +=1 + self.label_input_mic_record_timeout = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_mic_record_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.entry_input_mic_record_timeout = CTkEntry( + self.tabview_config.tab(config_tab_title_transcription), + textvariable=StringVar(value=config.INPUT_MIC_RECORD_TIMEOUT), + font=CTkFont(family=config.FONT_FAMILY) + ) + self.entry_input_mic_record_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + self.entry_input_mic_record_timeout.bind("", self.entry_input_mic_record_timeout_callback) + + ## entry input mic phrase timeout + row +=1 + self.label_input_mic_phrase_timeout = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_mic_phrase_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.entry_input_mic_phrase_timeout = CTkEntry( + self.tabview_config.tab(config_tab_title_transcription), + textvariable=StringVar(value=config.INPUT_MIC_PHRASE_TIMEOUT), + font=CTkFont(family=config.FONT_FAMILY) + ) + self.entry_input_mic_phrase_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + self.entry_input_mic_phrase_timeout.bind("", self.entry_input_mic_phrase_timeout_callback) + + ## entry input mic max phrases + row +=1 + self.label_input_mic_max_phrases = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_mic_max_phrases.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.entry_input_mic_max_phrases = CTkEntry( + self.tabview_config.tab(config_tab_title_transcription), + textvariable=StringVar(value=config.INPUT_MIC_MAX_PHRASES), + font=CTkFont(family=config.FONT_FAMILY) + ) + self.entry_input_mic_max_phrases.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + self.entry_input_mic_max_phrases.bind("", self.entry_input_mic_max_phrases_callback) + + ## entry input mic word filter + row +=1 + self.label_input_mic_word_filter = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_mic_word_filter.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + if len(config.INPUT_MIC_WORD_FILTER) > 0: + textvariable=StringVar(value=",".join(config.INPUT_MIC_WORD_FILTER)) + else: + textvariable=None + self.entry_input_mic_word_filter = CTkEntry( + self.tabview_config.tab(config_tab_title_transcription), + textvariable=textvariable, + placeholder_text="AAA,BBB,CCC", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.entry_input_mic_word_filter.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + self.entry_input_mic_word_filter.bind("", self.entry_input_mic_word_filters_callback) + + ## optionmenu input speaker device + row +=1 + self.label_input_speaker_device = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_speaker_device.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.optionmenu_input_speaker_device = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_transcription), + values=model.getListOutputDevice(), + command=self.optionmenu_input_speaker_device_callback, + variable=StringVar(value=config.CHOICE_SPEAKER_DEVICE), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_input_speaker_device.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown input speaker device + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_input_speaker_device = CTkScrollableDropdown( + self.optionmenu_input_speaker_device, + values=model.getListOutputDevice(), + justify="left", + button_color="transparent", + command=self.optionmenu_input_speaker_device_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_input_speaker_device.bind( + "", + lambda e: self.scrollableDropdown_input_speaker_device._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_speaker_device.frame._parent_frame)) else None, + ) + + ## optionmenu input speaker voice language + row +=1 + self.label_input_speaker_voice_language = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_speaker_voice_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.optionmenu_input_speaker_voice_language = CTkOptionMenu( + self.tabview_config.tab(config_tab_title_transcription), + values=list(transcription_lang.keys()), + command=self.optionmenu_input_speaker_voice_language_callback, + variable=StringVar(value=config.INPUT_SPEAKER_VOICE_LANGUAGE), + font=CTkFont(family=config.FONT_FAMILY), + dropdown_font=CTkFont(family=config.FONT_FAMILY), + ) + self.optionmenu_input_speaker_voice_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + + ## scrollableDropdown input speaker voice language + if SCROLLABLE_DROPDOWN: + self.scrollableDropdown_input_speaker_voice_language = CTkScrollableDropdown( + self.optionmenu_input_speaker_voice_language, + values=list(transcription_lang.keys()), + justify="left", + button_color="transparent", + command=self.optionmenu_input_speaker_voice_language_callback, + font=CTkFont(family=config.FONT_FAMILY), + ) + self.scrollableDropdown_input_speaker_voice_language.bind( + "", + lambda e: self.scrollableDropdown_input_speaker_voice_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_speaker_voice_language.frame._parent_frame)) else None, + ) + + ## entry input speaker energy threshold + row +=1 + self.label_input_speaker_energy_threshold = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_speaker_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + + ## progressBar input speaker energy threshold + self.slider_input_speaker_energy_threshold = CTkSlider( + self.tabview_config.tab(config_tab_title_transcription), + from_=0, + to=config.MAX_SPEAKER_ENERGY_THRESHOLD, + border_width=7, + button_length=0, + button_corner_radius=3, + number_of_steps=config.MAX_SPEAKER_ENERGY_THRESHOLD, + command=self.slider_input_speaker_energy_threshold_callback, + variable=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), + ) + self.slider_input_speaker_energy_threshold.grid(row=row, column=1, columnspan=1, padx=0, pady=5, sticky="nsew") + + ## progressBar input speaker energy threshold + row +=1 + self.checkbox_input_speaker_threshold_check = CTkCheckBox( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + onvalue=True, + offvalue=False, + command=self.checkbox_input_speaker_threshold_check_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + self.checkbox_input_speaker_threshold_check.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + + self.progressBar_input_speaker_energy_threshold = CTkProgressBar( + self.tabview_config.tab(config_tab_title_transcription), + corner_radius=0 + ) + self.progressBar_input_speaker_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=5, sticky="nsew") + self.progressBar_input_speaker_energy_threshold.set(0) + + ## checkbox input speaker dynamic energy threshold + row +=1 + self.label_input_speaker_dynamic_energy_threshold = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_speaker_dynamic_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.checkbox_input_speaker_dynamic_energy_threshold = CTkCheckBox( + self.tabview_config.tab(config_tab_title_transcription), + text="", + onvalue=True, + offvalue=False, + command=self.checkbox_input_speaker_dynamic_energy_threshold_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + self.checkbox_input_speaker_dynamic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + if config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD is True: + self.checkbox_input_speaker_dynamic_energy_threshold.select() + else: + self.checkbox_input_speaker_dynamic_energy_threshold.deselect() + + ## entry input speaker record timeout + row +=1 + self.label_input_speaker_record_timeout = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_speaker_record_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.entry_input_speaker_record_timeout = CTkEntry( + self.tabview_config.tab(config_tab_title_transcription), + textvariable=StringVar(value=config.INPUT_SPEAKER_RECORD_TIMEOUT), + font=CTkFont(family=config.FONT_FAMILY) + ) + self.entry_input_speaker_record_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + self.entry_input_speaker_record_timeout.bind("", self.entry_input_speaker_record_timeout_callback) + + ## entry input speaker phrase timeout + row +=1 + self.label_input_speaker_phrase_timeout = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_speaker_phrase_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.entry_input_speaker_phrase_timeout = CTkEntry( + self.tabview_config.tab(config_tab_title_transcription), + textvariable=StringVar(value=config.INPUT_SPEAKER_PHRASE_TIMEOUT), + font=CTkFont(family=config.FONT_FAMILY) + ) + self.entry_input_speaker_phrase_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + self.entry_input_speaker_phrase_timeout.bind("", self.entry_input_speaker_phrase_timeout_callback) + + ## entry input speaker max phrases + row +=1 + self.label_input_speaker_max_phrases = CTkLabel( + self.tabview_config.tab(config_tab_title_transcription), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_input_speaker_max_phrases.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.entry_input_speaker_max_phrases = CTkEntry( + self.tabview_config.tab(config_tab_title_transcription), + textvariable=StringVar(value=config.INPUT_SPEAKER_MAX_PHRASES), + font=CTkFont(family=config.FONT_FAMILY) + ) + self.entry_input_speaker_max_phrases.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + self.entry_input_speaker_max_phrases.bind("", self.entry_input_speaker_max_phrases_callback) + + # tab Parameter + ## entry ip address + row = 0 + padx = 5 + pady = 1 + self.label_ip_address = CTkLabel( + self.tabview_config.tab(config_tab_title_parameter), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_ip_address.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.entry_ip_address = CTkEntry( + self.tabview_config.tab(config_tab_title_parameter), + textvariable=StringVar(value=config.OSC_IP_ADDRESS), + font=CTkFont(family=config.FONT_FAMILY) + ) + self.entry_ip_address.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + self.entry_ip_address.bind("", self.entry_ip_address_callback) + + ## entry port + row +=1 + self.label_port = CTkLabel( + self.tabview_config.tab(config_tab_title_parameter), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_port.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.entry_port = CTkEntry( + self.tabview_config.tab(config_tab_title_parameter), + textvariable=StringVar(value=config.OSC_PORT), + font=CTkFont(family=config.FONT_FAMILY) + ) + self.entry_port.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + self.entry_port.bind("", self.entry_port_callback) + + ## entry authkey + row +=1 + self.label_authkey = CTkLabel( + self.tabview_config.tab(config_tab_title_parameter), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_authkey.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.entry_authkey = CTkEntry( + self.tabview_config.tab(config_tab_title_parameter), + textvariable=StringVar(value=config.AUTH_KEYS["DeepL(auth)"]), + font=CTkFont(family=config.FONT_FAMILY) + ) + self.entry_authkey.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + self.entry_authkey.bind("", self.entry_authkey_callback) + + ## entry message format + row +=1 + self.label_message_format = CTkLabel( + self.tabview_config.tab(config_tab_title_parameter), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_message_format.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.entry_message_format = CTkEntry( + self.tabview_config.tab(config_tab_title_parameter), + textvariable=StringVar(value=config.MESSAGE_FORMAT), + font=CTkFont(family=config.FONT_FAMILY) + ) + self.entry_message_format.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + self.entry_message_format.bind("", self.entry_message_format_callback) + + # tab Others + ## checkbox auto clear chat box + row = 0 + self.label_checkbox_auto_clear_chatbox = CTkLabel( + self.tabview_config.tab(config_tab_title_others), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_checkbox_auto_clear_chatbox.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.checkbox_auto_clear_chatbox = CTkCheckBox( + self.tabview_config.tab(config_tab_title_others), + text="", + onvalue=True, + offvalue=False, + command=self.checkbox_auto_clear_chatbox_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + self.checkbox_auto_clear_chatbox.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + if config.ENABLE_AUTO_CLEAR_CHATBOX is True: + self.checkbox_auto_clear_chatbox.select() + else: + self.checkbox_auto_clear_chatbox.deselect() + + # checkbox notice xsoverlay + row += 1 + self.label_checkbox_notice_xsoverlay = CTkLabel( + self.tabview_config.tab(config_tab_title_others), + text=init_lang_text, + fg_color="transparent", + font=CTkFont(family=config.FONT_FAMILY) + ) + self.label_checkbox_notice_xsoverlay.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") + self.checkbox_notice_xsoverlay = CTkCheckBox( + self.tabview_config.tab(config_tab_title_others), + text="", + onvalue=True, + offvalue=False, + command=self.checkbox_notice_xsoverlay_callback, + font=CTkFont(family=config.FONT_FAMILY) + ) + self.checkbox_notice_xsoverlay.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") + if config.ENABLE_NOTICE_XSOVERLAY is True: + self.checkbox_notice_xsoverlay.select() + else: + self.checkbox_notice_xsoverlay.deselect() + widget_config_window_label_setter(self, language_yaml_data) \ No newline at end of file diff --git a/test_model/window_information.py b/test_model/window_information.py new file mode 100644 index 00000000..cc8d8792 --- /dev/null +++ b/test_model/window_information.py @@ -0,0 +1,161 @@ +import os +from customtkinter import CTkToplevel, CTkTextbox, CTkFont +from config import config + +class ToplevelWindowInformation(CTkToplevel): + def __init__(self, parent, *args, **kwargs): + super().__init__(parent, *args, **kwargs) + self.withdraw() + self.parent = parent + self.grid_columnconfigure(0, weight=1) + self.grid_rowconfigure(0, weight=1) + # self.geometry(f"{500}x{300}") + self.minsize(500, 300) + + self.after(200, lambda: self.iconbitmap(os.path.join(os.path.dirname(__file__), "img", "app.ico"))) + self.title("Information") + # create textbox information + self.textbox_information = CTkTextbox( + self, + font=CTkFont(family=config.FONT_FAMILY) + ) + self.textbox_information.grid(row=0, column=0, padx=(10, 10), pady=(10, 10), sticky="nsew") + textbox_information_message = """VRCT(v1.3.2) + +# 概要 +VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツールになります。 +翻訳エンジンを使用してメッセージとその翻訳部分を同時に送信することができます。 +(翻訳エンジンはDeepL,Google,Bingに対応) + +# 使用方法 + 初期設定時 + 0. VRChatのOSCを有効にする(重要) + + (任意) + 1. DeepLのAPIを使用するためにアカウント登録し、認証キーを取得する + 2. ギアアイコンのボタンでconfigウィンドウを開く + 3. ParameterタブのDeepL Auth Keyに認証キーを記載 + 4. configウィンドウを閉じる + + 通常使用時 + 1. メッセージボックスにメッセージを記入 + 2. Enterキーを押し、メッセージを送信する + +# その他の設定 + translation チェックボックス: 翻訳の有効無効 + voice2chatbox チェックボックス : マイクの音声を文字起こししてチャットボックスに送信する + speaker2log チェックボックス : スピーカーの音声から文字起こししてログに表示する + foreground チェックボックス: 最前面表示の有効無効 + + テキストボックス + logタブ + すべてのログを表示 + sendタブ + 送信したメッセージを表示 + receiveタブ + 受信したメッセージを表示 + systemタブ + 機能についてのメッセージを表示 + + configウィンドウ + UIタブ + Transparency: ウィンドウの透過度の調整 + Appearance Theme: ウィンドウテーマを選択 + UI Scaling: UIサイズを調整 + Font Family: 表示フォントを選択 + UI Language: UIの表示言語を選択 + Translationタブ + Select Translator: 翻訳エンジンの変更 + Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択 + Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択 + Transcriptionタブ + Input Mic Host: マイクのホストAPIを選択 + Input Mic Device: マイクを選択 + Input Mic Voice Language: 入力する音声の言語 + Input Mic Energy Threshold: 音声取得のしきい値 + Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化 + Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整 + Input Mic Record Timeout: 音声の区切りの無音時間 + Input Mic Phase Timeout: 文字起こしする音声時間の上限 + Input Mic Max Phrases: 保留する単語の上限 + Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC) + Input Speaker Device: スピーカーを選択 + Input Speaker Voice Language: 受信する音声の言語 + Input Speaker Energy Threshold: 音声取得のしきい値 + Check threshold point: Input Speaker Energy Thresholdのしきい値を視覚化 + Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整 + Input Speaker Record Timeout: 音声の区切りの無音時間 + Input Speaker Phase Timeout: 文字起こしする音声時間の上限 + Input Speaker Max Phrases: 保留する単語の上限 + Parameterタブ + OSC IP address: 変更不要 + OSC port: 変更不要 + DeepL Auth key: DeepLの認証キーの設定 + Message Format: 送信するメッセージのデコレーションの設定 + [message]がメッセージボックスに記入したメッセージに置換される + [translation]が翻訳されたメッセージに置換される + 初期フォーマット:"[message]([translation])" + Othersタブ + Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする + (New!) Notification XSOverlay: XSOverlayの通知機能を有効(VR only) + + 設定の初期化 + config.jsonを削除 + +# お問い合わせ +要望などはTwitterまで +https://twitter.com/misya_ai + +# アップデート履歴 +[2023-05-29: v0.1b] v0.1b リリース +[2023-05-30: v0.2b] +- configボタンをギアアイコンに変更 +- 詳細情報のボタンを追加 +- 翻訳機能有効無効のチェックボックスを追加 +- 最前面表示の有効無効のチェックボックスを追加 +- いくつかのバグを修正 +[2023-06-03: v0.3b] +- 全体的にUIを刷新 +- 透過機能を追加 +- テーマのLight/Dark/Systemのモードの変更機能を追加 +- UIのスケール変更機能を追加 +- フォントの変更機能を追加 +[2023-06-06: v0.4b] +- 翻訳エンジンを追加 +- 入力と出力の翻訳言語を選択できるように変更 +[2023-06-20: v1.0] +- マイクからの音声の文字起こし機能を追加 +- スピーカーからの音声の文字起こし機能を追加 +[2023-06-28: v1.1] +- いくつかのバクを修正 +- 翻訳/文字起こし言語の表記を略称からわかりやすい文字に変更 +- 文字起こしの処理の軽量化 +[2023-07-05: v1.2] +- 文字起こし精度の向上 +[2023-07-21: v1.3] +- UIの表示言語を日本語/英語を選択できる機能を追加 +- Energy Thresholdの視覚化機能を追加 +- 文字起こしの誤認識対策のため、Word Filterを追加 +- WASAPI以外のHostAPIでもマイクとして使用できるようにHostAPIを選択できる機能を追加 +- メッセージ送信後に書き込んだメッセージを空にするか選択できる機能を追加 +- バグ対策のため、translation/voice2chatbox/speaker2log/foregroundは起動時はOFFになるように変更 +- バグ対策のため、スピーカーについて既定デバイス以外を選択した時にERRORが出るように変更 +- 半角入力時に一部の文字が書き込めないバグを修正 +[2023-07-22: v1.3.1] +- UIの表示言語選択に韓国語を追加 +[2023-07-30: v1.3.2] +- 試験的にXSOverlayへの通知機能を追加 +- checkbox ONの状態でもConfigを開けるように変更 +- 文字起こし言語の表示を修正 +- いくつかのバグを修正 + +# 注意事項 +再配布とかはやめてね +""" + + self.textbox_information.insert("end", textbox_information_message) + self.textbox_information.configure(state='disabled') + self.protocol("WM_DELETE_WINDOW", self.delete_window) + + def delete_window(self): + self.withdraw() \ No newline at end of file From 644ce9afa4b7af9151a04842076d6903fc40253f Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 27 Aug 2023 04:56:36 +0900 Subject: [PATCH 021/355] [Add] New GUI from Shiina --- VRCT.py | 499 -------------- ctk_scrollable_dropdown.py | 47 +- img/arrow_left_disabled.png | Bin 0 -> 974 bytes img/arrow_left_white.png | Bin 0 -> 969 bytes img/configuration_icon_disabled.png | Bin 0 -> 2069 bytes img/configuration_icon_white.png | Bin 0 -> 1688 bytes img/foreground_icon_disabled.png | Bin 0 -> 232 bytes img/foreground_icon_white.png | Bin 0 -> 227 bytes img/headphones_icon_disabled.png | Bin 0 -> 1328 bytes img/headphones_icon_white.png | Bin 0 -> 1171 bytes img/help_icon_disabled.png | Bin 0 -> 1715 bytes img/help_icon_white.png | Bin 0 -> 1595 bytes img/mic_icon_disabled.png | Bin 0 -> 1108 bytes img/mic_icon_white.png | Bin 0 -> 952 bytes img/narrow_arrow_down.png | Bin 0 -> 785 bytes img/translation_icon_disabled.png | Bin 0 -> 1440 bytes img/translation_icon_white.png | Bin 0 -> 1237 bytes img/vrct_logo_for_dark_mode.png | Bin 0 -> 7087 bytes img/vrct_logo_for_light_mode.png | Bin 0 -> 7664 bytes img/vrct_logo_mark_black.png | Bin 0 -> 1018 bytes img/vrct_logo_mark_white.png | Bin 0 -> 898 bytes main.py | 8 + utils.py | 108 +-- vrct_gui/__init__.py | 1 + vrct_gui/_changeMainWindowWidgetsStatus.py | 148 +++++ vrct_gui/_printToTextbox.py | 37 ++ vrct_gui/config_window/ConfigWindow.py | 36 + vrct_gui/config_window/__init__.py | 1 + vrct_gui/config_window/widgets/__init__.py | 4 + .../widgets/createConfigWindowTitle.py | 39 ++ .../widgets/createSettingBoxTitle.py | 20 + .../__init__.py | 1 + .../addConfigSideMenuItem.py | 109 +++ .../createSettingBoxContainer.py | 61 ++ .../createSideMenuAndSettingsBoxContainers.py | 149 +++++ .../SettingBoxGenerator.py | 494 ++++++++++++++ .../setting_box_containers/__init__.py | 1 + .../setting_box_general/__init__.py | 1 + .../createSettingBox_General.py | 225 +++++++ vrct_gui/main_window/__init__.py | 1 + .../main_window/createMainWindowWidgets.py | 93 +++ vrct_gui/main_window/widgets/__init__.py | 4 + .../widgets/create_entry_message_box.py | 21 + .../widgets/create_minimize_sidebar_button.py | 58 ++ .../main_window/widgets/create_sidebar.py | 622 ++++++++++++++++++ .../main_window/widgets/create_textbox.py | 177 +++++ vrct_gui/ui_managers/ColorThemeManager.py | 360 ++++++++++ vrct_gui/ui_managers/ImageFilenameManager.py | 54 ++ vrct_gui/ui_managers/UiScalingManager.py | 184 ++++++ vrct_gui/ui_managers/__init__.py | 3 + vrct_gui/ui_utils/__init__.py | 1 + vrct_gui/ui_utils/ui_utils.py | 150 +++++ vrct_gui/vrct_gui.py | 107 +++ window_help_and_info.py | 154 +++++ 54 files changed, 3364 insertions(+), 614 deletions(-) delete mode 100644 VRCT.py create mode 100644 img/arrow_left_disabled.png create mode 100644 img/arrow_left_white.png create mode 100644 img/configuration_icon_disabled.png create mode 100644 img/configuration_icon_white.png create mode 100644 img/foreground_icon_disabled.png create mode 100644 img/foreground_icon_white.png create mode 100644 img/headphones_icon_disabled.png create mode 100644 img/headphones_icon_white.png create mode 100644 img/help_icon_disabled.png create mode 100644 img/help_icon_white.png create mode 100644 img/mic_icon_disabled.png create mode 100644 img/mic_icon_white.png create mode 100644 img/narrow_arrow_down.png create mode 100644 img/translation_icon_disabled.png create mode 100644 img/translation_icon_white.png create mode 100644 img/vrct_logo_for_dark_mode.png create mode 100644 img/vrct_logo_for_light_mode.png create mode 100644 img/vrct_logo_mark_black.png create mode 100644 img/vrct_logo_mark_white.png create mode 100644 main.py create mode 100644 vrct_gui/__init__.py create mode 100644 vrct_gui/_changeMainWindowWidgetsStatus.py create mode 100644 vrct_gui/_printToTextbox.py create mode 100644 vrct_gui/config_window/ConfigWindow.py create mode 100644 vrct_gui/config_window/__init__.py create mode 100644 vrct_gui/config_window/widgets/__init__.py create mode 100644 vrct_gui/config_window/widgets/createConfigWindowTitle.py create mode 100644 vrct_gui/config_window/widgets/createSettingBoxTitle.py create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/__init__.py create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/addConfigSideMenuItem.py create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSettingBoxContainer.py create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/__init__.py create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/createSettingBox_General.py create mode 100644 vrct_gui/main_window/__init__.py create mode 100644 vrct_gui/main_window/createMainWindowWidgets.py create mode 100644 vrct_gui/main_window/widgets/__init__.py create mode 100644 vrct_gui/main_window/widgets/create_entry_message_box.py create mode 100644 vrct_gui/main_window/widgets/create_minimize_sidebar_button.py create mode 100644 vrct_gui/main_window/widgets/create_sidebar.py create mode 100644 vrct_gui/main_window/widgets/create_textbox.py create mode 100644 vrct_gui/ui_managers/ColorThemeManager.py create mode 100644 vrct_gui/ui_managers/ImageFilenameManager.py create mode 100644 vrct_gui/ui_managers/UiScalingManager.py create mode 100644 vrct_gui/ui_managers/__init__.py create mode 100644 vrct_gui/ui_utils/__init__.py create mode 100644 vrct_gui/ui_utils/ui_utils.py create mode 100644 vrct_gui/vrct_gui.py create mode 100644 window_help_and_info.py diff --git a/VRCT.py b/VRCT.py deleted file mode 100644 index ff7e41c8..00000000 --- a/VRCT.py +++ /dev/null @@ -1,499 +0,0 @@ -from time import sleep -from os import path as os_path - -import customtkinter -from customtkinter import CTk, CTkFrame, CTkCheckBox, CTkFont, CTkButton, CTkImage, CTkTabview, CTkTextbox, CTkEntry -from PIL.Image import open as Image_open - -from threading import Thread -from utils import print_textbox, get_localized_text, widget_main_window_label_setter -from window_config import ToplevelWindowConfig -from window_information import ToplevelWindowInformation -from config import config -from model import model - -class App(CTk): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - ## set UI theme - customtkinter.set_appearance_mode(config.APPEARANCE_THEME) - customtkinter.set_default_color_theme("blue") - - # init main window - self.iconbitmap(os_path.join(os_path.dirname(__file__), "img", "app.ico")) - self.title("VRCT") - self.geometry(f"{400}x{175}") - self.minsize(400, 175) - self.grid_columnconfigure(1, weight=1) - self.grid_rowconfigure(0, weight=1) - self.wm_attributes("-alpha", config.TRANSPARENCY/100) - customtkinter.set_widget_scaling(int(config.UI_SCALING.replace("%", "")) / 100) - self.protocol("WM_DELETE_WINDOW", self.delete_window) - - # add sidebar - self.add_sidebar() - - # add entry message box - self.entry_message_box = CTkEntry( - self, - placeholder_text="message", - font=CTkFont(family=config.FONT_FAMILY), - ) - self.entry_message_box.grid(row=1, column=1, columnspan=2, padx=5, pady=(5, 10), sticky="nsew") - self.entry_message_box.bind("", self.entry_message_box_press_key_enter) - self.entry_message_box.bind("", self.entry_message_box_press_key_any) - self.entry_message_box.bind("", self.entry_message_box_leave) - - # add tabview textbox - self.add_tabview_logs(get_localized_text(f"{config.UI_LANGUAGE}")) - - self.config_window = ToplevelWindowConfig(self) - self.information_window = ToplevelWindowInformation(self) - self.init_process() - - def init_process(self): - # set translator - if model.authenticationTranslator() is False: - # error update Auth key - self.printLogAuthenticationError() - - # set word filter - model.addKeywords() - - # check OSC started - model.checkOSCStarted() - - # check Software Updated - model.checkSoftwareUpdated() - - def button_config_callback(self): - self.foreground_stop() - self.transcription_stop() - self.checkbox_translation.configure(state="disabled") - self.checkbox_transcription_send.configure(state="disabled") - self.checkbox_transcription_receive.configure(state="disabled") - self.checkbox_foreground.configure(state="disabled") - self.tabview_logs.configure(state="disabled") - self.textbox_message_log.configure(state="disabled") - self.textbox_message_send_log.configure(state="disabled") - self.textbox_message_receive_log.configure(state="disabled") - self.textbox_message_system_log.configure(state="disabled") - self.entry_message_box.configure(state="disabled") - self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) - self.button_information.configure(state="disabled", fg_color=["gray92", "gray14"]) - self.config_window.deiconify() - self.config_window.focus_set() - self.config_window.focus() - self.config_window.grab_set() - - def button_information_callback(self): - self.information_window.deiconify() - self.information_window.focus_set() - self.information_window.focus() - - def checkbox_translation_callback(self): - config.ENABLE_TRANSLATION = self.checkbox_translation.get() - if config.ENABLE_TRANSLATION is True: - self.printLogStartTranslation() - else: - self.printLogStopTranslation() - - def transcription_send_start(self): - model.startMicTranscript(self.sendMicMessage) - self.printLogStartVoice2chatbox() - self.checkbox_transcription_send.configure(state="normal") - self.checkbox_transcription_receive.configure(state="normal") - self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) - - def transcription_send_stop(self): - model.stopMicTranscript() - self.printLogStopVoice2chatbox() - self.checkbox_transcription_send.configure(state="normal") - self.checkbox_transcription_receive.configure(state="normal") - self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) - - def transcription_send_stop_for_config(self): - model.stopMicTranscript() - self.printLogStopVoice2chatbox() - - def checkbox_transcription_send_callback(self): - config.ENABLE_TRANSCRIPTION_SEND = self.checkbox_transcription_send.get() - self.checkbox_transcription_send.configure(state="disabled") - self.checkbox_transcription_receive.configure(state="disabled") - self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) - if config.ENABLE_TRANSCRIPTION_SEND is True: - th_transcription_send_start = Thread(target=self.transcription_send_start) - th_transcription_send_start.daemon = True - th_transcription_send_start.start() - else: - th_transcription_send_stop = Thread(target=self.transcription_send_stop) - th_transcription_send_stop.daemon = True - th_transcription_send_stop.start() - - def transcription_receive_start(self): - model.startSpeakerTranscript(self.receiveSpeakerMessage) - self.printLogStartSpeaker2log() - self.checkbox_transcription_send.configure(state="normal") - self.checkbox_transcription_receive.configure(state="normal") - self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) - - def transcription_receive_stop(self): - model.stopSpeakerTranscript() - self.printLogStopSpeaker2log() - self.checkbox_transcription_send.configure(state="normal") - self.checkbox_transcription_receive.configure(state="normal") - self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) - - def transcription_receive_stop_for_config(self): - model.stopSpeakerTranscript() - self.printLogStopSpeaker2log() - - def checkbox_transcription_receive_callback(self): - config.ENABLE_TRANSCRIPTION_RECEIVE = self.checkbox_transcription_receive.get() - self.checkbox_transcription_send.configure(state="disabled") - self.checkbox_transcription_receive.configure(state="disabled") - self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - th_transcription_receive_start = Thread(target=self.transcription_receive_start) - th_transcription_receive_start.daemon = True - th_transcription_receive_start.start() - else: - th_transcription_receive_stop = Thread(target=self.transcription_receive_stop) - th_transcription_receive_stop.daemon = True - th_transcription_receive_stop.start() - - def transcription_start(self): - if config.ENABLE_TRANSCRIPTION_SEND is True: - th_transcription_send_start = Thread(target=self.transcription_send_start) - th_transcription_send_start.daemon = True - th_transcription_send_start.start() - sleep(2) - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - th_transcription_receive_start = Thread(target=self.transcription_receive_start) - th_transcription_receive_start.daemon = True - th_transcription_receive_start.start() - - def transcription_stop(self): - if config.ENABLE_TRANSCRIPTION_SEND is True: - th_transcription_send_stop = Thread(target=self.transcription_send_stop_for_config) - th_transcription_send_stop.daemon = True - th_transcription_send_stop.start() - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - th_transcription_receive_stop = Thread(target=self.transcription_receive_stop_for_config) - th_transcription_receive_stop.daemon = True - th_transcription_receive_stop.start() - - def checkbox_foreground_callback(self): - config.ENABLE_FOREGROUND = self.checkbox_foreground.get() - if config.ENABLE_FOREGROUND: - self.attributes("-topmost", True) - self.printLogStartForeground() - else: - self.attributes("-topmost", False) - self.printLogStopForeground() - - def foreground_start(self): - if config.ENABLE_FOREGROUND: - self.attributes("-topmost", True) - self.printLogStartForeground() - - def foreground_stop(self): - if config.ENABLE_FOREGROUND: - self.attributes("-topmost", False) - self.printLogStopForeground() - - def entry_message_box_press_key_enter(self, event): - # osc stop send typing - model.oscStopSendTyping() - - if config.ENABLE_FOREGROUND: - self.attributes("-topmost", True) - - message = self.entry_message_box.get() - self.sendChatMessage(message) - - def entry_message_box_press_key_any(self, event): - # osc start send typing - model.oscStartSendTyping() - if config.ENABLE_FOREGROUND: - self.attributes("-topmost", False) - - if event.keysym != "??": - if len(event.char) != 0 and event.keysym in config.BREAK_KEYSYM_LIST: - self.entry_message_box.insert("end", event.char) - return "break" - - def entry_message_box_leave(self, event): - # osc stop send typing - model.oscStopSendTyping() - if config.ENABLE_FOREGROUND: - self.attributes("-topmost", True) - - def delete_window(self): - self.quit() - self.destroy() - - def add_sidebar(self): - init_lang_text = "Loading..." - self.sidebar_frame = CTkFrame(master=self, corner_radius=0) - - # add checkbox translation - self.checkbox_translation = CTkCheckBox( - self.sidebar_frame, - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_translation_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add checkbox transcription send - self.checkbox_transcription_send = CTkCheckBox( - self.sidebar_frame, - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_transcription_send_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add checkbox transcription receive - self.checkbox_transcription_receive = CTkCheckBox( - self.sidebar_frame, - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_transcription_receive_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add checkbox foreground - self.checkbox_foreground = CTkCheckBox( - self.sidebar_frame, - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_foreground_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add button information - self.button_information = CTkButton( - self.sidebar_frame, - text=None, - width=36, - command=self.button_information_callback, - image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "info-icon-white.png"))) - ) - - # add button config - self.button_config = CTkButton( - self.sidebar_frame, - text=None, - width=36, - command=self.button_config_callback, - image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "config-icon-white.png"))) - ) - - self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsw") - self.sidebar_frame.grid_rowconfigure(5, weight=1) - self.checkbox_translation.grid(row=0, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") - self.checkbox_transcription_send.grid(row=1, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") - self.checkbox_transcription_receive.grid(row=2, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") - self.checkbox_foreground.grid(row=3, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") - self.button_information.grid(row=5, column=0, padx=(10, 5), pady=(5, 5), sticky="wse") - self.button_config.grid(row=5, column=1, padx=(5, 10), pady=(5, 5), sticky="wse") - - def delete_tabview_logs(self, pre_language_yaml_data): - self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_log"]) - self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_send"]) - self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_receive"]) - self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_system"]) - - def add_tabview_logs(self, language_yaml_data): - main_tab_title_log = language_yaml_data["main_tab_title_log"] - main_tab_title_send = language_yaml_data["main_tab_title_send"] - main_tab_title_receive = language_yaml_data["main_tab_title_receive"] - main_tab_title_system = language_yaml_data["main_tab_title_system"] - - # add tabview textbox - self.tabview_logs = CTkTabview(master=self) - self.tabview_logs.add(main_tab_title_log) - self.tabview_logs.add(main_tab_title_send) - self.tabview_logs.add(main_tab_title_receive) - self.tabview_logs.add(main_tab_title_system) - self.tabview_logs.grid(row=0, column=1, padx=0, pady=0, sticky="nsew") - self.tabview_logs._segmented_button.configure(font=CTkFont(family=config.FONT_FAMILY)) - self.tabview_logs._segmented_button.grid(sticky="W") - self.tabview_logs.tab(main_tab_title_log).grid_rowconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_log).grid_columnconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_send).grid_rowconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_send).grid_columnconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_receive).grid_rowconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_receive).grid_columnconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_system).grid_rowconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_system).grid_columnconfigure(0, weight=1) - self.tabview_logs.configure(fg_color="transparent") - - # add textbox message log - self.textbox_message_log = CTkTextbox( - self.tabview_logs.tab(main_tab_title_log), - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add textbox message send log - self.textbox_message_send_log = CTkTextbox( - self.tabview_logs.tab(main_tab_title_send), - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add textbox message receive log - self.textbox_message_receive_log = CTkTextbox( - self.tabview_logs.tab(main_tab_title_receive), - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add textbox message system log - self.textbox_message_system_log = CTkTextbox( - self.tabview_logs.tab(main_tab_title_system), - font=CTkFont(family=config.FONT_FAMILY) - ) - - self.textbox_message_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") - self.textbox_message_send_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") - self.textbox_message_receive_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") - self.textbox_message_system_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") - self.textbox_message_log.configure(state='disabled') - self.textbox_message_send_log.configure(state='disabled') - self.textbox_message_receive_log.configure(state='disabled') - self.textbox_message_system_log.configure(state='disabled') - - widget_main_window_label_setter(self, language_yaml_data) - - def printLogAuthenticationError(self): - print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") - print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") - - def printLogStartTranslation(self): - print_textbox(self.textbox_message_log, "Start translation", "INFO") - print_textbox(self.textbox_message_system_log, "Start translation", "INFO") - - def printLogStopTranslation(self): - print_textbox(self.textbox_message_log, "Stop translation", "INFO") - print_textbox(self.textbox_message_system_log, "Stop translation", "INFO") - - def printLogStartVoice2chatbox(self): - print_textbox(self.textbox_message_log, "Start voice2chatbox", "INFO") - print_textbox(self.textbox_message_system_log, "Start voice2chatbox", "INFO") - - def printLogStopVoice2chatbox(self): - print_textbox(self.textbox_message_log, "Stop voice2chatbox", "INFO") - print_textbox(self.textbox_message_system_log, "Stop voice2chatbox", "INFO") - - def printLogStartSpeaker2log(self): - print_textbox(self.textbox_message_log, "Start speaker2log", "INFO") - print_textbox(self.textbox_message_system_log, "Start speaker2log", "INFO") - - def printLogStopSpeaker2log(self): - print_textbox(self.textbox_message_log, "Stop speaker2log", "INFO") - print_textbox(self.textbox_message_system_log, "Stop speaker2log", "INFO") - - def printLogStartForeground(self): - print_textbox(self.textbox_message_log, "Start foreground", "INFO") - print_textbox(self.textbox_message_system_log, "Start foreground", "INFO") - - def printLogStopForeground(self): - print_textbox(self.textbox_message_log, "Stop foreground", "INFO") - print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO") - - def printLogDetectWordFilter(self, message): - print_textbox(self.textbox_message_log, f"Detect WordFilter :{message}", "INFO") - print_textbox(self.textbox_message_system_log, f"Detect WordFilter :{message}", "INFO") - - def printLogOSCError(self): - print_textbox(self.textbox_message_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") - print_textbox(self.textbox_message_system_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") - - def printLogSendMessage(self, message): - print_textbox(self.textbox_message_log, f"{message}", "SEND") - print_textbox(self.textbox_message_send_log, f"{message}", "SEND") - - def printLogReceiveMessage(self, message): - print_textbox(self.textbox_message_log, f"{message}", "RECEIVE") - print_textbox(self.textbox_message_receive_log, f"{message}", "RECEIVE") - - def sendChatMessage(self, message): - if len(message) > 0: - # translate - if config.ENABLE_TRANSLATION is False: - chat_message = f"{message}" - elif model.getTranslatorStatus() is False: - self.printLogAuthenticationError() - chat_message = f"{message}" - else: - chat_message = model.getInputTranslate(message) - - # send OSC message - if config.ENABLE_OSC is True: - model.oscSendMessage(chat_message) - else: - self.printLogOSCError() - - # update textbox message log - self.printLogSendMessage(chat_message) - - # delete message in entry message box - if config.ENABLE_AUTO_CLEAR_CHATBOX is True: - self.entry_message_box.delete(0, customtkinter.END) - - def sendMicMessage(self, message): - if len(message) > 0: - # word filter - if model.checkKeywords(message): - self.printLogDetectWordFilter(message) - return - - # translate - if config.ENABLE_TRANSLATION is False: - voice_message = f"{message}" - elif model.getTranslatorStatus() is False: - self.printLogAuthenticationError() - voice_message = f"{message}" - else: - voice_message = model.getInputTranslate(message) - - if config.ENABLE_TRANSCRIPTION_SEND is True: - if config.ENABLE_OSC is True: - # osc send message - model.oscSendMessage(voice_message) - else: - self.printLogOSCError() - # update textbox message log - self.printLogSendMessage(voice_message) - - def receiveSpeakerMessage(self, message): - if len(message) > 0: - # translate - if config.ENABLE_TRANSLATION is False: - voice_message = f"{message}" - elif model.getTranslatorStatus() is False: - self.printLogAuthenticationError() - voice_message = f"{message}" - else: - voice_message = model.getOutputTranslate(message) - - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - # update textbox message receive log - self.printLogReceiveMessage(voice_message) - if config.ENABLE_NOTICE_XSOVERLAY is True: - model.notificationXsoverlay(voice_message) - -if __name__ == "__main__": - try: - app = App() - app.mainloop() - except Exception as e: - import traceback - with open('error.log', 'a') as f: - traceback.print_exc(file=f) \ No newline at end of file diff --git a/ctk_scrollable_dropdown.py b/ctk_scrollable_dropdown.py index 81b98205..f0f48c3e 100644 --- a/ctk_scrollable_dropdown.py +++ b/ctk_scrollable_dropdown.py @@ -15,19 +15,25 @@ import time 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, + fg_color=None, max_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): + command=None, image_values=[], alpha: float = 0.97, double_click=False, + resize=True, frame_border_color=None, text_color=None, autocomplete=False, + max_height: int = None, button_pady: int = 0, min_show_button_num: int = 1, + button_corner_radius: int = None, frame_corner_radius=0, + **button_kwargs): super().__init__(takefocus=1) self.transient(self.master) self.alpha = alpha self.attach = attach self.corner = frame_corner_radius + # とりあえずframe_corner_radiusはframe_corner_radiusの名前のまま使いたい + self.frame_corner_radius = frame_corner_radius self.padding = 0 self.focus_something = False self.disable = True + self.font_size = 12 if button_kwargs.get("font", None) is None else button_kwargs["font"]._size if sys.platform.startswith("win"): self.after(100, lambda: self.overrideredirect(True)) @@ -48,7 +54,7 @@ class CTkScrollableDropdown(customtkinter.CTkToplevel): self.hide = True self.attach.bind('', lambda e: self._withdraw() if not self.disable else None, add="+") self.attach.winfo_toplevel().bind('', lambda e: self._withdraw() if not self.disable else None, add="+") - self.attach.winfo_toplevel().bind("", lambda e: self._withdraw() if not self.disable else None, add="+") + self.attach.winfo_toplevel().bind("", lambda e: self._withdraw() if not self.disable else None, add="+") self.attach.winfo_toplevel().bind("", lambda e: self._withdraw() if not self.disable else None, add="+") self.attach.winfo_toplevel().bind("", lambda e: self._withdraw() if not self.disable else None, add="+") @@ -84,6 +90,10 @@ class CTkScrollableDropdown(customtkinter.CTkToplevel): self.autocomplete = autocomplete self.var_update = customtkinter.StringVar() self.appear = False + self.max_height = max_height + self.button_pady = button_pady + self.min_show_button_num = min_show_button_num + self.button_corner_radius = button_corner_radius if justify.lower()=="left": self.justify = "w" @@ -92,7 +102,7 @@ class CTkScrollableDropdown(customtkinter.CTkToplevel): else: self.justify = "c" - self.button_height = button_height + self.button_height = max_button_height + self.font_size self.values = values self.button_num = len(self.values) self.image_values = None if len(image_values)!=len(self.values) else image_values @@ -179,8 +189,10 @@ class CTkScrollableDropdown(customtkinter.CTkToplevel): text_color=self.text_color, image=self.image_values[i] if self.image_values is not None else None, anchor=self.justify, + corner_radius= self.button_corner_radius, command=lambda k=row: self._attach_key_press(k), **button_kwargs) - self.widgets[self.i].pack(fill="x", pady=2, padx=(self.padding, 0)) + pady = 0 if self.button_num-1 == self.i else self.button_pady + self.widgets[self.i].pack(fill="x", pady=(0, pady), padx=(self.padding, 0)) self.i+=1 self.hide = False @@ -195,12 +207,25 @@ class CTkScrollableDropdown(customtkinter.CTkToplevel): self.width_new = self.attach.winfo_width() if self.width is None else self.width if self.resize: - if self.button_num==1: - self.height_new = self.button_height * self.button_num + 45 + button_height_include_pady = self.button_height + self.button_pady + + if self.min_show_button_num < self.button_num: + min_buttons_height = button_height_include_pady * self.min_show_button_num else: - self.height_new = self.button_height * self.button_num + 35 - if self.height_new>self.height: - self.height_new = self.height + min_buttons_height = button_height_include_pady * self.button_num + # delete last one's pady px + min_buttons_height -= self.button_pady + + # minor adjustment + 5px + min_buttons_height += 5 + # adjust by frame_corner_radius + min_buttons_height += (self.frame_corner_radius * 2) + + if self.max_height: + if min_buttons_height>self.max_height: + min_buttons_height = self.max_height + + self.height_new = min_buttons_height self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, self.x_pos, self.y_pos)) diff --git a/img/arrow_left_disabled.png b/img/arrow_left_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..5b5f3afaf5b9d90597727eb57423f6781b1c51ab GIT binary patch literal 974 zcmV;<12O!GP)4h=_=2Qa4ik(TjgT>7f)UrHDw8QbdXe5yABv51R!M(UK_;@MhZX0x;{G+uP}MW}miaw*NN*+SuJc ze&8h$Z6hLb&h5w}F%SW5>>dEmd+@!|lV}G>^j$z3yBEL=5pDg`@6Nd$-vdUXuL9cG zeSd@hRie}B=%EtO#_k93jEJ@fs()sVE)I==HufNZwh(@Und@1a%t_jg`T#!xKoiks zLG@3}TyJH`g0%fgM?f3f41oX0%(W1Bq6$Yq8=C=mL`1Qm`nrGeZ$kU2L_iyR7{CK5 zyiz=xrO9%hzDl1$+Snrj@M}t`k-YGw3ut4H2Eea&;TtPO`YcUA8+#1EePNsLl~SWw z+IU;E-%V=|K#75(RX11ZR| zMzXZ=p)B}90@~Oq05^s3OUyiyrH!>R;d2XU(>?>>hNNy3MQ@mSG)o)nWy9wZ(8itv z+;t)RqEcMT(#FRs;E4iPVr|;z0e4jhzo3+=r_G5^RlySljKCA!z7POEAIEX6YIw1L zHujQp?urn8E{@}Rb8>v6YIv~#5p4=+v*VmYKj4W1-T`gwB>EP|UR~OLAu> zh9R-F=2MEfazj<777qb~P!I5YxYA_4GNC=r}quUx=O z90V{el-N*;$Fek;9~uEKvENIGj+nf}KTm1;c1G69k{8T8NkrcS)wct(xTCM9l&vgz z#mwVG^!0D`d|cgDBi>)yxgA!*X2X__=m(&Rys-1GIaNeOG;d(OT0Jg>X= z_jC6?XRo!so2kc-&E0ZD_eu_$nth!!fs zKQeP8OXD%^_>}?}>$U*6qZI#vnH%*ic@;88We1tDZYzMBL^PlXpJV1`Jxkt%&Ql41 zv2G`T>jC0N>RIx(ow-WiLdLpX0pe$wdAMEiL?y3)v2HhjYsx=nMnoD}8o%pg&eB%E zShpuY{IrOSbSj>xq!lpM?E`R0DSj%7WF)P{GiA)(@d_C0_6LZ6CnCdTiSHP|Sa%S> zMddh?A~Kw%@%ysGcLZRpI|Sf@a*PRP9?sHuwoLJD0~qTL130e?f8)RNb7hNf3&2=+ z6u>!U_-mgYe5gV^QQ%j|Sa%G-8D;n@5pHH_^06xML;)*ctUDecek_WlQMGtAfU)i* zfYZt`^MQe=nbzX@s>Q1Th-g8;nBM^^x*?t@;2rR*a7r1TM^QAI)`o%`Rh0pFh~oiU zX>1^hMr*a9O1DyQE)@DDilV5Q)`lv3ncGU{jCDrbM>-)*T2)>L!J`k!49~8EIJs&HjM2ddeqvrPKta z)x^fSy#OvNA*MaVbl(7Yh&=$VC?RG%#8lq^c!=Equ9B{;^4_Q0lYIl=Awn8kvmT5&|E~eX~yiyRReEy|IKay{I>f8FK>br__#X5g<;00000NkvXXu0mjfo0rBR literal 0 HcmV?d00001 diff --git a/img/configuration_icon_disabled.png b/img/configuration_icon_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..03a4a217a2db292605f54b8257aa9460865e5720 GIT binary patch literal 2069 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 VV!l88avA7K22WQ%mvv4FO#m;{S=s;q literal 0 HcmV?d00001 diff --git a/img/foreground_icon_white.png b/img/foreground_icon_white.png new file mode 100644 index 0000000000000000000000000000000000000000..50370ad1d3cddf36bb00cf03b86e3ae8d82418a2 GIT binary patch literal 227 zcmeAS@N?(olHy`uVBq!ia0vp^1|ZDA1|-9oezpTC&H|6fVg?4jBOuH;Rhv&5C^*&A z#W5t~-rF0FTn!2WZV!2`-}^r&Zo#&|7GAgYjo;rg>DHxP*z)Dx_NR^4qAczDpSd2t ze^xqDx%vMIhAlk7RA}Dqm|JKRR~X0t-#N1v;)NhZBn2rg1d-HgwN><`2=P+0 zm&|NzErQnewWT*GQhX3v`p{AfrEg*j)mqHVW_FX1+JHzkRw`PdA`;pnZ3!YJ1TSHC zXV3Zikd5rU*=&-r;O}iNXU^|GXJ*d%26%+O&|F+(XlPj6pS-_WYu*arIRNbd+5lc$ z=;j`PF#w|gE(xMDEsGXicyVW^_BDW$Os`N%y#erkX0$cbF<}g#ucee3)WOCjE*pB5;jX2LNWYE3HR*ZTIDQ3D9f1`?c050fcAThKMpmlxe^xthGMTYrC({ zxQ`jiN43@t0HcoVbpUB18WBQVU$SKJ-`hI3<^bS_;WNRzcki`ntvi@`3xF`GR4Mgtt#(yMz)cN?v)Sxl03IvaJ#HBCY0Hd_&9!_nPBOJ3pI5(@^*s>^ z1|N^dyDDbtsw3ceUcBrA5JDWBZ+QT)%-Ag<#KE%NtzJHFRy)6H0A_xvc$Ch%-hO#ABl9${c~T7NY@Tcz>%^5TT|)5iiKlEfNVCq z38gE{oM9N>EtLE;a;neb?|(R#^EUry0A_x^c$qR?+EfHlm#KbOl*oa*g65(or7Ee}%i8MU3%YR~g7GxK3T zg)z{~x%XoBSp{oH-{Uip1=(YfG&2Nn+xF!G$`)$z# z!$L}V9Hqtc|Am~C^0=_f=ok?l_1mUVi0G(gM#lsI7*c)!;HKYF4REvIJhhlJ&FF-b zA_8E+f1k{PaVbT_G@}y_0suh5ieHmbZXu!@ejC<;h;B$JwvM3%5(Hv)mc zy2AgMhiNp%iRe%u5LlP6VkI}Yq9*Q+cVz&4=(+=+dHH-7Gw(!EQ9s|NM5HlQ)ZYYf mm57D{hA|MYQPe-eZ~g@fapSuE2LH|g0000P)dpGb0!@#-socsIEIrpCP9k@%r$aFMv?beZ_~^XAQ4EF#NAWCO4nSl*(oXlBnk=MHxrKnP)}h@1gRO>X(h%sz0={oFxM6GB)c zB5woR$esz;&1|i6?!p}hP_0&rg+k#cU`@v5Av1f~Id`%X@r*#$i^wNrzgOqdG+o=* z*Eia%e#>`2RbOi&e>ldtx?6bw=iKQS<7yylx~x>y*IU(ZIs!?OlvVXNz&Bj_JjS@w zdq3W-d_9#)MT^DaXTXaMmqucY4|?za&Z^gR1XT5n4Ef_}n!YsM@&M&>Ii_j46FA;* zsidlJHmjF80@Z4@w@@hj2@KSCl9@g1oEx5Q`FcVKi$vs?hG!>fn%>*j*LTZGojC%k z`bkB=XiY557#G(Eu0ool_*_O=43 zR4P5d6ZK_gwy(dxf7<7LGs@+1G_!pT#T%>D>Ma|`TLLH)3hS7QmIs!NuiOEkrWsK z{s7LJ+4nKVBU!6(W&pK>{VgDCFGQ(SnwF!O76DjBaRTg%F@D&X_)n1uAv{#eyp}b4 zKu!TnFj&jfCX<}Wbs&VWLPSoqlAi-C6Oj`kgcVZ)NRq^h$Wfr2w_zKUMdWCbBz`gg zRec{=lDAn0EU7soNs=s9)r&w+J2i8D2kIF$v-?$5ceE=%$LJA}9SUs9+oTINDXkBf z<~qw2SeUm(7cA`PaCa`Ez?Hluy5Nce=kvDcg7XR-%iE$0j%keXAXAg){|lpLc2K?d z!@&N$ZSH{m&beU)h%vqkT*}+111{B^Z*Jzi_lcQp0cQR9{Q!T~fbUM4l#}r2Z0c l&dk1zF@Do5slQ9!{0kL7ntSQ+x{m+=002ovPDHLkV1hchB;^1A literal 0 HcmV?d00001 diff --git a/img/help_icon_disabled.png b/img/help_icon_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..03e1c086e9c7d467dc98d0a09e2f10550370da4f GIT binary patch literal 1715 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 P00000NkvXXu0mjfrNnKG literal 0 HcmV?d00001 diff --git a/img/translation_icon_disabled.png b/img/translation_icon_disabled.png new file mode 100644 index 0000000000000000000000000000000000000000..533feb8182515f6cbc346d21ef231fc044abfafc GIT binary patch literal 1440 zcmV;R1z-A!P)M)Q6>%LVf7Ng0)b! z+iW)3-K5a!LJ?dn6&0;k3l?8mv{exLU=hR*R&8dINxHFXwTj|PQL2KtNcEuvseLHY zhq{|)X72GJH|sRrq-ism-2;K#GiT16-#vG3?m2{@#V(X$j^peG@C1N$-VY(A+z@mo zgyT4Sx*a?KFmu1-ID3NjYXI2kR}j$&Ddj{e8Nb+xW4lmVYRH!<@{ zrPLPvzHB$G!klx^Kl}|0@%Lf0f=Y{z&U1)16WN& z-vT&CL_aQO@IdV)zM*8&skbxf)C3WgHPAbmbZP>?Y}T}Mrd7x+7C>7W001jjT<3XS z9{?z&%&ch(rIZUG3g8le>|y}4m4U2jzu|e_&j41}!I@d;;K9sKWlj6T6H_PKo{-Q0 zOv`?bnP1Yj6#x)IOb8(kYLkBexXe6Wsa$PqdkqzU8PC^(rWc6lv{v7vl-jLfUKT>^ zs@d|hr@s8Ut*nLy0KII4h?D@70YE9W4?r&w{Sb@we6V80^3SyG?Q?ULm0<$3vu4i# z7;s(pJ0fy@;AJ61dMLitHLb#+)?bm*xa7ZCO1Y-Dclm|Q4?k306aXTcVCGW*?q+5H zP(+lEL?Zi{d3n~f_bH|JYuop?4L<7n?;lAGo$p5Ab-CdZN{=OHdU|^97eZ_iLTr;# z-jzwG(yr?s@I3EVWZ>s<}?bi_z09*#}6A^tJi^V<~ zia(~7B)~$FNvC$Ux*k@I-IXoR$`$)`nfewpuMgrF&CF5l>#*ZEXRTbZ4*-NF^UvI> zP99_zO0jM>2sBK;Qfk^Rlwtz4w9Jd+ILA7H2)o8M089c{@4JwwR;!8Q*?e1$q%_uy zq!QbbeOM_Sp0x`gufiItAy);kywLOc!Nx`Q4wKf}zMeZVK1`3x1n zb-f+X?MNb;NslGpUC7R~3c?4F$lFtQjHHGxEo2+^67v7TPBt^r+v?@|&~@D%&1^#h zuyVx*eJ`krL?WLwvmH(hR*1-7Q8hhUv^=Zdieydus=tVjMx%EIQP2Rjs;pYA{yiT& zQMDTx2(-rb2k$t}vBqb0BLgoo^WiQ`%ymLUdjrovr#1FX1V1CCJQ_GaDjBZ`A+~lC uqWw-)!H)@JEiYw6!zf5K8T=T zl=M&vQ9=YQNl{Uj^icE?R1gF`Bti6ttt=BNv6qT^lOpw{CCmp|!~~a}`}^&>Y_mJR z)5H8muj8!FI(P2Q_w5|cJ#)VIoZq>>a~L5YNs>VkIUpiW0Goj#-Ju3dsOq??eqfB5 z@6ume2!OSAy@>o60`CPTRCRZ$RGQ43K$0YbQ55~z!{D!y@ZN9DIuntIJex!C0Gq>a zWeRc5od)hr>AtVm>tp3|`BFEItJUhdLZNUN*ppJ9iR1XbtO@)_>cD#MhmA2^1>ZvA z-;}Br$MLEx2}HDXo0|o1t=;CF`^#GUS|`riWU<@Mb^``FF|yWf7m>BVP8zkm&pCGv zn2h82WY$4tP5^iV*q)NN07rnYfqi{XKtz53&H@htBfzh~SrPeTC4+~?8Mr0lIDRjV z<1t_oKvmz5<9N(_Kj)nL)>^xFr3iFdLRf3J0P6un~Ry%$nxe>6|z5u)uZ07+~bxc*?3MSJuGO@oAl-1k_5XweXj}s2Dh#Ue8@O!;p|D;x{eHCmg z#bR-7zY#bI5Jk~%T)Wpqz@(}^TB%ePi^bxupr2h>SeQyb?7iPOJ3G57j$?`ASaK(z zs$;+fU>$HL;DMvw`<+1SoI4~UM}qmsLqkK0>F13x=SN0HYFP)~ggECkt!Vd_s@3XX z6h)&zQB_YFV=h^1$3^6^l-V!d`-hA%bG-_-9Xe`z<#KtRYX>-u1URLtpV#a4&nlIQ z&og-FFax)Pyk_j45Xf`J&U~QBd&Z7{iL^F~qQli{b=~cH&3nccYwgP-^6u?=@!oGV z#++};dC%BguT`#Gxw2|_c=&3c&wIx1da>1d=$G3RoO5piuLp54Wc0#K?63150hCIm zN$>quRXqrtq0zE>j;X1s2-u%e4Mb72e>pxfznd&41J>FdBJy*K`dl2xcX#SP*Q;+U zMC7TI*v$G}*4oideXJOPBuNbLa1iHI^_?Y%^(O%l*$dne#N)t6G;X>8k0eR5y59*j zr#Mvg%dmQHF3DC0g8fOrTDuw87R1DRe=*myvaVxB3oPYxjuAcOAOBxUjIWVa=K~ zi%a>>Jp-NAcEHHMz`(AX8RWW2OOj+Q@IX^m)n`=ohKpVlMb7~{QiMxXx>yO zN~O|=?d+_zk%;UF;+`Z)hKw;&?QC-`p_VV~_)>PEttLS%wroFEhJdy9e&C*KG5Rl- zvJG31V@nXf{-tCcXx;-&FEWtnjNKCa;nv^EJOka%*vo;c9?YD;N~~2fg)wGcRd>@; zN?8Z5s=JLb^F8T8J({s=z!_CN=)K?C{O8O6`FJp9C=!h@00000NkvXXu0mjf*7a0H literal 0 HcmV?d00001 diff --git a/img/vrct_logo_for_dark_mode.png b/img/vrct_logo_for_dark_mode.png new file mode 100644 index 0000000000000000000000000000000000000000..7eeaabc26f728a7991160400ead86a6a2139948b GIT binary patch literal 7087 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 F{{b%nsA&KI literal 0 HcmV?d00001 diff --git a/img/vrct_logo_for_light_mode.png b/img/vrct_logo_for_light_mode.png new file mode 100644 index 0000000000000000000000000000000000000000..75b1afd911aec2a3d791198c1fb9c6b071529e0c GIT binary patch literal 7664 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 literal 0 HcmV?d00001 diff --git a/img/vrct_logo_mark_white.png b/img/vrct_logo_mark_white.png new file mode 100644 index 0000000000000000000000000000000000000000..0a6f1968fa8e156c214edec51b9c1d9f928cc6a7 GIT binary patch literal 898 zcmeAS@N?(olHy`uVBq!ia0vp^9zg8K!3HFY9zE&@Qk(@Ik;M!Q+`=Ht$S`Y;1W=H% zILO_JVcj{Imp~3nx}&cn1H;CC?mvmF3=B+vJzX3_Dj46+>CS!RAkfDCdBTJZjeI-! zCUL#H#JEyn3DXWP1yKifcg~$$9_$abI@UAI6B6RsQ9og#kaik}PQ}~V{+srEbndoa zdwuuj-6sWZSk})TX;+Hse+b=11skW1x8Vo1x+kPDHDFy+>2R%eeH_ReUEK+ulqe& z=lo;aV=N2r2Hejvvo+bEwBC_#h*!4wiv2$Bwk2o+49Hv`iDvvmftCba)~{yF{`h>lGvT! zeRPpnDX%}9!fBvYCK=~v{A(3y$T!}jhE^T? zF9PfBKiS#OKW|@aT5$CAx(n}LT{>Pjy|>_d`{Lkz!pmUv+?#~}&dJHMU8nxD`9kP~Gy+kg21r;i)*Z^Z2vn)X07e9xCIp(CE90<&8a z<8s1IrfrV(GE@)0q{CVCZsHpDuU=Y~ueHOprnar{e`i@H%(6G=_|5s1*GuC!RJSbY z^WB&dERo4xIq!P=0`)1&^cFSnPf_Y&J!A2qRp?0I0Xdbw53j$Ud|GJxn>!iavs4$V z{B2v}c;v%}|GFKGOW4Ysd6@Y4dKapkn z-Gn1&E4&26U!Qf~y6xjq>A zL9{8sVAd>&u`8WOD#92wV!D45B<;{an^LB{Ts5jlY0+ literal 0 HcmV?d00001 diff --git a/main.py b/main.py new file mode 100644 index 00000000..ac80e5f5 --- /dev/null +++ b/main.py @@ -0,0 +1,8 @@ +from vrct_gui import vrct_gui + +class VRCT(): + def __init__(self): + pass +if __name__ == "__main__": + # vrct_gui = VRCT_GUI() + vrct_gui.start() \ No newline at end of file diff --git a/utils.py b/utils.py index 2bee5814..3033cb13 100644 --- a/utils.py +++ b/utils.py @@ -1,106 +1,6 @@ from os import path as os_path -import yaml -from datetime import datetime +from PIL.Image import open as Image_open -def print_textbox(textbox, message, tags=None): - now = datetime.now() - now = now.strftime('%H:%M:%S') - - textbox.tag_config("ERROR", foreground="#FF0000") - textbox.tag_config("INFO", foreground="#1BFF00") - textbox.tag_config("SEND", foreground="#0378e2") - textbox.tag_config("RECEIVE", foreground="#ffa500") - - textbox.configure(state='normal') - textbox.insert("end", f"[{now}][") - textbox.insert("end", f"{tags}", tags) - textbox.insert("end", f"]{message}\n") - textbox.configure(state='disabled') - textbox.see("end") - -def get_localized_text(language): - file_path = os_path.join(os_path.dirname(__file__), "locales.yml") - - with open(file_path, encoding="utf-8") as file: - languages_yaml_data = yaml.safe_load(file) - default_language = "en" - if language in languages_yaml_data: - localized_text = languages_yaml_data[language] - if default_language in languages_yaml_data: - default_text = languages_yaml_data[default_language] - merged_text = {**default_text, **localized_text} - return merged_text - else: - return localized_text - else: - return None - -def get_key_by_value(dictionary, value): - for key, val in dictionary.items(): - if val == value: - return key - return None - -def widget_config_window_label_setter(self, language_yaml_data): - widget_names = [ - # tab UI - "label_transparency", - "label_appearance_theme", - "label_ui_scaling", - "label_font_family", - "label_ui_language", - - # tab Translation - "label_translation_translator", - "label_translation_input_language", - "label_translation_output_language", - - # tab Transcription - "label_input_mic_host", - "label_input_mic_device", - "label_input_mic_voice_language", - "label_input_mic_energy_threshold", - "checkbox_input_mic_threshold_check", - "label_input_mic_dynamic_energy_threshold", - "label_input_mic_record_timeout", - "label_input_mic_phrase_timeout", - "label_input_mic_max_phrases", - "label_input_mic_word_filter", - - "label_input_speaker_device", - "label_input_speaker_voice_language", - "label_input_speaker_energy_threshold", - "checkbox_input_speaker_threshold_check", - "label_input_speaker_dynamic_energy_threshold", - "label_input_speaker_record_timeout", - "label_input_speaker_phrase_timeout", - "label_input_speaker_max_phrases", - - # tab Parameter - "label_ip_address", - "label_port", - "label_authkey", - "label_message_format", - - # tab Others - "label_checkbox_auto_clear_chatbox", - "label_checkbox_notice_xsoverlay", - ] - for name in widget_names: - widget = getattr(self, name) - text_value = language_yaml_data.get(name) - if widget is not None and text_value is not None: - widget.configure(text=text_value + ":") - -def widget_main_window_label_setter(self, language_yaml_data): - widget_names = [ - "checkbox_translation", - "checkbox_transcription_send", - "checkbox_transcription_receive", - "checkbox_foreground", - ] - for name in widget_names: - widget = getattr(self, name) - text_value = language_yaml_data.get(name) - if widget is not None and text_value is not None: - widget.configure(text=text_value) \ No newline at end of file +def getImageFile(file_name): + img = Image_open(os_path.join(os_path.dirname(__file__), "img", file_name)) + return img \ No newline at end of file diff --git a/vrct_gui/__init__.py b/vrct_gui/__init__.py new file mode 100644 index 00000000..5c2e8cca --- /dev/null +++ b/vrct_gui/__init__.py @@ -0,0 +1 @@ +from .vrct_gui import vrct_gui \ No newline at end of file diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py new file mode 100644 index 00000000..602555c2 --- /dev/null +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -0,0 +1,148 @@ +from customtkinter import CTkImage + +from .ui_utils import getImageFileFromUiUtils + + +def _changeMainWindowWidgetsStatus(vrct_gui, settings, status, target_names): + COMPACT_MODE_ICON_SIZE_TUPLES = (settings.COMPACT_MODE_ICON_SIZE, settings.COMPACT_MODE_ICON_SIZE) + + if target_names == "All": + target_names = ["translation_switch", "transcription_send_switch", "transcription_receive_switch", "foreground_switch", "quick_language_settings", "config_button", "minimize_sidebar_button", "entry_message_box"] + + + + + def update_switch_status(widget_frame, widget_label, widget_switch_box, widget_selected_mark, widget_compact_mode_icon, icon_name, disabled_icon_name): + if status == "disabled": + widget_frame.configure(cursor="") + widget_label.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) + widget_switch_box.configure(state="disabled", progress_color=settings.ctm.SF__SWITCH_BOX_DISABLE_BG_COLOR) + widget_selected_mark.configure(fg_color=settings.ctm.SF__SELECTED_MARK_DISABLE_BG_COLOR) + icon_filename = disabled_icon_name + elif status == "normal": + widget_frame.configure(cursor="hand2") + widget_label.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) + widget_switch_box.configure(state="normal", progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR) + widget_selected_mark.configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) + icon_filename = icon_name + + image = CTkImage(getImageFileFromUiUtils(icon_filename), size=COMPACT_MODE_ICON_SIZE_TUPLES) + widget_compact_mode_icon.configure(image=image) + + + + + for target_name in target_names: + match target_name: + case "translation_switch": + update_switch_status( + widget_frame=vrct_gui.translation_frame, + widget_label=vrct_gui.label_translation, + widget_switch_box=vrct_gui.translation_switch_box, + widget_selected_mark=vrct_gui.translation_selected_mark, + widget_compact_mode_icon=vrct_gui.translation_compact_mode_icon, + icon_name=settings.image_filename.TRANSLATION_ICON, + disabled_icon_name=settings.image_filename.TRANSLATION_ICON_DISABLED + ) + case "transcription_send_switch": + update_switch_status( + widget_frame=vrct_gui.transcription_send_frame, + widget_label=vrct_gui.label_transcription_send, + widget_switch_box=vrct_gui.transcription_send_switch_box, + widget_selected_mark=vrct_gui.transcription_send_selected_mark, + widget_compact_mode_icon=vrct_gui.transcription_send_compact_mode_icon, + icon_name=settings.image_filename.MIC_ICON, + disabled_icon_name=settings.image_filename.MIC_ICON_DISABLED + ) + case "transcription_receive_switch": + update_switch_status( + widget_frame=vrct_gui.transcription_receive_frame, + widget_label=vrct_gui.label_transcription_receive, + widget_switch_box=vrct_gui.transcription_receive_switch_box, + widget_selected_mark=vrct_gui.transcription_receive_selected_mark, + widget_compact_mode_icon=vrct_gui.transcription_receive_compact_mode_icon, + icon_name=settings.image_filename.HEADPHONES_ICON, + disabled_icon_name=settings.image_filename.HEADPHONES_ICON_DISABLED + ) + case "foreground_switch": + update_switch_status( + widget_frame=vrct_gui.foreground_frame, + widget_label=vrct_gui.label_foreground, + widget_switch_box=vrct_gui.foreground_switch_box, + widget_selected_mark=vrct_gui.foreground_selected_mark, + widget_compact_mode_icon=vrct_gui.foreground_compact_mode_icon, + icon_name=settings.image_filename.FOREGROUND_ICON, + disabled_icon_name=settings.image_filename.FOREGROUND_ICON_DISABLED + ) + + + + + + + case "quick_language_settings": + if status == "disabled": + vrct_gui.sqls__container_title.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) + vrct_gui.sqls__title_text_your_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) + vrct_gui.sqls__title_text_target_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) + vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE) + vrct_gui.sqls__optionmenu_your_language.configure(state="disabled") + vrct_gui.sqls__optionmenu_target_language.configure(state="disabled") + + elif status == "normal": + vrct_gui.sqls__container_title.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) + vrct_gui.sqls__title_text_your_language.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) + vrct_gui.sqls__title_text_target_language.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) + vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) + vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) + vrct_gui.sqls__optionmenu_your_language.configure(state="normal") + vrct_gui.sqls__optionmenu_target_language.configure(state="normal") + + + case "config_button": + if status == "disabled": + vrct_gui.sidebar_config_button_wrapper.configure(cursor="") + vrct_gui.sidebar_config_button.configure( + image=CTkImage(getImageFileFromUiUtils(settings.image_filename.CONFIGURATION_ICON_DISABLED)), + ) + elif status == "normal": + vrct_gui.sidebar_config_button_wrapper.configure(cursor="hand2") + vrct_gui.sidebar_config_button.configure( + image=CTkImage(getImageFileFromUiUtils(settings.image_filename.CONFIGURATION_ICON)), + ) + + + case "minimize_sidebar_button": + LOGO_SIZE = vrct_gui.minimize_sidebar_button.cget("image").cget("size") + if status == "disabled": + vrct_gui.minimize_sidebar_button_container.configure(cursor="") + + + if settings.IS_SIDEBAR_COMPACT_MODE is True: + image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT_DISABLED).rotate(180), size=LOGO_SIZE) + else: + image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT_DISABLED), size=LOGO_SIZE) + vrct_gui.minimize_sidebar_button.configure(image=image_file) + + elif status == "normal": + vrct_gui.minimize_sidebar_button_container.configure(cursor="hand2") + if settings.IS_SIDEBAR_COMPACT_MODE is True: + image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT).rotate(180), size=LOGO_SIZE) + else: + image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT), size=LOGO_SIZE) + vrct_gui.minimize_sidebar_button.configure(image=image_file) + + + case "entry_message_box": + if status == "disabled": + vrct_gui.entry_message_box.configure(state="disabled", placeholder_text_color=settings.ctm.TEXTBOX_ENTRY_PLACEHOLDER_DISABLED_COLOR, text_color=settings.ctm.TEXTBOX_ENTRY_TEXT_DISABLED_COLOR) + elif status == "normal": + vrct_gui.entry_message_box.configure(state="normal", placeholder_text_color=settings.ctm.TEXTBOX_ENTRY_PLACEHOLDER_COLOR, text_color=settings.ctm.TEXTBOX_ENTRY_TEXT_COLOR) + + + case _: + raise ValueError(f"No matching case for target_name: {target_name}") + + + + vrct_gui.update() \ No newline at end of file diff --git a/vrct_gui/_printToTextbox.py b/vrct_gui/_printToTextbox.py new file mode 100644 index 00000000..c0a696c7 --- /dev/null +++ b/vrct_gui/_printToTextbox.py @@ -0,0 +1,37 @@ +from datetime import datetime +from customtkinter import CTkFont +from config import config + + +def _printToTextbox(settings, target_textbox, original_message, translated_message, tags=None): + now_raw_data = datetime.now() + now = now_raw_data.strftime('%H:%M:%S') + now_hm = now_raw_data.strftime('%H:%M') + + target_textbox.tag_config("NORMAL_TEXT", foreground=settings.ctm.TEXTBOX_TEXT_COLOR) + + target_textbox.tag_config("ERROR", foreground="#FF0000") + + target_textbox.tag_config("INFO", justify="center") + target_textbox.tag_config("INFO_COLOR", foreground="#1BFF00") + + target_textbox.tag_config("SEND", justify="left") + target_textbox.tag_config("SEND_COLOR", foreground="#0378e2") + + target_textbox.tag_config("RECEIVE", justify="left") + target_textbox.tag_config("RECEIVE_COLOR", foreground="#ffa500") + + target_textbox._textbox.tag_configure("START", spacing1=10) + + target_textbox._textbox.tag_configure("LABEL", font=CTkFont(family=config.FONT_FAMILY, size=12, weight="normal")) + target_textbox._textbox.tag_configure("TIMESTAMP", font=CTkFont(family=config.FONT_FAMILY, size=12, weight="normal")) + target_textbox._textbox.tag_configure("ORIGINAL_MESSAGE", font=CTkFont(family=config.FONT_FAMILY, size=12, weight="normal")) + target_textbox._textbox.tag_configure("TRANSLATED_MESSAGE", font=CTkFont(family=config.FONT_FAMILY, size=16, weight="normal")) + + target_textbox.configure(state='normal') + target_textbox.insert("end", f"[{tags}] ", ("START", "LABEL", tags, f"{tags}_COLOR")) + target_textbox.insert("end", f"{now_hm} ", ("TIMESTAMP", tags)) + target_textbox.insert("end", f"{original_message}\n", ("ORIGINAL_MESSAGE", "NORMAL_TEXT", tags)) + target_textbox.insert("end", f"{translated_message}\n", ("TRANSLATED_MESSAGE", "NORMAL_TEXT", tags)) + target_textbox.configure(state='disabled') + target_textbox.see("end") \ No newline at end of file diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py new file mode 100644 index 00000000..bcee1594 --- /dev/null +++ b/vrct_gui/config_window/ConfigWindow.py @@ -0,0 +1,36 @@ +from .widgets import createConfigWindowTitle, createSettingBoxTitle, createSideMenuAndSettingsBoxContainers + +from customtkinter import CTkToplevel + +from ..ui_utils import setDefaultActiveTab + +from config import config + +class ConfigWindow(CTkToplevel): + def __init__(self, vrct_gui, settings): + super().__init__() + + self.INPUT_MIC_RECORD_TIMEOUT = 3 + self.INPUT_SOURCE_LANG = "aaaaaaaaa" + self.INPUT_SPEAKER_ENERGY_THRESHOLD = 300 + self.MAX_SPEAKER_ENERGY_THRESHOLD = 4000 + self.INPUT_MIC_PHRASE_TIMEOUT = 3 + + + # configure window + self.title("test config_window.py") + self.geometry(f"{1080}x{680}") + + + self.configure(fg_color="#ff7f50") + self.protocol("WM_DELETE_WINDOW", vrct_gui.closeConfigWindow) + + + + + createConfigWindowTitle(config_window=self, settings=settings) + + createSettingBoxTitle(config_window=self, settings=settings) + + + createSideMenuAndSettingsBoxContainers(config_window=self, settings=settings) diff --git a/vrct_gui/config_window/__init__.py b/vrct_gui/config_window/__init__.py new file mode 100644 index 00000000..c6dbe941 --- /dev/null +++ b/vrct_gui/config_window/__init__.py @@ -0,0 +1 @@ +from .ConfigWindow import ConfigWindow \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/__init__.py b/vrct_gui/config_window/widgets/__init__.py new file mode 100644 index 00000000..08f2304b --- /dev/null +++ b/vrct_gui/config_window/widgets/__init__.py @@ -0,0 +1,4 @@ +from .createConfigWindowTitle import createConfigWindowTitle +from .createSettingBoxTitle import createSettingBoxTitle + +from .createSideMenuAndSettingsBoxContainers import createSideMenuAndSettingsBoxContainers \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createConfigWindowTitle.py b/vrct_gui/config_window/widgets/createConfigWindowTitle.py new file mode 100644 index 00000000..bbbd9606 --- /dev/null +++ b/vrct_gui/config_window/widgets/createConfigWindowTitle.py @@ -0,0 +1,39 @@ +from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkImage + +from ...ui_utils import getImageFileFromUiUtils + + +def createConfigWindowTitle(config_window, settings): + + config_window.grid_columnconfigure(0, weight=0, minsize=settings.uism.TOP_BAR_SIDE__WIDTH) + config_window.grid_rowconfigure(0, weight=0, minsize=settings.uism.TOP_BAR__HEIGHT) + config_window.side_menu_config_window_title_logo_frame = CTkFrame(config_window, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) + config_window.side_menu_config_window_title_logo_frame.grid(row=0, column=0, sticky="nsew") + + config_window.side_menu_config_window_title_logo_frame.grid_rowconfigure(0,weight=1) + config_window.side_menu_config_window_title_logo_frame.grid_columnconfigure(0,weight=1) + config_window.side_menu_config_window_title_logo_wrapper = CTkFrame(config_window.side_menu_config_window_title_logo_frame, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) + config_window.side_menu_config_window_title_logo_wrapper.grid(row=0, column=0, padx=settings.uism.TOP_BAR_SIDE__TITLE_PADX, pady=settings.uism.TOP_BAR__IPADY, sticky="nsew") + + + + + config_window.side_menu_config_window_title_logo_wrapper.grid_rowconfigure(0,weight=1) + config_window.side_menu_config_window_title = CTkLabel( + config_window.side_menu_config_window_title_logo_frame, + text="Settings", + height=0, + anchor="w", + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TOP_BAR_SIDE__CONFIG_TITLE_FONT_SIZE, weight="bold"), + text_color=settings.ctm.LABELS_TEXT_COLOR, + ) + config_window.side_menu_config_window_title.place(relx=0.275, rely=0.5, anchor="w") + + config_window.side_menu_config_window_title_logo = CTkLabel( + config_window.side_menu_config_window_title_logo_frame, + text=None, + height=0, + anchor="w", + image=CTkImage(getImageFileFromUiUtils(settings.image_filename.VRCT_LOGO_MARK),size=settings.uism.TOP_BAR_SIDE__CONFIG_LOGO_MARK_SIZE), + ) + config_window.side_menu_config_window_title_logo.place(relx=0.08, rely=0.59, anchor="w") diff --git a/vrct_gui/config_window/widgets/createSettingBoxTitle.py b/vrct_gui/config_window/widgets/createSettingBoxTitle.py new file mode 100644 index 00000000..57f5c573 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSettingBoxTitle.py @@ -0,0 +1,20 @@ +from customtkinter import CTkFont, CTkFrame, CTkLabel + +def createSettingBoxTitle(config_window, settings): + + config_window.grid_columnconfigure(1, weight=1) + config_window.main_current_active_config_title_container = CTkFrame(config_window, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) + config_window.main_current_active_config_title_container.grid(row=0, column=1, sticky="nsew") + + + config_window.main_current_active_config_title_container.grid_rowconfigure(0, weight=1) + config_window.main_current_active_config_title = CTkLabel( + config_window.main_current_active_config_title_container, + height=0, + text=None, + anchor="w", + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TOP_BAR_MAIN__TITLE_FONT_SIZE, weight="bold"), + text_color=settings.ctm.LABELS_TEXT_COLOR + ) + config_window.main_current_active_config_title.grid(row=0, column=0, padx=0, pady=settings.uism.TOP_BAR__IPADY) + diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/__init__.py new file mode 100644 index 00000000..245472c9 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/__init__.py @@ -0,0 +1 @@ +from .createSideMenuAndSettingsBoxContainers import createSideMenuAndSettingsBoxContainers \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/addConfigSideMenuItem.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/addConfigSideMenuItem.py new file mode 100644 index 00000000..1f518a07 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/addConfigSideMenuItem.py @@ -0,0 +1,109 @@ +from customtkinter import CTkFont, CTkFrame, CTkLabel + +from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor + + + +def addConfigSideMenuItem(config_window, settings, side_menu_settings, side_menu_row, all_side_menu_tab_attr_name): + + + def switchActiveAndPassiveSettingBoxContainerTabsColor(target_active_widget): + + setting_box_container_tabs = [] + for tab_attr_name in all_side_menu_tab_attr_name: + tab_attr = getattr(config_window, tab_attr_name) + setting_box_container_tabs.append(tab_attr) + + switchTabsColor( + target_widget=target_active_widget, + tab_buttons=setting_box_container_tabs, + active_bg_color=settings.ctm.SIDE_MENU_LABELS_BG_COLOR, + active_text_color=settings.ctm.SIDE_MENU_LABELS_SELECTED_TEXT_COLOR, + passive_bg_color=settings.ctm.SIDE_MENU_LABELS_BG_COLOR, + passive_text_color=settings.ctm.LABELS_TEXT_COLOR + ) + + for setting_box_container_tab in setting_box_container_tabs: + setting_box_container_tab.children["!ctkframe"].place(relx=-1) + + target_active_widget.children["!ctkframe"].place(relx=0) + + + + + def switchSettingBoxContainerTabFunction(target_active_widget): + switchActiveAndPassiveSettingBoxContainerTabsColor(target_active_widget) + switchActiveTabAndPassiveTab(target_active_widget, config_window.current_active_side_menu_tab, config_window.current_active_side_menu_tab.passive_function, settings.ctm.SIDE_MENU_LABELS_HOVERED_BG_COLOR, settings.ctm.SIDE_MENU_LABELS_CLICKED_BG_COLOR, settings.ctm.SIDE_MENU_LABELS_BG_COLOR) + config_window.current_active_side_menu_tab = target_active_widget + + + + + + + def switchSettingBoxContainer(target_setting_box_container_attr_name): + config_window.current_active_setting_box_container.grid_remove() + config_window.current_active_setting_box_container = getattr(config_window, target_setting_box_container_attr_name) + config_window.current_active_setting_box_container.grid() + + # Move to the top position when the setting box is switched. + config_window.main_setting_box_scrollable_container._parent_canvas.yview_moveto("0") + + + def switchToTargetSettingBoxContainer(e, text, target_active_tab_widget_attr_name, target_setting_box_container_attr_name): + print("switchToTargetSettingBoxContainer", target_setting_box_container_attr_name) + config_window.main_current_active_config_title.configure(text=text) + target_active_tab_widget = getattr(config_window, target_active_tab_widget_attr_name) + switchSettingBoxContainerTabFunction(target_active_tab_widget) + switchSettingBoxContainer(target_setting_box_container_attr_name) + + + + + side_menu_tab_attr_name = side_menu_settings["side_menu_tab_attr_name"] + label_attr_name = side_menu_settings["label_attr_name"] + selected_mark_attr_name = side_menu_settings["selected_mark_attr_name"] + text = side_menu_settings["text"] + setting_box_container_attr_name = side_menu_settings["setting_box_container_settings"]["setting_box_container_attr_name"] + command = lambda e: switchToTargetSettingBoxContainer( + e=e, + text=text, + target_active_tab_widget_attr_name=side_menu_tab_attr_name, + target_setting_box_container_attr_name=setting_box_container_attr_name, + ) + + + # Side menu + frame_widget = CTkFrame(config_window.side_menu_container, corner_radius=0, fg_color=settings.ctm.SIDE_MENU_LABELS_BG_COLOR, cursor="hand2", width=0, height=0) + setattr(config_window, side_menu_tab_attr_name, frame_widget) + + frame_widget.grid(row=side_menu_row, column=0, pady=(0,1), sticky="ew") + frame_widget.grid_columnconfigure(0, weight=1) + + label_widget = CTkLabel( + frame_widget, + text=text, + height=0, + corner_radius=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SIDE_MENU_LABELS_FONT_SIZE, weight="normal"), + anchor="w", + text_color=settings.ctm.LABELS_TEXT_COLOR, + ) + setattr(config_window, label_attr_name, label_widget) + + selected_mark_widget = CTkFrame(frame_widget, corner_radius=0, fg_color=settings.ctm.SIDE_MENU_SELECTED_MARK_ACTIVE_BG_COLOR, width=3, height=0) + setattr(config_window, selected_mark_attr_name, selected_mark_widget) + + + + + + # Arrange + selected_mark_widget.place(relx=-1, rely=0.5, relheight=1, anchor="w") + label_widget.grid(row=0, column=0, padx=settings.uism.SIDE_MENU_LABELS_IPADX, pady=settings.uism.SIDE_MENU_LABELS_IPADY, sticky="ew") + + bindEnterAndLeaveColor([frame_widget, label_widget], settings.ctm.SIDE_MENU_LABELS_HOVERED_BG_COLOR, settings.ctm.SIDE_MENU_LABELS_BG_COLOR) + bindButtonPressColor([frame_widget, label_widget], settings.ctm.SIDE_MENU_LABELS_CLICKED_BG_COLOR, settings.ctm.SIDE_MENU_LABELS_BG_COLOR) + + frame_widget.passive_function = command + bindButtonReleaseFunction([frame_widget, label_widget], command) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSettingBoxContainer.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSettingBoxContainer.py new file mode 100644 index 00000000..7935e7d6 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSettingBoxContainer.py @@ -0,0 +1,61 @@ +from customtkinter import CTkFont, CTkFrame, CTkLabel + + +def createSettingBoxContainer(config_window, settings, setting_box_container_settings): + + + def createSectionTitle(container_widget, section_title): + setting_box_wrapper_section_title_frame = CTkFrame(container_widget, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) + + setting_box_wrapper_section_title = CTkLabel( + setting_box_wrapper_section_title_frame, + text=section_title, + anchor="w", + height=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SB__SECTION_TITLE_FONT_SIZE, weight="normal"), + text_color=settings.ctm.LABELS_TEXT_COLOR + ) + setting_box_wrapper_section_title.grid(row=0, column=0, padx=0, pady=settings.uism.SB__SECTION_TITLE_BOTTOM_PADY) + + return setting_box_wrapper_section_title_frame + + # Common setting + + # Setting box container + setting_box_container_widget = CTkFrame(config_window.main_setting_box_bg_wrapper, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) + setattr(config_window, setting_box_container_settings["setting_box_container_attr_name"], setting_box_container_widget) + + + + + setting_boxes_length = len(setting_box_container_settings["setting_boxes"]) + setting_box_row = 0 + for i, setting_box_setting in enumerate(setting_box_container_settings["setting_boxes"]): + SB__TOP_PADY = 0 + SB__BOTTOM_PADY = settings.uism.SB__BOTTOM_PADY + + setting_box_and_section_title_wrapper = CTkFrame(setting_box_container_widget, fg_color=settings.ctm.SB__WRAPPER_BG_COLOR, corner_radius=0, width=0, height=0) + + if setting_box_setting["section_title"] is not None: + setting_box_wrapper_section_title_frame= createSectionTitle( + container_widget=setting_box_and_section_title_wrapper, + section_title=setting_box_setting["section_title"], + ) + setting_box_wrapper_section_title_frame.grid(row=0, column=0, sticky="ew", padx=0, pady=0) + if i == 0: SB__TOP_PADY = settings.uism.SB__TOP_PADY_IF_WITH_SECTION_TITLE + + # if the first one of setting boxes, adjust top pady + if i == 0: SB__TOP_PADY = settings.uism.SB__TOP_PADY_IF_WITHOUT_SECTION_TITLE + + # if the last one of setting boxes, remove bottom pady + if i+1 == setting_boxes_length: SB__BOTTOM_PADY = 0 + + setting_box_wrapper = CTkFrame(setting_box_and_section_title_wrapper, fg_color=settings.ctm.SB__WRAPPER_BG_COLOR, corner_radius=0, width=0, height=0) + setting_box_wrapper.grid(row=1, column=0) + setting_box_row+=1 + + setting_box_and_section_title_wrapper.grid(row=setting_box_row, column=0, sticky="ew", padx=0, pady=(SB__TOP_PADY, SB__BOTTOM_PADY)) + + if setting_box_setting["setting_box"] is not None: + setting_box_setting["setting_box"](setting_box_wrapper=setting_box_wrapper, config_window=config_window, settings=settings) + diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py new file mode 100644 index 00000000..161d5e96 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -0,0 +1,149 @@ +from customtkinter import CTkFrame, CTkScrollableFrame + +from ....ui_utils import setDefaultActiveTab + +from .addConfigSideMenuItem import addConfigSideMenuItem +from .createSettingBoxContainer import createSettingBoxContainer + + +from .setting_box_containers import createSettingBox_General + + +def createSideMenuAndSettingsBoxContainers(config_window, settings): + + # Main container + config_window.main_bg_container = CTkFrame(config_window, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) + config_window.main_bg_container.grid(row=1, column=1, sticky="nsew") + + config_window.main_bg_container.grid_columnconfigure(0, weight=1) + config_window.main_bg_container.grid_rowconfigure(0, weight=0) + + + + + # Side menu Base + config_window.grid_rowconfigure(1, weight=1) + config_window.side_menu_bg_container = CTkFrame(config_window, corner_radius=0, fg_color=settings.ctm.SIDE_MENU_BG_COLOR, width=0, height=0) + config_window.side_menu_bg_container.grid(row=1, column=0, sticky="nsew") + + + config_window.side_menu_container = CTkFrame(config_window.side_menu_bg_container, corner_radius=0, fg_color=settings.ctm.SIDE_MENU_LABELS_BG_FOR_FAKE_BORDER_COLOR, width=0, height=0) + config_window.side_menu_container.grid(row=0, column=0, padx=settings.uism.TOP_BAR_SIDE__TITLE_PADX, pady=(settings.uism.SIDE_MENU_TOP_PADY, 0)) + + + + # Setting box container + config_window.main_bg_container.grid_rowconfigure(1, weight=1) + config_window.main_setting_box_scrollable_container = CTkScrollableFrame(config_window.main_bg_container, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR) + config_window.main_setting_box_scrollable_container.grid(row=1, column=0, sticky="nsew") + + + config_window.main_setting_box_bg_wrapper = CTkFrame(config_window.main_setting_box_scrollable_container, corner_radius=0, width=0, height=0, fg_color=settings.ctm.MAIN_BG_COLOR) + config_window.main_setting_box_bg_wrapper.grid(row=0, column=0, pady=settings.uism.SB__BOTTOM_MARGIN, sticky="n") + + + + side_menu_and_setting_box_containers_settings = [ + { + "side_menu_tab_attr_name": "side_menu_tab_general", + "label_attr_name": "label_general", + "selected_mark_attr_name": "translation_selected_mark", + "text": "General", + "setting_box_container_settings": { + "setting_box_container_attr_name": "setting_box_container_general", + "setting_boxes": [ + { "section_title": None, "setting_box": createSettingBox_General }, + # { "section_title": "General Section Title", "setting_box": createSettingBox_General }, + # { "section_title": None, "setting_box": createSettingBox_General }, + ] + }, + "activate_by_default": True, + }, + { + "side_menu_tab_attr_name": "side_menu_tab_translation", + "label_attr_name": "label_translation", + "selected_mark_attr_name": "transcription_send_selected_mark", + "text": "Translation", + "setting_box_container_settings": { + "setting_box_container_attr_name": "setting_box_container_translation", + "setting_boxes": [ + { "section_title": None, "setting_box": None }, + ] + }, + }, + { + "side_menu_tab_attr_name": "side_menu_tab_transcription", + "label_attr_name": "label_transcription", + "selected_mark_attr_name": "transcription_receive_selected_mark", + "text": "Transcription", + "setting_box_container_settings": { + "setting_box_container_attr_name": "setting_box_container_transcription", + "setting_boxes": [ + { "section_title": None, "setting_box": None }, + ] + }, + }, + { + "side_menu_tab_attr_name": "side_menu_tab_parameters", + "label_attr_name": "label_parameters", + "selected_mark_attr_name": "foreground_selected_mark", + "text": "Parameters", + "setting_box_container_settings": { + "setting_box_container_attr_name": "setting_box_container_parameters", + "setting_boxes": [ + { "section_title": None, "setting_box": None }, + ] + }, + }, + { + "side_menu_tab_attr_name": "side_menu_tab_others", + "label_attr_name": "label_others", + "selected_mark_attr_name": "foreground_selected_mark", + "text": "Others", + "setting_box_container_settings": { + "setting_box_container_attr_name": "setting_box_container_others", + "setting_boxes": [ + { "section_title": None, "setting_box": None }, + ] + }, + }, + ] + + all_side_menu_tab_attr_name = [item["side_menu_tab_attr_name"] for item in side_menu_and_setting_box_containers_settings] + + side_menu_row=0 + for sm_and_sbc_setting in side_menu_and_setting_box_containers_settings: + addConfigSideMenuItem( + config_window=config_window, + settings=settings, + side_menu_settings=sm_and_sbc_setting, + side_menu_row=side_menu_row, + all_side_menu_tab_attr_name=all_side_menu_tab_attr_name, + ) + side_menu_row+=1 + + + createSettingBoxContainer( + config_window=config_window, + settings=settings, + setting_box_container_settings=sm_and_sbc_setting["setting_box_container_settings"], + + ) + + + if sm_and_sbc_setting.get("activate_by_default", None) is not None: + # Set default active side menu tab + config_window.main_current_active_config_title.configure(text=sm_and_sbc_setting["text"]) + config_window.current_active_side_menu_tab = getattr(config_window, sm_and_sbc_setting["side_menu_tab_attr_name"]) + setDefaultActiveTab( + active_tab_widget=config_window.current_active_side_menu_tab, + active_bg_color=settings.ctm.SIDE_MENU_LABELS_BG_COLOR, + active_text_color=settings.ctm.SIDE_MENU_LABELS_SELECTED_TEXT_COLOR + ) + config_window.current_active_side_menu_tab.children["!ctkframe"].place(relx=0) + + # Set default active setting box container + config_window.current_active_setting_box_container = getattr(config_window, sm_and_sbc_setting["setting_box_container_settings"]["setting_box_container_attr_name"]) + config_window.current_active_setting_box_container.grid() + + diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py new file mode 100644 index 00000000..5e0a530d --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py @@ -0,0 +1,494 @@ +from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar, END as CTK_END +from ctk_scrollable_dropdown import CTkScrollableDropdown + +from vrct_gui.ui_utils import createButtonWithImage + + + +class SettingBoxGenerator(): + def __init__(self, config_window, settings): + + self.IS_CONFIG_WINDOW_COMPACT_MODE = settings.IS_CONFIG_WINDOW_COMPACT_MODE + self.ctm = settings.ctm + self.uism = settings.uism + self.FONT_FAMILY = settings.FONT_FAMILY + self.config_window = config_window + + + + def _createSettingBoxFrameWrapper(self, setting_box_frame): + setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=self.uism.SB__MAIN_WIDTH, height=0) + setting_box_frame_wrapper.grid(row=0, column=0, padx=self.uism.SB__IPADX, pady=self.uism.SB__IPADY, sticky="ew") + setting_box_frame_wrapper.grid_columnconfigure((0,1), weight=1, minsize=int(self.uism.SB__MAIN_WIDTH / 2)) + return setting_box_frame_wrapper + + def _createSettingBoxFrame(self, parent_widget, label_text, desc_text): + setting_box_frame = CTkFrame(parent_widget, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=0, height=0) + setting_box_frame_wrapper = self._createSettingBoxFrameWrapper(setting_box_frame) + self._setSettingBoxLabels(setting_box_frame_wrapper, label_text, desc_text) + + # "pady=(0,1)" is for bottom padding. It can be removed(override) when you do like "self.attr_name.grid(row=row, pady=0)" + setting_box_frame.grid(column=0, padx=0, pady=(0,1), sticky="ew") + return (setting_box_frame, setting_box_frame_wrapper) + + def _setSettingBoxLabels(self, setting_box_frame, label_text, desc_text=False): + + setting_box_labels_frame = CTkFrame(setting_box_frame, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=0, height=0) + + setting_box_label = CTkLabel( + setting_box_labels_frame, + text=label_text, + anchor="w", + # height=0, + font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__LABEL_FONT_SIZE, weight="normal"), + text_color=self.ctm.LABELS_TEXT_COLOR + ) + setting_box_label.grid(row=0, column=0, padx=0, pady=0, sticky="ew") + + if desc_text == False or self.IS_CONFIG_WINDOW_COMPACT_MODE is True: + pass + else: + self.setting_box_desc = CTkLabel( + setting_box_labels_frame, + text=desc_text, + anchor="w", + justify="left", + # height=0, + wraplength=int(self.uism.SB__MAIN_WIDTH / 2), + font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__DESC_FONT_SIZE, weight="normal"), + text_color=self.ctm.LABELS_DESC_TEXT_COLOR + ) + self.setting_box_desc.grid(row=1, column=0, padx=0, pady=(self.uism.SB__DESC_TOP_PADY,0), sticky="ew") + + setting_box_labels_frame.grid(row=0, column=0, padx=0, pady=0, sticky="w") + + + + def createSettingBoxDropdownMenu(self, parent_widget, label_text, desc_text, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values, command, variable): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, label_text, desc_text) + + setting_box_dropdown_menu_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) + setting_box_dropdown_menu_frame.grid(row=0, column=1, padx=0, sticky="e") + + self.createOption_DropdownMenu( + setting_box_dropdown_menu_frame=setting_box_dropdown_menu_frame, + optionmenu_attr_name=optionmenu_attr_name, + dropdown_menu_attr_name=dropdown_menu_attr_name, + dropdown_menu_values=dropdown_menu_values, + command=command, + variable=variable, + ) + + return setting_box_frame + + + + + def createSettingBoxSwitch(self, parent_widget, label_text, desc_text, switch_attr_name, is_checked, command): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, label_text, desc_text) + + setting_box_switch_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) + setting_box_switch_frame.grid(row=0, column=1, padx=0, sticky="e") + + switch_widget = CTkSwitch( + setting_box_switch_frame, + text=None, + height=0, + width=0, + corner_radius=int(self.uism.SB__SWITCH_BOX_HEIGHT/2), + border_width=0, + switch_height=self.uism.SB__SWITCH_BOX_HEIGHT, + switch_width=self.uism.SB__SWITCH_BOX_WIDTH, + onvalue=True, + offvalue=False, + command=command, + fg_color=self.ctm.SB__SWITCH_BOX_BG_COLOR, + # bg_color="red", + progress_color=self.ctm.SB__SWITCH_BOX_ACTIVE_BG_COLOR, + ) + setattr(self.config_window, switch_attr_name, switch_widget) + + switch_widget.select() if is_checked else switch_widget.deselect() + + switch_widget.grid(row=0, column=0) + + return setting_box_frame + + + + def createSettingBoxCheckbox(self, parent_widget, label_text, desc_text, checkbox_attr_name, is_checked, command): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, label_text, desc_text) + + setting_box_checkbox_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) + setting_box_checkbox_frame.grid(row=0, column=1, padx=0, sticky="e") + + checkbox_widget = CTkCheckBox( + setting_box_checkbox_frame, + text=None, + width=0, + checkbox_width=self.uism.SB__CHECKBOX_SIZE, + checkbox_height=self.uism.SB__CHECKBOX_SIZE, + onvalue=True, + offvalue=False, + command=command, + corner_radius=self.uism.SB__CHECKBOX_CORNER_RADIUS, + border_width=self.uism.SB__CHECKBOX_BORDER_WIDTH, + border_color=self.ctm.SB__CHECKBOX_BORDER_COLOR, + hover_color=self.ctm.SB__CHECKBOX_HOVER_COLOR, + checkmark_color=self.ctm.SB__CHECKBOX_CHECKMARK_COLOR, + fg_color=self.ctm.SB__CHECKBOX_CHECKED_COLOR, + # fg_color=self.ctm.SB__SWITCH_BOX_BG_COLOR, + # bg_color="red", + # progress_color=self.ctm.SB__SWITCH_BOX_ACTIVE_BG_COLOR, + ) + setattr(self.config_window, checkbox_attr_name, checkbox_widget) + + checkbox_widget.select() if is_checked else checkbox_widget.deselect() + + checkbox_widget.grid(row=0, column=0) + + return setting_box_frame + + + + + + + def createSettingBoxSlider(self, parent_widget, label_text, desc_text, slider_attr_name, slider_range, slider_number_of_steps, command, variable): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, label_text, desc_text) + + setting_box_slider_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) + setting_box_slider_frame.grid(row=0, column=1, padx=0, sticky="e") + + slider_widget = CTkSlider( + setting_box_slider_frame, + from_=slider_range[0], + to=slider_range[1], + number_of_steps=slider_number_of_steps, + button_color=self.ctm.SB__SLIDER_BUTTON_COLOR, + button_hover_color=self.ctm.SB__SLIDER_BUTTON_HOVERED_COLOR, + command=command, + variable=variable, + ) + setattr(self.config_window, slider_attr_name, slider_widget) + + slider_widget.grid(row=0, column=0) + + return setting_box_frame + + + + + def createSettingBoxProgressbarXSlider(self, + parent_widget, label_text, desc_text, command, + entry_attr_name, + slider_attr_name, slider_range, slider_number_of_steps, + progressbar_attr_name, + passive_button_attr_name, passive_button_command, + active_button_attr_name, active_button_command, + button_image_filename, + variable, + ): + + + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, label_text, desc_text) + + setting_box_progressbar_x_slider_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) + setting_box_progressbar_x_slider_frame.grid(row=0, column=1, padx=0, sticky="e") + + + ENTRY_WIDTH = self.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH + BAR_WIDTH = self.uism.SB__PROGRESSBAR_X_SLIDER__BAR_WIDTH + + BAR_PADDING = int(ENTRY_WIDTH + self.uism.SB__PROGRESSBAR_X_SLIDER__BAR_RIGHT_PADX) + BUTTON_PADDING = int(BAR_WIDTH + BAR_PADDING + self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_RIGHT_PADX) + + def adjusted_command__for_entry_bind__Any_KeyRelease(e): + # try: + # int(e.widget.get()) + # except: + # e.widget.delete(0, CTK_END) + # return + # print(int(e.widget.get())) + + + i = int(e.widget.get()) + if i < 0 or i > slider_range[1]: + e.widget.delete(0, CTK_END) + i = max(0, min(int(e.widget.get()), slider_range[1])) + # e.widget.insert(0, i) + command(i) + def adjusted_command__for_slider(value): + command(int(value)) + + entry_widget = CTkEntry( + setting_box_progressbar_x_slider_frame, + width=ENTRY_WIDTH, + height=self.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT, + textvariable=variable, + font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__ENTRY_FONT_SIZE, weight="normal"), + ) + + entry_widget.bind("", adjusted_command__for_entry_bind__Any_KeyRelease) + entry_widget.grid(row=0, column=0, padx=0, pady=0, sticky="e") + setattr(self.config_window, entry_attr_name, entry_widget) + + + # at least 2px is needed otherwise the slider button is gonna broken. + SLIDER_BORDER_WIDTH = max(2,self.uism.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_LENGTH) + SLIDER_BUTTON_LENGTH = int(SLIDER_BORDER_WIDTH/2) + slider_widget = CTkSlider( + setting_box_progressbar_x_slider_frame, + from_=slider_range[0], + to=slider_range[1], + number_of_steps=slider_number_of_steps, + command=adjusted_command__for_slider, + variable=variable, + height=self.uism.SB__PROGRESSBAR_X_SLIDER__SLIDER_HEIGHT, + width=BAR_WIDTH, + border_width=0, + button_length=SLIDER_BORDER_WIDTH, + button_corner_radius=SLIDER_BUTTON_LENGTH, + corner_radius=0, + button_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_COLOR, + button_hover_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_HOVERED_COLOR, + fg_color=self.ctm.SB__BG_COLOR, + progress_color=self.ctm.SB__BG_COLOR, + border_color=self.ctm.SB__BG_COLOR, + ) + slider_widget.grid(row=0, column=0, padx=(0, BAR_PADDING), sticky="e") + setattr(self.config_window, slider_attr_name, slider_widget) + + + + + progressbar_widget = CTkProgressBar( + setting_box_progressbar_x_slider_frame, + width=BAR_WIDTH, + height=self.uism.SB__PROGRESSBAR_X_SLIDER__PROGRESSBAR_HEIGHT, + corner_radius=0, + ) + setattr(self.config_window, progressbar_attr_name, progressbar_widget) + progressbar_widget.grid(row=0, column=0, padx=(0, BAR_PADDING), sticky="e") + progressbar_widget.set(0) + + + + + passive_button_wrapper = self._createPassiveButtonForProgressbarXSlider(setting_box_progressbar_x_slider_frame, BUTTON_PADDING, passive_button_command, button_image_filename) + setattr(self.config_window, passive_button_attr_name, passive_button_wrapper) + + active_button_wrapper = self._createActiveButtonForProgressbarXSlider(setting_box_progressbar_x_slider_frame, BUTTON_PADDING, active_button_command, button_image_filename) + setattr(self.config_window, active_button_attr_name, active_button_wrapper) + + passive_button_wrapper.grid() + return setting_box_frame + + + + + def createSettingBoxEntry(self, parent_widget, label_text, desc_text, entry_attr_name, entry_width, entry_bind__Any_KeyRelease, entry_textvariable): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, label_text, desc_text) + + setting_box_entry_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) + setting_box_entry_frame.grid(row=0, column=1, padx=0, sticky="e") + + entry_widget = CTkEntry( + setting_box_entry_frame, + width=entry_width, + height=self.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT, + textvariable=entry_textvariable, + font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__ENTRY_FONT_SIZE, weight="normal"), + ) + entry_widget.bind("", entry_bind__Any_KeyRelease) + setattr(self.config_window, entry_attr_name, entry_widget) + + + entry_widget.grid(row=0, column=0) + + return setting_box_frame + + + + # if setting_box_type == "dropdown_menu_x_dropdown_menu": + # self.setting_box_dropdown_menu_x_dropdown_menu = CTkFrame(self.setting_box, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=0, height=0) + # self.setting_box_dropdown_menu_x_dropdown_menu.grid(row=0, column=1, padx=(0, self.uism.SB__RIGHT_PADX), rowspan=2, sticky="e") + + + + # # Labels + # self.optionmenu_label_left = CTkLabel( + # self.setting_box_dropdown_menu_x_dropdown_menu, + # text=kwargs["left_dropdown_menu_label"], + # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), + # ) + # self.optionmenu_label_left.grid(row=0, column=0) + + # self.the_space_between_optionmenu = CTkLabel( + # self.setting_box_dropdown_menu_x_dropdown_menu, + # text=None, + # ) + # self.the_space_between_optionmenu.grid(row=0, column=1) + + + # self.optionmenu_label_right = CTkLabel( + # self.setting_box_dropdown_menu_x_dropdown_menu, + # text=kwargs["right_dropdown_menu_label"], + # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), + # ) + # self.optionmenu_label_right.grid(row=0, column=2) + + + + # # Option menus + # self.createOption_DropdownMenu( + # setattr_obj, + # self.setting_box_dropdown_menu_x_dropdown_menu, + # kwargs["left_optionmenu_attr_name"], + # kwargs["left_dropdown_menu_attr_name"], + # dropdown_menu_values=kwargs["left_dropdown_menu_values"], + # width=150, + # command=kwargs["left_dropdown_menu_command"], + # variable=kwargs["left_dropdown_menu_variable"], + # ) + # getattr(setattr_obj, kwargs["left_optionmenu_attr_name"]).grid(row=1, column=0) + + + + # self.the_label_between_optionmenu = CTkLabel( + # self.setting_box_dropdown_menu_x_dropdown_menu, + # text="-->", + # # anchor="w", + # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), + # text_color=self.ctm.LABELS_TEXT_COLOR + # ) + # self.the_label_between_optionmenu.grid(row=1, column=1, padx=self.uism.SB__RIGHT_PADX/2) + + + # self.createOption_DropdownMenu( + # setattr_obj, + # self.setting_box_dropdown_menu_x_dropdown_menu, + # kwargs["right_optionmenu_attr_name"], + # kwargs["right_dropdown_menu_attr_name"], + # dropdown_menu_values=kwargs["right_dropdown_menu_values"], + # width=150, + # command=kwargs["right_dropdown_menu_command"], + # variable=kwargs["right_dropdown_menu_variable"], + # ) + # getattr(setattr_obj, kwargs["right_optionmenu_attr_name"]).grid(row=1, column=2) + + + + + # if setting_box_type == "radio_buttons": + # self.setting_box_radio_buttons_frame = CTkFrame(self.setting_box, corner_radius=0, width=0, height=0) + # self.setting_box_radio_buttons_frame.grid(row=0, column=1, padx=(0, self.uism.SB__RIGHT_PADX), rowspan=2, sticky="e") + + # RADIO_BUTTON_RIGHT_PAD = 14 + # self.setting_box_radio_button_1 = CTkRadioButton( + # self.setting_box_radio_buttons_frame, + # text="lorem ipsum", + # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__RADIO_BUTTON_FONT_SIZE, weight="normal") + # ) + # self.setting_box_radio_button_1.grid(row=0, column=0, padx=(0,RADIO_BUTTON_RIGHT_PAD), sticky="e") + + # self.setting_box_radio_button_2 = CTkRadioButton( + # self.setting_box_radio_buttons_frame, + # text="lorem ipsum", + # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__RADIO_BUTTON_FONT_SIZE, weight="normal") + # ) + # self.setting_box_radio_button_2.grid(row=0, column=1, padx=(0,RADIO_BUTTON_RIGHT_PAD), sticky="e") + + # self.setting_box_radio_button_3 = CTkRadioButton( + # self.setting_box_radio_buttons_frame, + # text="lorem ipsum", + # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__RADIO_BUTTON_FONT_SIZE, weight="normal") + # ) + # self.setting_box_radio_button_3.grid(row=0, column=2, padx=(0,RADIO_BUTTON_RIGHT_PAD), sticky="e") + + + + + + def createOption_DropdownMenu(self, setting_box_dropdown_menu_frame, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values, command, variable): + option_menu_widget = CTkOptionMenu( + setting_box_dropdown_menu_frame, + height=self.uism.SB__OPTIONMENU_HEIGHT, + width=self.uism.SB__OPTIONMENU_WIDTH, + button_color=self.ctm.SB__OPTIONMENU_BG_COLOR, + button_hover_color=self.ctm.SB__OPTIONMENU_HOVERED_BG_COLOR, + fg_color=self.ctm.SB__OPTIONMENU_BG_COLOR, + font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), + variable=variable, + anchor="w", + ) + option_menu_widget.grid(row=0, column=0, sticky="e") + setattr(self.config_window, optionmenu_attr_name, option_menu_widget) + + # set the value to the option menu's variable automatically + def adjustedCommand(selected_value): + option_menu_widget.set(selected_value) + command(selected_value) + + dropdown_menu_widget = CTkScrollableDropdown( + option_menu_widget, + values=dropdown_menu_values, + justify="left", + width=self.uism.SB__DROPDOWN_MENU_WIDTH, + min_show_button_num=6, + button_pady=0, + frame_corner_radius=self.uism.SB__DROPDOWN_MENU_FRAME_CORNER_RADIUS, + max_button_height=self.uism.SB__DROPDOWN_MENU_MAX_BUTTON_HEIGHT, + max_height=self.uism.SB__DROPDOWN_MENU_FRAME_MAX_HEIGHT, + font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), + command=adjustedCommand, + ) + + # dropdown_menu_widget.bind( + # "", + # lambda e: dropdown_menu_widget._withdraw() if not str(e.widget).startswith(str(dropdown_menu_widget.frame._parent_frame)) else None, + # ) + dropdown_menu_widget.bind( + "", + lambda e: print(e), + ) + + setattr(self.config_window, dropdown_menu_attr_name, dropdown_menu_widget) + return option_menu_widget + + + + + def _createPassiveButtonForProgressbarXSlider(self, setting_box_progressbar_x_slider_frame, BUTTON_PADDING, button_command, button_image_filename): + button_wrapper = createButtonWithImage( + parent_widget=setting_box_progressbar_x_slider_frame, + button_fg_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR, + button_enter_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_HOVERED_COLOR, + button_clicked_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_CLICKED_COLOR, + button_image_filename=button_image_filename, + button_image_size=self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE, + button_ipadxy=self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY, + button_command=button_command, + shape="circle", + ) + button_wrapper.grid(row=0, column=0, padx=(0,BUTTON_PADDING), sticky="e") + button_wrapper.grid_remove() + return button_wrapper + + + + def _createActiveButtonForProgressbarXSlider(self, setting_box_progressbar_x_slider_frame, BUTTON_PADDING, button_command, button_image_filename): + button_wrapper = createButtonWithImage( + parent_widget=setting_box_progressbar_x_slider_frame, + button_fg_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_COLOR, + button_enter_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_HOVERED_COLOR, + button_clicked_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_CLICKED_COLOR, + button_image_filename=button_image_filename, + button_image_size=self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE, + button_ipadxy=self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY, + button_command=button_command, + shape="circle", + ) + button_wrapper.grid(row=0, column=0, padx=(0,BUTTON_PADDING), sticky="e") + button_wrapper.grid_remove() + return button_wrapper \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py new file mode 100644 index 00000000..f992f8c0 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py @@ -0,0 +1 @@ +from .setting_box_general import createSettingBox_General \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/__init__.py new file mode 100644 index 00000000..c0c243b9 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/__init__.py @@ -0,0 +1 @@ +from .createSettingBox_General import createSettingBox_General \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/createSettingBox_General.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/createSettingBox_General.py new file mode 100644 index 00000000..70fd7bd3 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/createSettingBox_General.py @@ -0,0 +1,225 @@ +from time import sleep + +from customtkinter import StringVar, IntVar + + +from ..SettingBoxGenerator import SettingBoxGenerator + +from config import config + +def createSettingBox_General(setting_box_wrapper, config_window, settings): + + + sbg = SettingBoxGenerator(config_window, settings) + + createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu + createSettingBoxSwitch = sbg.createSettingBoxSwitch + createSettingBoxCheckbox = sbg.createSettingBoxCheckbox + createSettingBoxSlider = sbg.createSettingBoxSlider + createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider + createSettingBoxEntry = sbg.createSettingBoxEntry + + + + def checkbox_input_speaker_threshold_check_callback(e, passive_button_wrapper_widget, active_button_wrapper_widget, is_turned_on): + print("is_turned_on", is_turned_on) + + if is_turned_on is True: + passive_button_widget = passive_button_wrapper_widget.children["!ctklabel"] + passive_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + passive_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + passive_button_wrapper_widget.update_idletasks() + sleep(1) + + passive_button_wrapper_widget.grid_remove() + active_button_wrapper_widget.grid() + + elif is_turned_on is False: + # active_button_widget = active_button_wrapper_widget.children["!ctklabel"] + # active_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + # active_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + # active_button_wrapper_widget.update_idletasks() + # sleep(3) + + active_button_wrapper_widget.grid_remove() + passive_button_wrapper_widget.grid() + + # def slider_input_speaker_energy_threshold_callback(self, value): + # config_window.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) + + def set_input_threshold(value): + print(value) + config.INPUT_SPEAKER_ENERGY_THRESHOLD = value + print(config.INPUT_SPEAKER_ENERGY_THRESHOLD) + + + + + + + + + + def dropdownMenuFun(selected_value, optionmenu_widget, dropdown_menu_widget): + print(selected_value) + config.INPUT_SOURCE_LANG = selected_value + + def switchFun(_, switch_box_widget): + print(switch_box_widget.get()) + + def checkboxFun(_, checkbox_box_widget): + print(checkbox_box_widget.get()) + + def sliderFun(_, value, slider_widget): + print(value) + + def entryFun(e, entry_widget): + print(e.widget.get()) + config_window.INPUT_MIC_PHRASE_TIMEOUT = int(e.widget.get()) + print(config_window.INPUT_MIC_PHRASE_TIMEOUT) + + + + row=0 + config_window.sb__dropdown_menu_1 = createSettingBoxDropdownMenu( + parent_widget=setting_box_wrapper, + label_text="Option Menu", + desc_text="Select your preferences from the options menu.\nYou can choose your preferred language.", + optionmenu_attr_name="optionmenu_attr_name_1", + dropdown_menu_attr_name="dropdown_menu_attr_name_1", + dropdown_menu_values=["tt", "Japanese", "English"], + command=lambda value: dropdownMenuFun(value, config_window.optionmenu_attr_name_1, config_window.dropdown_menu_attr_name_1), + variable=StringVar(value=config.INPUT_SOURCE_LANG) + ) + config_window.sb__dropdown_menu_1.grid(row=row) + row+=1 + + + config_window.sb__dropdown_menu_2 = createSettingBoxDropdownMenu( + parent_widget=setting_box_wrapper, + label_text="Option Menu", + desc_text="Select your preferences from the options menu.\nYou can choose your preferred language.", + optionmenu_attr_name="optionmenu_attr_name_2", + dropdown_menu_attr_name="dropdown_menu_attr_name_1", + dropdown_menu_values=["tt", "Japanese", "English"], + command=lambda value: dropdownMenuFun(value, config_window.optionmenu_attr_name_2, config_window.dropdown_menu_attr_name_1), + variable=StringVar(value=config.INPUT_SOURCE_LANG) + ) + config_window.sb__dropdown_menu_2.grid(row=row) + row+=1 + + config_window.sb__switch_1 = createSettingBoxSwitch( + parent_widget=setting_box_wrapper, + label_text="Switch", + desc_text="Turning this switch on will bring happiness.\nAs for turning it off... I leave that to your imagination", + switch_attr_name="switch_attr_name_1", + command=lambda: switchFun(config_window.switch_attr_name_1), + is_checked=True, + ) + config_window.sb__switch_1.grid(row=row) + row+=1 + + config_window.sb__checkbox_1 = createSettingBoxCheckbox( + parent_widget=setting_box_wrapper, + label_text="Checkbox", + desc_text="Checkbox ticked, a checkmark.", + checkbox_attr_name="checkbox_attr_name_1", + command=lambda: checkboxFun(config_window.checkbox_attr_name_1), + is_checked=False, + ) + config_window.sb__checkbox_1.grid(row=row) + row+=1 + + config_window.sb__slider_1 = createSettingBoxSlider( + parent_widget=setting_box_wrapper, + label_text="Slider", + desc_text="Adjust using the slider; the balance is up to you.", + slider_attr_name="slider_attr_name_1", + slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), + slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, + command=lambda value: sliderFun(value, config_window.slider_attr_name_1), + variable=IntVar(value=config_window.INPUT_SPEAKER_ENERGY_THRESHOLD), + ) + config_window.sb__slider_1.grid(row=row) + row+=1 + + + config_window.sb__progressbar_x_slider_1 = createSettingBoxProgressbarXSlider( + parent_widget=setting_box_wrapper, + label_text="Progressbar and Slider for check the threshold", + desc_text="just the slider to modify the threshold for activating voice input.\nPress the microphone button to start input, and you can adjust it while monitoring the actual volume.", + command=set_input_threshold, # ? + variable=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), + entry_attr_name="progressbar_x_slider__entry_attr_name_1", + + + slider_attr_name="progressbar_x_slider__slider_attr_name_1", + slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), + slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, + + progressbar_attr_name="progressbar_x_slider__progressbar_attr_name_1", + + passive_button_attr_name="progressbar_x_slider__passive_button_attr_name_1", + passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + e, + config_window.progressbar_x_slider__passive_button_attr_name_1, + config_window.progressbar_x_slider__active_button_attr_name_1, + is_turned_on=True, + ), + active_button_attr_name="progressbar_x_slider__active_button_attr_name_1", + active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + e, + config_window.progressbar_x_slider__passive_button_attr_name_1, + config_window.progressbar_x_slider__active_button_attr_name_1, + is_turned_on=False, + ), + button_image_filename="mic_icon_white.png" + ) + config_window.sb__progressbar_x_slider_1.grid(row=row) + row+=1 + + config_window.sb__progressbar_x_slider_2 = createSettingBoxProgressbarXSlider( + parent_widget=setting_box_wrapper, + label_text="Progressbar and Slider for check the threshold2", + desc_text="just the slider to modify the threshold for activating voice input.\nPress the microphone button to start input, and you can adjust it while monitoring the actual volume.", + command=set_input_threshold, # ? + variable=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), + + entry_attr_name="progressbar_x_slider__entry_attr_name_2", + + + slider_attr_name="progressbar_x_slider__slider_attr_name_2", + slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), + slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, + progressbar_attr_name="progressbar_x_slider__progressbar_attr_name_2", + + passive_button_attr_name="progressbar_x_slider__passive_button_attr_name_2", + passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + e, + config_window.progressbar_x_slider__passive_button_attr_name_2, + config_window.progressbar_x_slider__active_button_attr_name_2, + is_turned_on=True, + ), + active_button_attr_name="progressbar_x_slider__active_button_attr_name_2", + active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + e, + config_window.progressbar_x_slider__passive_button_attr_name_2, + config_window.progressbar_x_slider__active_button_attr_name_2, + is_turned_on=False, + ), + button_image_filename="headphones_icon_white.png" + ) + config_window.sb__progressbar_x_slider_2.grid(row=row) + row+=1 + + config_window.sb__entry_1 = createSettingBoxEntry( + parent_widget=setting_box_wrapper, + label_text="Entry", + desc_text="Please input a numerical value.", + entry_attr_name="entry_attr_name_1", + entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_bind__Any_KeyRelease=lambda value: entryFun(value, config_window.entry_attr_name_1), + entry_textvariable=IntVar(value=config_window.INPUT_MIC_PHRASE_TIMEOUT), + ) + config_window.sb__entry_1.grid(row=row, pady=0) + row+=1 \ No newline at end of file diff --git a/vrct_gui/main_window/__init__.py b/vrct_gui/main_window/__init__.py new file mode 100644 index 00000000..a34b6566 --- /dev/null +++ b/vrct_gui/main_window/__init__.py @@ -0,0 +1 @@ +from .createMainWindowWidgets import createMainWindowWidgets \ No newline at end of file diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py new file mode 100644 index 00000000..980a15be --- /dev/null +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -0,0 +1,93 @@ +from .widgets import createSidebar, createMinimizeSidebarButton, createTextbox, createEntryMessageBox + +from customtkinter import CTkFrame + +from ..ui_utils import createButtonWithImage, getImagePath + + +def createMainWindowWidgets(vrct_gui, settings): + vrct_gui.protocol("WM_DELETE_WINDOW", vrct_gui.quitVRCT) + + # self.IS_DEVELOPER_MODE = False + # self.IS_DEVELOPER_MODE = True + + + + + # self.YOUR_LANGUAGE = "Japanese\n(Japan)" + # self.TARGET_LANGUAGE = "English\n(United States)" + + vrct_gui.iconbitmap(getImagePath("app.ico")) + vrct_gui.title("VRCT") + vrct_gui.geometry(f"{880}x{640}") + vrct_gui.minsize(400, 175) + + + # Main Container + vrct_gui.grid_columnconfigure(1, weight=1) + + vrct_gui.configure(fg_color="#ff7f50") + # return + + + # Main Container + vrct_gui.main_bg_container = CTkFrame(vrct_gui, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) + vrct_gui.main_bg_container.grid(row=0, column=1, sticky="nsew") + + + # top bar + vrct_gui.main_bg_container.grid_columnconfigure(0, weight=1) + vrct_gui.main_topbar_container = CTkFrame(vrct_gui.main_bg_container, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) + vrct_gui.main_topbar_container.grid(row=0, column=0, sticky="ew") + + + + + + vrct_gui.main_topbar_container.columnconfigure(1,weight=1) + vrct_gui.main_topbar_center_container = CTkFrame(vrct_gui.main_topbar_container, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) + vrct_gui.main_topbar_center_container.grid(row=0, column=1, sticky="nsew") + + + + + # Help and Info button + vrct_gui.help_and_info_button_container = createButtonWithImage( + parent_widget=vrct_gui.main_topbar_container, + button_fg_color=settings.ctm.HELP_AND_INFO_BUTTON_BG_COLOR, + button_enter_color=settings.ctm.HELP_AND_INFO_BUTTON_HOVERED_BG_COLOR, + button_clicked_color=settings.ctm.HELP_AND_INFO_BUTTON_CLICKED_BG_COLOR, + button_image_filename=settings.image_filename.HELP_ICON, + button_image_size=settings.uism.HELP_AND_INFO_BUTTON_SIZE, + button_ipadxy=settings.uism.HELP_AND_INFO_BUTTON_IPADXY, + button_command=vrct_gui.openHelpAndInfoWindow, + corner_radius=settings.uism.HELP_AND_INFO_BUTTON_CORNER_RADIUS, + ) + vrct_gui.help_and_info_button_container.grid(row=0, column=3, padx=settings.uism.HELP_AND_INFO_BUTTON_PADX, pady=settings.uism.HELP_AND_INFO_BUTTON_PADY, sticky="e") + + createSidebar(settings, vrct_gui) + + createMinimizeSidebarButton(settings, vrct_gui) + + createTextbox(settings, vrct_gui) + + createEntryMessageBox(settings, vrct_gui) + + + + + + # def delete_window(self): + # self.vrct_gui.quit() + # self.vrct_gui.destroy() + + # def openConfigWindow(self, e): + # self.config_window.deiconify() + # self.config_window.focus_set() + # self.config_window.focus() + # self.config_window.grab_set() + + # def openHelpAndInfoWindow(self, e): + # self.information_window.deiconify() + # self.information_window.focus_set() + # self.information_window.focus() \ No newline at end of file diff --git a/vrct_gui/main_window/widgets/__init__.py b/vrct_gui/main_window/widgets/__init__.py new file mode 100644 index 00000000..ac037cb8 --- /dev/null +++ b/vrct_gui/main_window/widgets/__init__.py @@ -0,0 +1,4 @@ +from .create_sidebar import createSidebar +from .create_minimize_sidebar_button import createMinimizeSidebarButton +from .create_textbox import createTextbox +from .create_entry_message_box import createEntryMessageBox diff --git a/vrct_gui/main_window/widgets/create_entry_message_box.py b/vrct_gui/main_window/widgets/create_entry_message_box.py new file mode 100644 index 00000000..61dfe593 --- /dev/null +++ b/vrct_gui/main_window/widgets/create_entry_message_box.py @@ -0,0 +1,21 @@ + +from customtkinter import CTkFont, CTkFrame, CTkEntry + +def createEntryMessageBox(settings, main_window): + main_window.main_entry_message_container = CTkFrame(main_window.main_bg_container, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) + main_window.main_entry_message_container.grid(row=2, column=0, sticky="ew") + + + main_window.main_entry_message_container.columnconfigure(0, weight=1) + main_window.entry_message_box = CTkEntry( + main_window.main_entry_message_container, + border_color=settings.ctm.TEXTBOX_ENTRY_BORDER_COLOR, + fg_color=settings.ctm.TEXTBOX_ENTRY_BG_COLOR, + text_color=settings.ctm.TEXTBOX_ENTRY_TEXT_COLOR, + placeholder_text="Enter your message...", + placeholder_text_color=settings.ctm.TEXTBOX_ENTRY_PLACEHOLDER_COLOR, + height=settings.uism.TEXTBOX_ENTRY_HEIGHT, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_ENTRY_FONT_SIZE, weight="normal"), + ) + main_window.entry_message_box.grid(row=0, column=0, padx=settings.uism.TEXTBOX_ENTRY_PADX, pady=settings.uism.TEXTBOX_ENTRY_PADY, sticky="nsew") + main_window.entry_message_box._entry.grid(padx=settings.uism.TEXTBOX_ENTRY_IPADX, pady=settings.uism.TEXTBOX_ENTRY_IPADY) diff --git a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py new file mode 100644 index 00000000..97414c70 --- /dev/null +++ b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py @@ -0,0 +1,58 @@ + +from customtkinter import CTkFrame, CTkLabel, CTkImage + +from ...ui_utils import getImageFileFromUiUtils, bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction + +from .create_sidebar import createSidebar + + +def createMinimizeSidebarButton(settings, main_window): + + def enableCompactMode(e): + settings.IS_SIDEBAR_COMPACT_MODE = True + main_window.minimize_sidebar_button_container.destroy() + createMinimizeSidebarButton(settings, main_window) + main_window.sidebar_bg_container.destroy() + createSidebar(settings, main_window) + + def disableCompactMode(e): + settings.IS_SIDEBAR_COMPACT_MODE = False + main_window.minimize_sidebar_button_container.destroy() + createMinimizeSidebarButton(settings, main_window) + main_window.sidebar_bg_container.destroy() + createSidebar(settings, main_window) + + + main_window.minimize_sidebar_button_container = CTkFrame(main_window.main_topbar_container, corner_radius=0, fg_color=settings.ctm.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR, cursor="hand2", width=0, height=0) + + + + main_window.minimize_sidebar_button = CTkLabel( + main_window.minimize_sidebar_button_container, + text=None, + corner_radius=0, + height=0, + image=CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) + ) + + + if settings.IS_SIDEBAR_COMPACT_MODE is True: + image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT).rotate(180),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) + bindButtonReleaseFunction([main_window.minimize_sidebar_button_container, main_window.minimize_sidebar_button], disableCompactMode) + + else: + image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) + bindButtonReleaseFunction([main_window.minimize_sidebar_button_container, main_window.minimize_sidebar_button], enableCompactMode) + + main_window.minimize_sidebar_button_container.grid_rowconfigure((0,2), weight=1) + main_window.minimize_sidebar_button.configure(image=image_file) + main_window.minimize_sidebar_button_container.grid(row=0, column=0, sticky="nsw") + main_window.minimize_sidebar_button.grid(row=1, column=0, padx=0, pady=0) + + + bindEnterAndLeaveColor([main_window.minimize_sidebar_button, main_window.minimize_sidebar_button_container], settings.ctm.MINIMIZE_SIDEBAR_BUTTON_HOVERED_BG_COLOR, settings.ctm.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR) + bindButtonPressColor([main_window.minimize_sidebar_button, main_window.minimize_sidebar_button_container], settings.ctm.MINIMIZE_SIDEBAR_BUTTON_CLICKED_BG_COLOR, settings.ctm.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR) + + + + diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py new file mode 100644 index 00000000..1b290954 --- /dev/null +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -0,0 +1,622 @@ +from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkSwitch, CTkImage, StringVar + +from ...ui_utils import getImageFileFromUiUtils, openImageKeepAspectRatio, retag, getLatestHeight, bindEnterAndLeaveColor, bindButtonPressColor, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressAndReleaseFunction, setDefaultActiveTab, bindButtonFunctionAndColor, switchActiveTabAndPassiveTab, switchTabsColor + +from time import sleep + + +def createSidebar(settings, main_window): + from vrct_gui import vrct_gui + changeMainWindowWidgetsStatus = vrct_gui.changeMainWindowWidgetsStatus + + + + + + def toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, mark): + mark.place(relx=0.85) if is_turned_on else mark.place(relx=-1) + + + def toggleTranslationFeature(): + is_turned_on = getattr(main_window, "translation_switch_box").get() + print(is_turned_on) + toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.translation_selected_mark) + toggleTranslationFeatures = [toggleTranslationFeature] + + def toggleTranscriptionSendFeature(): + is_turned_on = getattr(main_window, "transcription_send_switch_box").get() + print(is_turned_on) + toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_send_selected_mark) + if is_turned_on is True: + changeMainWindowWidgetsStatus("disabled", "All") + sleep(1.5) + changeMainWindowWidgetsStatus("normal", "All") + + def toggleTranscriptionReceiveFeature(): + is_turned_on = getattr(main_window, "transcription_receive_switch_box").get() + print(is_turned_on) + toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_receive_selected_mark) + if is_turned_on is True: + changeMainWindowWidgetsStatus("disabled", "All") + sleep(1.5) + changeMainWindowWidgetsStatus("normal", "All") + + + def toggleForegroundFeature(): + is_turned_on = getattr(main_window, "foreground_switch_box").get() + print(is_turned_on) + toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.foreground_selected_mark) + + + + + def toggleTranslationSwitchBox(e): + main_window.translation_switch_box.toggle() + + def toggleTranscriptionSendSwitchBox(e): + main_window.transcription_send_switch_box.toggle() + + def toggleTranscriptionReceiveSwitchBox(e): + main_window.transcription_receive_switch_box.toggle() + + def toggleForegroundSwitchBox(e): + main_window.foreground_switch_box.toggle() + + + def changeSidebarFeaturesColorByEvents(ww, event_name): + target_frame_widget = getattr(main_window, ww) + if event_name == "enter": + target_frame_widget.configure(fg_color=settings.ctm.SF__HOVERED_BG_COLOR) + target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_HOVERED_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR) + target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_HOVERED_BG_COLOR) + + elif event_name == "leave": + target_frame_widget.configure(fg_color=settings.ctm.SF__BG_COLOR) + target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR) + target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) + + elif event_name == "button_press": + target_frame_widget.configure(fg_color=settings.ctm.SF__CLICKED_BG_COLOR) + target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_CLICKED_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR) + target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_CLICKED_BG_COLOR) + + elif event_name == "button_release": + target_frame_widget.configure(fg_color=settings.ctm.SF__BG_COLOR) + target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR) + target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) + + + + + + def switchActiveAndPassivePresetsTabsColor(target_active_widget): + quick_setting_tabs = [ + getattr(main_window, "sqls__presets_button_1"), + getattr(main_window, "sqls__presets_button_2"), + getattr(main_window, "sqls__presets_button_3") + ] + + switchTabsColor( + target_widget=target_active_widget, + tab_buttons=quick_setting_tabs, + active_bg_color=settings.ctm.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR, + active_text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR, + passive_bg_color=settings.ctm.SIDEBAR_BG_COLOR, + passive_text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE + ) + + def switchPresetTabFunction(target_active_widget): + switchActiveAndPassivePresetsTabsColor(target_active_widget) + switchActiveTabAndPassiveTab(target_active_widget, main_window.current_active_preset_tab, main_window.current_active_preset_tab.passive_function, settings.ctm.SQLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SQLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR) + + main_window.sqls__optionmenu_your_language.configure(variable=StringVar(value=main_window.YOUR_LANGUAGE)) + main_window.sqls__optionmenu_target_language.configure(variable=StringVar(value=main_window.TARGET_LANGUAGE)) + main_window.current_active_preset_tab = target_active_widget + + + + + + + + def switchToPreset1(e): + print("1") + main_window.YOUR_LANGUAGE = "Japanese\n(Japan)" + main_window.TARGET_LANGUAGE = "English\n(United States)" + target_active_widget = getattr(main_window, "sqls__presets_button_1") + switchPresetTabFunction(target_active_widget) + + def switchToPreset2(e): + print("2") + main_window.YOUR_LANGUAGE = "English\n(United States)" + main_window.TARGET_LANGUAGE = "Japanese\n(Japan)" + target_active_widget = getattr(main_window, "sqls__presets_button_2") + switchPresetTabFunction(target_active_widget) + + def switchToPreset3(e): + print("3") + main_window.YOUR_LANGUAGE = "Japanese\n(Japan)" + main_window.TARGET_LANGUAGE = "Chinese, Cantonese\n(Traditional Hong Kong)" + target_active_widget = getattr(main_window, "sqls__presets_button_3") + switchPresetTabFunction(target_active_widget) + + + + + + def createOption_DropdownMenu_for_quickSettings(setattr_obj, parent_widget, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values=None, width:int = 200, font_size:int = 10, text_color="white", command=None, variable=""): + setattr(setattr_obj, optionmenu_attr_name, CTkOptionMenu( + parent_widget, + height=30, + width=width, + values=dropdown_menu_values, + button_color=settings.ctm.SQLS__DROPDOWN_MENU_BG_COLOR, + fg_color=settings.ctm.SQLS__DROPDOWN_MENU_BG_COLOR, + text_color=text_color, + font=CTkFont(family=settings.FONT_FAMILY, size=font_size, weight="normal"), + variable=variable, + anchor="center", + )) + target_optionmenu_attr = getattr(setattr_obj, optionmenu_attr_name) + target_optionmenu_attr.grid(row=0, column=0, sticky="e") + + + + + def createQuickLanguageSettingBox(parent_widget, title_text, title_text_attr_name, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values, variable): + sqls__box = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.SQLS__BOX_BG_COLOR, width=0, height=0) + + sqls__box.columnconfigure((0,2), weight=1) + + sqls__box_wrapper = CTkFrame(sqls__box, corner_radius=0, fg_color=settings.ctm.SQLS__BOX_BG_COLOR, width=0, height=0) + sqls__box_wrapper.grid(row=2, column=1, padx=0, pady=settings.uism.SQLS__BOX_IPADY) + + sqls__label = CTkLabel( + sqls__box_wrapper, + text=title_text, + height=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SQLS__BOX_SECTION_TITLE_FONT_SIZE, weight="normal"), + text_color=settings.ctm.SQLS__BOX_SECTION_TITLE_TEXT_COLOR + ) + sqls__label.grid(row=0, column=0, pady=(0,settings.uism.SQLS__BOX_SECTION_TITLE_BOTTOM_PADY)) + setattr(main_window, title_text_attr_name, sqls__label) + + + + + createOption_DropdownMenu_for_quickSettings( + main_window, + sqls__box_wrapper, + optionmenu_attr_name, + dropdown_menu_attr_name, + dropdown_menu_values=dropdown_menu_values, + # command=self.fakeCommand, + width=settings.uism.SQLS__BOX_DROPDOWN_MENU_WIDTH, + font_size=settings.uism.SQLS__BOX_DROPDOWN_MENU_FONT_SIZE, + text_color=settings.ctm.LABELS_TEXT_COLOR, + variable=variable, + # variable=StringVar(value="Chinese, Cantonese\n(Traditional Hong Kong)"), + ) + getattr(main_window, optionmenu_attr_name).grid(row=1, column=0, padx=0, pady=0) + + return sqls__box + + + + + + + # Side Bar Container + main_window.sidebar_bg_container = CTkFrame(main_window, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + main_window.sidebar_bg_container.grid(row=0, column=0, sticky="nsew") + + + MIN_SIDEBAR_WIDTH = settings.uism.COMPACT_MODE_SIDEBAR_WIDTH if settings.IS_SIDEBAR_COMPACT_MODE is True else settings.uism.SIDEBAR_WIDTH + main_window.sidebar_bg_container.grid_columnconfigure(0, weight=0, minsize=MIN_SIDEBAR_WIDTH) + main_window.grid_rowconfigure(0, weight=1) + + + (img, width, height) = openImageKeepAspectRatio(settings.image_filename.VRCT_LOGO, settings.uism.SF__LOGO_MAX_SIZE) + main_window.sidebar_logo = CTkLabel( + main_window.sidebar_bg_container, + fg_color=settings.ctm.SIDEBAR_BG_COLOR, + text=None, + height=0, + image=CTkImage(img, size=(width,height)) + ) + + # "height-a_s" represents the adjustment in size, and the "pady+a_s/2" indicates a padding increase of 4 pixels to compensate for the reduction. + a_s = settings.uism.SF__LOGO_HEIGHT_FOR_ADJUSTMENT + (img, width, height) = openImageKeepAspectRatio(settings.image_filename.VRCT_LOGO_MARK, height-a_s) + main_window.sidebar_compact_mode_logo = CTkLabel( + main_window.sidebar_bg_container, + fg_color=settings.ctm.SIDEBAR_BG_COLOR, + text=None, + height=0, + image=CTkImage(img, size=(width,height)) + ) + + if settings.IS_SIDEBAR_COMPACT_MODE is True: + main_window.sidebar_compact_mode_logo.grid(row=0, column=0, pady=( + int(settings.uism.SF__LOGO_PADY[0]+a_s/2), + int(settings.uism.SF__LOGO_PADY[1]+a_s/2) + )) + else: + main_window.sidebar_logo.grid(row=0, column=0, pady=settings.uism.SF__LOGO_PADY) + + # Sidebar Features + main_window.sidebar_features_container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color="transparent", width=0, height=0) + main_window.sidebar_features_container.grid(row=1, column=0, pady=0, sticky="ew") + main_window.sidebar_features_container.grid_columnconfigure(0, weight=1) + + + + sidebar_features_settings = [ + { + "frame_attr_name": "translation_frame", + "command": lambda:[func() for func in toggleTranslationFeatures], + "switch_box_attr_name": "translation_switch_box", + "toggle_switch_box_command": toggleTranslationSwitchBox, + "label_attr_name": "label_translation", + "compact_mode_icon_attr_name": "translation_compact_mode_icon", + "selected_mark_attr_name": "translation_selected_mark", + "text": "Translation", + "icon_file_name": settings.image_filename.TRANSLATION_ICON, + }, + { + "frame_attr_name": "transcription_send_frame", + "command": toggleTranscriptionSendFeature, + "switch_box_attr_name": "transcription_send_switch_box", + "toggle_switch_box_command": toggleTranscriptionSendSwitchBox, + "label_attr_name": "label_transcription_send", + "compact_mode_icon_attr_name": "transcription_send_compact_mode_icon", + "selected_mark_attr_name": "transcription_send_selected_mark", + "text": "Voice2chatbox", + "icon_file_name": settings.image_filename.MIC_ICON, + }, + { + "frame_attr_name": "transcription_receive_frame", + "command": toggleTranscriptionReceiveFeature, + "switch_box_attr_name": "transcription_receive_switch_box", + "toggle_switch_box_command": toggleTranscriptionReceiveSwitchBox, + "label_attr_name": "label_transcription_receive", + "compact_mode_icon_attr_name": "transcription_receive_compact_mode_icon", + "selected_mark_attr_name": "transcription_receive_selected_mark", + "text": "Speaker2chatbox", + "icon_file_name": settings.image_filename.HEADPHONES_ICON, + }, + { + "frame_attr_name": "foreground_frame", + "command": toggleForegroundFeature, + "switch_box_attr_name": "foreground_switch_box", + "toggle_switch_box_command": toggleForegroundSwitchBox, + "label_attr_name": "label_foreground", + "compact_mode_icon_attr_name": "foreground_compact_mode_icon", + "selected_mark_attr_name": "foreground_selected_mark", + "text": "Foreground", + "icon_file_name": settings.image_filename.FOREGROUND_ICON, + }, + ] + + + + row=0 + for sfs in sidebar_features_settings: + frame_attr_name = sfs["frame_attr_name"] + command = sfs["command"] + switch_box_attr_name = sfs["switch_box_attr_name"] + toggle_switch_box_command = sfs["toggle_switch_box_command"] + label_attr_name = sfs["label_attr_name"] + compact_mode_icon_attr_name = sfs["compact_mode_icon_attr_name"] + selected_mark_attr_name = sfs["selected_mark_attr_name"] + text = sfs["text"] + icon_file_name = sfs["icon_file_name"] + + frame_widget = CTkFrame(main_window.sidebar_features_container, corner_radius=0, fg_color=settings.ctm.SF__BG_COLOR, cursor="hand2", width=0, height=0) + setattr(main_window, frame_attr_name, frame_widget) + + frame_widget.grid(row=row, column=0, pady=(0,1), sticky="ew") + frame_widget.grid_columnconfigure(0, weight=1) + + label_widget = CTkLabel( + frame_widget, + text=text, + height=0, + corner_radius=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SF__LABEL_FONT_SIZE, weight="normal"), + anchor="w", + text_color=settings.ctm.LABELS_TEXT_COLOR, + ) + setattr(main_window, label_attr_name, label_widget) + + + switch_box_widget = CTkSwitch( + frame_widget, + text=None, + height=0, + width=0, + corner_radius=int(settings.uism.SF__SWITCH_BOX_HEIGHT/2), + border_width=0, + switch_height=settings.uism.SF__SWITCH_BOX_HEIGHT, + switch_width=settings.uism.SF__SWITCH_BOX_WIDTH, + onvalue=True, + offvalue=False, + command=command, + fg_color=settings.ctm.SF__SWITCH_BOX_BG_COLOR, + bg_color=settings.ctm.SF__BG_COLOR, + progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR, + ) + # # if sfs["is_checked"] is True: + # # target_attr.select() + # # else: + # # target_attr.deselect() + setattr(main_window, switch_box_attr_name, switch_box_widget) + + + if settings.COMPACT_MODE_ICON_SIZE == 0: + label_widget.grid(row=row, column=0, pady=settings.uism.SF__LABELS_IPADY, padx=(settings.uism.SF__LABEL_LEFT_PAD,0), sticky="ew") + settings.COMPACT_MODE_ICON_SIZE = int(getLatestHeight(frame_widget) - settings.uism.SF__COMPACT_MODE_ICON_PADY*2) + label_widget.grid_remove() + + + # for compact mode + compact_mode_icon_widget = CTkLabel( + frame_widget, + text=None, + height=0, + corner_radius=0, + image=CTkImage(getImageFileFromUiUtils(icon_file_name),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)), + ) + setattr(main_window, compact_mode_icon_attr_name, compact_mode_icon_widget) + + selected_mark_widget = CTkFrame(frame_widget, corner_radius=0, fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR, width=settings.uism.SF__SELECTED_MARK_WIDTH, height=0) + setattr(main_window, selected_mark_attr_name, selected_mark_widget) + + + # Arrange + if settings.IS_SIDEBAR_COMPACT_MODE is True: + compact_mode_icon_widget.grid(row=row, column=0, pady=settings.uism.SF__COMPACT_MODE_ICON_PADY) + selected_mark_widget.place(relx=-1, rely=0.5, relheight=0.75, anchor="center") + else: + label_widget.grid(row=row, column=0, pady=settings.uism.SF__LABELS_IPADY, padx=(settings.uism.SF__LABEL_LEFT_PAD,0), sticky="ew") + switch_box_widget.grid(row=row, column=0, padx=(0,settings.uism.SF__SWITCH_BOX_RIGHT_PAD), sticky="e") + + + # Unbind the event "" originally set up by the widget to manually control it. + switch_box_widget._canvas.unbind("") + switch_box_widget._text_label.unbind("") + + bindButtonReleaseFunction([compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], toggle_switch_box_command) + + retag("vrct_"+frame_attr_name, compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget) + + def commonEventFunction(e, event_type): + for ww in e.widget.master.bindtags(): + if ww.startswith("vrct_"): + ww = ww.replace("vrct_", "") + changeSidebarFeaturesColorByEvents(ww, event_type) + break + + def enterFunction(e): + commonEventFunction(e, "enter") + + def leaveFunction(e): + commonEventFunction(e, "leave") + + def buttonPressFunction(e): + commonEventFunction(e, "button_press") + + def buttonReleasedFunction(e): + commonEventFunction(e, "button_release") + + bindEnterAndLeaveFunction([compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], enterFunction, leaveFunction) + + bindButtonPressAndReleaseFunction([compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], buttonPressFunction, buttonReleasedFunction) + + row+=1 + + + + + + # Sidebar Quick Language Settings, SQLS + main_window.sqls__container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + + if settings.IS_SIDEBAR_COMPACT_MODE is False: + main_window.sqls__container.grid(row=2, column=0, sticky="new") + + main_window.sqls__container.grid_columnconfigure(0, weight=1) + + + main_window.sqls__container_title = CTkLabel(main_window.sqls__container, + # text="言語設定", + text="Language Settings", + height=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SQLS__TITLE_FONT_SIZE, weight="normal"), + text_color=settings.ctm.SQLS__TITLE_TEXT_COLOR + ) + main_window.sqls__container_title.grid(row=0, column=0, pady=settings.uism.SQLS__TITLE_PADY, sticky="nsew") + + + + # Presets buttons + main_window.sidebar_bg_container.grid_rowconfigure(2, weight=1) + main_window.sqls__presets_buttons_box = CTkFrame(main_window.sqls__container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + main_window.sqls__presets_buttons_box.grid(row=1, column=0, sticky="ew") + + main_window.sqls__presets_buttons_box.grid_columnconfigure((0,1,2), weight=1) + + + preset_tabs_settings = [ + { + "preset_tab_attr_name": "sqls__presets_button_1", + "command": switchToPreset1, + "text": "1", + }, + { + "preset_tab_attr_name": "sqls__presets_button_2", + "command": switchToPreset2, + "text": "2", + }, + { + "preset_tab_attr_name": "sqls__presets_button_3", + "command": switchToPreset3, + "text": "3", + }, + ] + + column=0 + for preset_tab_settings in preset_tabs_settings: + preset_tab_attr_name = preset_tab_settings["preset_tab_attr_name"] + command = preset_tab_settings["command"] + text = preset_tab_settings["text"] + + setattr( + main_window, + preset_tab_attr_name, + CTkFrame( + main_window.sqls__presets_buttons_box, + corner_radius=0, + fg_color=settings.ctm.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR, + width=0, + height=30, + cursor="hand2", + ) + ) + parent_widget = getattr(main_window, preset_tab_attr_name) + parent_widget.grid(row=0, column=column, sticky="ew") + + label_widget = CTkLabel( + parent_widget, + text=text, + height=0, + fg_color=settings.ctm.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SQLS__PRESET_TAB_NUMBER_FONT_SIZE, weight="bold"), + anchor="center", + text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE + ) + label_widget.place(relx=0.5, rely=0.5, anchor="center") + + + + bindEnterAndLeaveColor([parent_widget, label_widget], settings.ctm.SQLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR) + bindButtonPressColor([parent_widget, label_widget], settings.ctm.SQLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR) + + parent_widget.passive_function = command + bindButtonReleaseFunction([parent_widget, label_widget], command) + + column+=1 + + # Set default active preset tab + main_window.current_active_preset_tab = getattr(main_window, "sqls__presets_button_1") + setDefaultActiveTab( + active_tab_widget=main_window.current_active_preset_tab, + active_bg_color=settings.ctm.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR, + active_text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR + ) + + + # Quick Language settings BOX + main_window.sqls__box_frame = CTkFrame(main_window.sqls__container, corner_radius=0, fg_color=settings.ctm.SQLS__BG_COLOR, width=0, height=0) + main_window.sqls__box_frame.grid(row=2, column=0, sticky="ew") + main_window.sqls__box_frame.grid_columnconfigure(0, weight=1) + + # Your language + main_window.sqls__box_your_language = createQuickLanguageSettingBox( + parent_widget=main_window.sqls__box_frame, + # title_text="あなたの言語", + title_text="Your Language", + title_text_attr_name="sqls__title_text_your_language", + optionmenu_attr_name="sqls__optionmenu_your_language", + dropdown_menu_attr_name="sqls__dropdown_menu_your_language", + dropdown_menu_values=["1""2","pppp\npppp"], + variable=StringVar(value=main_window.YOUR_LANGUAGE) + ) + main_window.sqls__box_your_language.grid(row=2, column=0, padx=0, pady=(settings.uism.SQLS__BOX_TOP_PADY,0),sticky="ew") + + + # Both direction arrow icon + main_window.sqls__arrow_direction_box = CTkFrame(main_window.sqls__box_frame, corner_radius=0, fg_color=settings.ctm.SQLS__BG_COLOR, width=0, height=0) + main_window.sqls__arrow_direction_box.grid(row=3, column=0, padx=0, pady=settings.uism.SQLS__BOX_ARROWS_PADY,sticky="ew") + + main_window.sqls__arrow_direction_box.grid_columnconfigure((0,4), weight=1) + + main_window.sqls__both_direction_up = CTkLabel( + main_window.sqls__arrow_direction_box, + text=None, + height=0, + image=CTkImage(getImageFileFromUiUtils(settings.image_filename.NARROW_ARROW_DOWN).rotate(180),size=settings.uism.SQLS__BOX_ARROWS_IMAGE_SIZE) + + ) + main_window.sqls__both_direction_up.grid(row=0, column=1, pady=0) + + main_window.sqls__both_direction_desc = CTkLabel( + main_window.sqls__arrow_direction_box, + # text="双方向に翻訳", + text="Translate Each Other", + height=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SQLS__BOX_ARROWS_DESC_FONT_SIZE, weight="normal"), + text_color=settings.ctm.SQLS__BOX_ARROWS_TEXT_COLOR, + ) + main_window.sqls__both_direction_desc.grid(row=0, column=2, padx=settings.uism.SQLS__BOX_ARROWS_DESC_PADX, pady=0) + + main_window.sqls__both_direction_label_down = CTkLabel( + main_window.sqls__arrow_direction_box, + text=None, + height=0, + image=CTkImage(getImageFileFromUiUtils(settings.image_filename.NARROW_ARROW_DOWN).rotate(0),size=settings.uism.SQLS__BOX_ARROWS_IMAGE_SIZE) + + ) + main_window.sqls__both_direction_label_down.grid(row=0, column=3, pady=0) + + + + # Target language + main_window.sqls__box_target_language = createQuickLanguageSettingBox( + parent_widget=main_window.sqls__box_frame, + # title_text="相手の言語", + title_text="Target Language", + title_text_attr_name="sqls__title_text_target_language", + optionmenu_attr_name="sqls__optionmenu_target_language", + dropdown_menu_attr_name="sqls__dropdown_menu_target_language", + dropdown_menu_values=["1""2","pppp\npppp2"], + variable=StringVar(value=main_window.TARGET_LANGUAGE) + ) + main_window.sqls__box_target_language.grid(row=4, column=0, padx=0, pady=(0,0),sticky="ew") + + + + + + # Config Button + main_window.sidebar_config_button_container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, width=0, height=0) + main_window.sidebar_config_button_container.grid(row=3, column=0, sticky="ew") + + + main_window.sidebar_config_button_container.grid_columnconfigure(0, weight=1) + main_window.sidebar_config_button_wrapper = CTkFrame(main_window.sidebar_config_button_container, corner_radius=settings.uism.SIDEBAR_CONFIG_BUTTON_CORNER_RADIUS, fg_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, height=0, width=0, cursor="hand2") + main_window.sidebar_config_button_wrapper.grid(row=0, column=0, padx=settings.uism.SIDEBAR_CONFIG_BUTTON_PADX, pady=settings.uism.SIDEBAR_CONFIG_BUTTON_PADY, sticky="ew") + + + + + main_window.sidebar_config_button_wrapper.grid_columnconfigure(0, weight=1) + + settings.uism.CONFIG_BUTTON_PADX= 0 + main_window.sidebar_config_button = CTkLabel( + main_window.sidebar_config_button_wrapper, + text=None, + height=0, + image=CTkImage(getImageFileFromUiUtils(settings.image_filename.CONFIGURATION_ICON),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)) + ) + main_window.sidebar_config_button.grid(row=0, column=0, padx=0, pady=settings.uism.SIDEBAR_CONFIG_BUTTON_IPADY) + + + bindButtonFunctionAndColor( + target_widgets=[main_window.sidebar_config_button_wrapper, main_window.sidebar_config_button], + enter_color=settings.ctm.CONFIG_BUTTON_HOVERED_BG_COLOR, + leave_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, + clicked_color=settings.ctm.CONFIG_BUTTON_CLICKED_BG_COLOR, + buttonReleasedFunction=main_window.openConfigWindow, + ) + diff --git a/vrct_gui/main_window/widgets/create_textbox.py b/vrct_gui/main_window/widgets/create_textbox.py new file mode 100644 index 00000000..1bc4a05b --- /dev/null +++ b/vrct_gui/main_window/widgets/create_textbox.py @@ -0,0 +1,177 @@ +from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkTextbox + +from ...ui_utils import getLatestWidth, getLongestText, bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, setDefaultActiveTab, switchActiveTabAndPassiveTab, switchTabsColor + + +def createTextbox(settings, main_window): + + def switchTextbox(target_textbox_attr_name): + main_window.current_active_textbox.grid_remove() + main_window.current_active_textbox = getattr(main_window, target_textbox_attr_name) + main_window.current_active_textbox.grid() + + def switchToTextboxAll(e): + print("switchToTextboxAll") + target_active_widget = getattr(main_window, "textbox_tab_all") + switchTextboxTabFunction(target_active_widget) + switchTextbox("textbox_all") + + def switchToTextboxSent(e): + print("switchToTextboxSent") + target_active_widget = getattr(main_window, "textbox_tab_sent") + switchTextboxTabFunction(target_active_widget) + switchTextbox("textbox_sent") + + def switchToTextboxReceived(e): + print("switchToTextboxReceived") + target_active_widget = getattr(main_window, "textbox_tab_received") + switchTextboxTabFunction(target_active_widget) + switchTextbox("textbox_received") + + def switchToTextboxSystem(e): + print("switchToTextboxSystem") + target_active_widget = getattr(main_window, "textbox_tab_system") + switchTextboxTabFunction(target_active_widget) + switchTextbox("textbox_system") + + + def switchTextboxTabFunction(target_active_widget): + switchActiveAndPassiveTextboxTabsColor(target_active_widget) + switchActiveTabAndPassiveTab(target_active_widget, main_window.current_active_textbox_tab, main_window.current_active_textbox_tab.passive_function, settings.ctm.TEXTBOX_TAB_BG_HOVERED_COLOR, settings.ctm.TEXTBOX_TAB_BG_CLICKED_COLOR, settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR) + main_window.current_active_textbox_tab = target_active_widget + + def switchActiveAndPassiveTextboxTabsColor(target_active_widget): + textbox_tabs = [ + getattr(main_window, "textbox_tab_all"), + getattr(main_window, "textbox_tab_sent"), + getattr(main_window, "textbox_tab_received"), + getattr(main_window, "textbox_tab_system") + ] + + switchTabsColor( + target_widget=target_active_widget, + tab_buttons=textbox_tabs, + active_bg_color=settings.ctm.TEXTBOX_BG_COLOR, + active_text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR, + passive_bg_color=settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR, + passive_text_color=settings.ctm.TEXTBOX_TAB_TEXT_PASSIVE_COLOR + ) + + + + + # Text box + main_window.main_bg_container.grid_rowconfigure(1, weight=1) + main_window.main_textbox_container = CTkFrame(main_window.main_bg_container, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) + main_window.main_textbox_container.grid(row=1, column=0, sticky="nsew") + + main_window.main_textbox_container.columnconfigure(0,weight=1) + main_window.main_textbox_container.rowconfigure(0,weight=1) + + main_window.textbox_switch_tabs_container = CTkFrame(main_window.main_topbar_center_container, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) + main_window.textbox_switch_tabs_container.place(relx=0.07, rely=1.15, anchor="sw") + + main_window.textbox_switch_tabs_container.grid_columnconfigure((0,1,2,3), weight=1, uniform="textbox_tabs") + + textbox_settings = [ + { + "textbox_tab_attr_name": "textbox_tab_all", + "command": switchToTextboxAll, + "textbox_attr_name": "textbox_all", + "text": "All", + }, + { + "textbox_tab_attr_name": "textbox_tab_sent", + "command": switchToTextboxSent, + "textbox_attr_name": "textbox_sent", + "text": "Sent", + }, + + { + "textbox_tab_attr_name": "textbox_tab_received", + "command": switchToTextboxReceived, + "textbox_attr_name": "textbox_received", + "text": "Received", + }, + + { + "textbox_tab_attr_name": "textbox_tab_system", + "command": switchToTextboxSystem, + "textbox_attr_name": "textbox_system", + "text": "System", + }, + ] + + + longest_text = getLongestText(textbox_settings) + + setattr(main_window, "_mlw", CTkLabel( + main_window, + text=longest_text, + corner_radius=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_TAB_FONT_SIZE, weight="normal"), + height=0, + anchor="center", + )) + label_widget = getattr(main_window, "_mlw") + label_widget.grid() + MAX_LABEL_WIDTH = getLatestWidth(label_widget) + label_widget.grid_remove() + + + column=0 + for textbox_setting in textbox_settings: + setattr(main_window, textbox_setting["textbox_tab_attr_name"], CTkFrame(main_window.textbox_switch_tabs_container, corner_radius=settings.uism.TEXTBOX_TAB_CORNER_RADIUS, fg_color=settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR, cursor="hand2", width=0, height=0) + ) + target_widget = getattr(main_window, textbox_setting["textbox_tab_attr_name"]) + target_widget.grid(row=0, column=column, pady=0, padx=(0,2), sticky="ew") + + + + setattr(main_window, "label_widget", CTkLabel( + target_widget, + text=textbox_setting["text"], + corner_radius=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_TAB_FONT_SIZE, weight="normal"), + height=0, + width=MAX_LABEL_WIDTH, + anchor="center", + text_color=settings.ctm.TEXTBOX_TAB_TEXT_PASSIVE_COLOR, + )) + label_widget = getattr(main_window, "label_widget") + label_widget.grid(row=0, column=column) + + label_widget.grid(row=0, column=column, pady=settings.uism.TEXTBOX_TAB_PADY, padx=settings.uism.TEXTBOX_TAB_PADX) + + bindEnterAndLeaveColor([target_widget, label_widget], settings.ctm.TEXTBOX_TAB_BG_HOVERED_COLOR, settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR) + bindButtonPressColor([target_widget, label_widget], settings.ctm.TEXTBOX_TAB_BG_CLICKED_COLOR, settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR) + + target_widget.passive_function = textbox_setting["command"] + bindButtonReleaseFunction([target_widget, label_widget], textbox_setting["command"]) + + + + setattr(main_window, textbox_setting["textbox_attr_name"], CTkTextbox( + main_window.main_textbox_container, + corner_radius=settings.uism.TEXTBOX_CORNER_RADIUS, + fg_color=settings.ctm.TEXTBOX_BG_COLOR, + text_color="lime", # Textbox's text_color is set when printing. so this is for prevent from non-setting text_color like the gloves used in food factories are blue. + )) + textbox_widget = getattr(main_window, textbox_setting["textbox_attr_name"]) + textbox_widget.grid(row=0, column=0, padx=settings.uism.TEXTBOX_PADX, pady=0, sticky="nsew") + textbox_widget.grid_remove() + textbox_widget.configure(state="disabled") + # print_textbox(main_window, textbox_widget, "send", textbox_setting["textbox_attr_name"], "SEND") + + column+=1 + + # Set default active textbox tab + main_window.current_active_textbox_tab = getattr(main_window, "textbox_tab_all") + setDefaultActiveTab( + active_tab_widget=main_window.current_active_textbox_tab, + active_bg_color=settings.ctm.TEXTBOX_TAB_BG_ACTIVE_COLOR, + active_text_color=settings.ctm.TEXTBOX_TAB_TEXT_ACTIVE_COLOR + ) + + main_window.current_active_textbox = getattr(main_window, "textbox_all") + main_window.current_active_textbox.grid() diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py new file mode 100644 index 00000000..df0fbe50 --- /dev/null +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -0,0 +1,360 @@ +from types import SimpleNamespace + +class ColorThemeManager(): + def __init__(self, theme): + print(theme) + self.main = SimpleNamespace() + self.config_window = SimpleNamespace() + + self.PRIMARY_100_COLOR = "#c4eac1" + self.PRIMARY_200_COLOR = "#9cdd98" + self.PRIMARY_300_COLOR = "#70d16c" + self.PRIMARY_400_COLOR = "#49c649" + self.PRIMARY_500_COLOR = "#0abb1d" + self.PRIMARY_600_COLOR = "#00ac11" + self.PRIMARY_650_COLOR = "#00A309" + self.PRIMARY_700_COLOR = "#009900" + self.PRIMARY_800_COLOR = "#008800" + self.PRIMARY_900_COLOR = "#006900" + + + self.DARK_100_COLOR = "#f5f7fb" + self.DARK_200_COLOR = "#f1f2f6" + self.DARK_300_COLOR = "#e9eaee" + self.DARK_400_COLOR = "#c7c8cc" + self.DARK_500_COLOR = "#a9aaae" + self.DARK_600_COLOR = "#7f8084" + self.DARK_700_COLOR = "#6a6c6f" + self.DARK_750_COLOR = "#5b5c5f" + self.DARK_800_COLOR = "#4b4c4f" + self.DARK_825_COLOR = "#434447" + self.DARK_850_COLOR = "#3a3b3e" + self.DARK_875_COLOR = "#323336" + self.DARK_888_COLOR = "#2e2f32" + self.DARK_900_COLOR = "#292a2d" + self.DARK_925_COLOR = "#242528" + self.DARK_950_COLOR = "#1f2022" + self.DARK_975_COLOR = "#1a1b1d" + self.DARK_1000_COLOR = "#151517" # THE DARKEST COLOR + + + self.LIGHT_100_COLOR = "#f2f2f2" # THE LIGHTEST COLOR + self.LIGHT_200_COLOR = "#e9e9e9" + self.LIGHT_250_COLOR = "#e1e1e1" + self.LIGHT_300_COLOR = "#d9d9d9" + self.LIGHT_325_COLOR = "#d0d0d0" + self.LIGHT_350_COLOR = "#c7c7c7" + self.LIGHT_375_COLOR = "#bebebe" + self.LIGHT_400_COLOR = "#b5b5b5" + self.LIGHT_450_COLOR = "#a5a5a5" + self.LIGHT_500_COLOR = "#959595" + self.LIGHT_600_COLOR = "#6d6d6d" + self.LIGHT_700_COLOR = "#5a5a5a" + self.LIGHT_750_COLOR = "#515151" + self.LIGHT_800_COLOR = "#3b3b3b" + self.LIGHT_850_COLOR = "#323232" + self.LIGHT_875_COLOR = "#2b2b2b" + self.LIGHT_900_COLOR = "#1b1b1b" + # self.LIGHT_925_COLOR = "#121212" + # self.LIGHT_950_COLOR = "#0c0c0c" + # self.LIGHT_975_COLOR = "#070707" + self.LIGHT_1000_COLOR = "#010101" + + + if theme == "Dark": + self._createDarkModeColor() + elif theme == "Light": + self._createLightModeColor() + + + def _createDarkModeColor(self): + # Common + self.main.BASIC_TEXT_COLOR = self.LIGHT_100_COLOR + self.main.LABELS_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + + # Main + self.main.MAIN_BG_COLOR = self.DARK_900_COLOR + + self.main.TEXTBOX_BG_COLOR = self.DARK_950_COLOR + self.main.TEXTBOX_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + self.main.TEXTBOX_TAB_BG_PASSIVE_COLOR = self.DARK_850_COLOR + self.main.TEXTBOX_TAB_BG_ACTIVE_COLOR = self.main.TEXTBOX_BG_COLOR + self.main.TEXTBOX_TAB_BG_HOVERED_COLOR = self.DARK_800_COLOR + self.main.TEXTBOX_TAB_BG_CLICKED_COLOR = self.DARK_850_COLOR + self.main.TEXTBOX_TAB_TEXT_ACTIVE_COLOR = self.main.BASIC_TEXT_COLOR + self.main.TEXTBOX_TAB_TEXT_PASSIVE_COLOR = self.DARK_500_COLOR + + self.main.TEXTBOX_ENTRY_TEXT_COLOR = self.DARK_300_COLOR + self.main.TEXTBOX_ENTRY_TEXT_DISABLED_COLOR = self.DARK_500_COLOR + self.main.TEXTBOX_ENTRY_BG_COLOR = self.DARK_875_COLOR + self.main.TEXTBOX_ENTRY_BORDER_COLOR = self.DARK_750_COLOR + self.main.TEXTBOX_ENTRY_PLACEHOLDER_COLOR = self.DARK_500_COLOR + self.main.TEXTBOX_ENTRY_PLACEHOLDER_DISABLED_COLOR = self.DARK_700_COLOR + + + + # Sidebar + self.main.SIDEBAR_BG_COLOR = self.DARK_875_COLOR + + # Sidebar Features + self.main.SF__BG_COLOR = self.DARK_850_COLOR + self.main.SF__HOVERED_BG_COLOR = self.DARK_800_COLOR + self.main.SF__CLICKED_BG_COLOR = self.DARK_900_COLOR + self.main.SF__TEXT_DISABLED_COLOR = self.DARK_500_COLOR + + self.main.SF__SWITCH_BOX_BG_COLOR = self.DARK_800_COLOR + self.main.SF__SWITCH_BOX_HOVERED_BG_COLOR = self.DARK_750_COLOR + self.main.SF__SWITCH_BOX_CLICKED_BG_COLOR = self.DARK_850_COLOR + self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR = self.PRIMARY_650_COLOR + self.main.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR = self.PRIMARY_500_COLOR + self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR = self.PRIMARY_700_COLOR + self.main.SF__SWITCH_BOX_DISABLE_BG_COLOR = self.PRIMARY_900_COLOR + + self.main.SF__SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR + self.main.SF__SELECTED_MARK_ACTIVE_HOVERED_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR + self.main.SF__SELECTED_MARK_ACTIVE_CLICKED_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR + self.main.SF__SELECTED_MARK_DISABLE_BG_COLOR = self.main.SF__SWITCH_BOX_DISABLE_BG_COLOR + + + # Sidebar quick settings + self.main.SQLS__TITLE_TEXT_COLOR = self.DARK_400_COLOR + + self.main.SQLS__BG_COLOR = self.DARK_825_COLOR + + self.main.SQLS__PRESETS_TAB_BG_HOVERED_COLOR = self.DARK_850_COLOR + self.main.SQLS__PRESETS_TAB_BG_CLICKED_COLOR = self.DARK_888_COLOR + self.main.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR = self.main.SIDEBAR_BG_COLOR + self.main.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR = self.main.SQLS__BG_COLOR + self.main.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE = self.DARK_600_COLOR + self.main.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + + self.main.SQLS__BOX_BG_COLOR = self.DARK_850_COLOR + self.main.SQLS__BOX_SECTION_TITLE_TEXT_COLOR = self.DARK_400_COLOR + self.main.SQLS__BOX_ARROWS_TEXT_COLOR = self.DARK_500_COLOR + + self.main.SQLS__DROPDOWN_MENU_BG_COLOR = self.DARK_900_COLOR + + + self.main.CONFIG_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR + self.main.CONFIG_BUTTON_HOVERED_BG_COLOR = self.DARK_800_COLOR + self.main.CONFIG_BUTTON_CLICKED_BG_COLOR = self.DARK_900_COLOR + # self.main.CONFIG_BUTTON_DISABLE_COLOR = self.DARK_900_COLOR + + self.main.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR + self.main.MINIMIZE_SIDEBAR_BUTTON_HOVERED_BG_COLOR = self.DARK_800_COLOR + self.main.MINIMIZE_SIDEBAR_BUTTON_CLICKED_BG_COLOR = self.DARK_900_COLOR + # self.main.MINIMIZE_SIDEBAR_BUTTON_DISABLE_COLOR = self.DARK_900_COLOR + + self.main.HELP_AND_INFO_BUTTON_BG_COLOR = self.main.MAIN_BG_COLOR + self.main.HELP_AND_INFO_BUTTON_HOVERED_BG_COLOR = self.DARK_850_COLOR + self.main.HELP_AND_INFO_BUTTON_CLICKED_BG_COLOR = self.DARK_950_COLOR + # self.main.HELP_AND_INFO_BUTTON_DISABLE_COLOR = self.DARK_900_COLOR + + + # Common + self.config_window.BASIC_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + self.config_window.LABELS_TEXT_COLOR = self.config_window.BASIC_TEXT_COLOR + self.config_window.LABELS_DESC_TEXT_COLOR = self.DARK_500_COLOR + + + # Top bar + self.config_window.TOP_BAR_BG_COLOR = self.DARK_850_COLOR + + + # Main + self.config_window.MAIN_BG_COLOR = self.DARK_950_COLOR + + # This is for fake border color + self.config_window.SB__WRAPPER_BG_COLOR = self.DARK_750_COLOR + + self.config_window.SB__BG_COLOR = self.DARK_888_COLOR + + self.config_window.SB__OPTIONMENU_BG_COLOR = self.DARK_925_COLOR + self.config_window.SB__OPTIONMENU_HOVERED_BG_COLOR = self.DARK_850_COLOR + + self.config_window.SB__SLIDER_BUTTON_COLOR = self.DARK_700_COLOR + self.config_window.SB__SLIDER_BUTTON_HOVERED_COLOR = self.DARK_600_COLOR + + self.config_window.SB__SWITCH_BOX_BG_COLOR = self.main.SF__SWITCH_BOX_BG_COLOR + self.config_window.SB__SWITCH_BOX_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR + + self.config_window.SB__CHECKBOX_BORDER_COLOR = self.DARK_500_COLOR + self.config_window.SB__CHECKBOX_HOVER_COLOR = self.DARK_800_COLOR + self.config_window.SB__CHECKBOX_CHECKED_COLOR = self.PRIMARY_700_COLOR + self.config_window.SB__CHECKBOX_CHECKMARK_COLOR = self.config_window.BASIC_TEXT_COLOR + + self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_COLOR = self.PRIMARY_700_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_HOVERED_COLOR = self.PRIMARY_500_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR = self.DARK_800_COLOR + + self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR = self.DARK_800_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_HOVERED_COLOR = self.DARK_700_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_CLICKED_COLOR = self.DARK_900_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR = self.DARK_850_COLOR + + self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_COLOR = self.PRIMARY_700_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_HOVERED_COLOR = self.PRIMARY_600_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_CLICKED_COLOR = self.PRIMARY_900_COLOR + # self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_DISABLED_COLOR = self.PRIMARY_900_COLOR + + + # Side menu + self.config_window.SIDE_MENU_BG_COLOR = self.config_window.MAIN_BG_COLOR + + self.config_window.SIDE_MENU_LABELS_BG_COLOR = self.config_window.SIDE_MENU_BG_COLOR + self.config_window.SIDE_MENU_LABELS_BG_FOR_FAKE_BORDER_COLOR = self.config_window.SIDE_MENU_BG_COLOR + self.config_window.SIDE_MENU_LABELS_HOVERED_BG_COLOR = self.DARK_850_COLOR + self.config_window.SIDE_MENU_LABELS_CLICKED_BG_COLOR = self.PRIMARY_900_COLOR + self.config_window.SIDE_MENU_LABELS_SELECTED_TEXT_COLOR = self.PRIMARY_300_COLOR + + self.config_window.SIDE_MENU_SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR + + + + + + + + + + def _createLightModeColor(self): + # Common + self.main.BASIC_TEXT_COLOR = self.DARK_1000_COLOR + self.main.LABELS_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + + # Main + self.main.MAIN_BG_COLOR = self.LIGHT_300_COLOR + + self.main.TEXTBOX_BG_COLOR = self.LIGHT_200_COLOR + self.main.TEXTBOX_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + self.main.TEXTBOX_TAB_BG_PASSIVE_COLOR = self.LIGHT_350_COLOR + self.main.TEXTBOX_TAB_BG_ACTIVE_COLOR = self.main.TEXTBOX_BG_COLOR + self.main.TEXTBOX_TAB_BG_HOVERED_COLOR = self.LIGHT_300_COLOR + self.main.TEXTBOX_TAB_BG_CLICKED_COLOR = self.LIGHT_350_COLOR + self.main.TEXTBOX_TAB_TEXT_ACTIVE_COLOR = self.main.BASIC_TEXT_COLOR + self.main.TEXTBOX_TAB_TEXT_PASSIVE_COLOR = self.LIGHT_500_COLOR + + self.main.TEXTBOX_ENTRY_TEXT_COLOR = self.LIGHT_800_COLOR + self.main.TEXTBOX_ENTRY_TEXT_DISABLED_COLOR = self.LIGHT_500_COLOR + self.main.TEXTBOX_ENTRY_BG_COLOR = self.LIGHT_325_COLOR + self.main.TEXTBOX_ENTRY_BORDER_COLOR = self.LIGHT_450_COLOR + self.main.TEXTBOX_ENTRY_PLACEHOLDER_COLOR = self.LIGHT_600_COLOR + self.main.TEXTBOX_ENTRY_PLACEHOLDER_DISABLED_COLOR = self.LIGHT_700_COLOR + + + + # Sidebar + self.main.SIDEBAR_BG_COLOR = self.LIGHT_375_COLOR + + # Sidebar Features + self.main.SF__BG_COLOR = self.LIGHT_350_COLOR + self.main.SF__HOVERED_BG_COLOR = self.LIGHT_300_COLOR + self.main.SF__CLICKED_BG_COLOR = self.LIGHT_200_COLOR + self.main.SF__TEXT_DISABLED_COLOR = self.LIGHT_500_COLOR + + self.main.SF__SWITCH_BOX_BG_COLOR = self.LIGHT_300_COLOR + self.main.SF__SWITCH_BOX_HOVERED_BG_COLOR = self.LIGHT_450_COLOR + self.main.SF__SWITCH_BOX_CLICKED_BG_COLOR = self.LIGHT_350_COLOR + self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR = self.PRIMARY_650_COLOR + self.main.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR = self.PRIMARY_500_COLOR + self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR = self.PRIMARY_700_COLOR + self.main.SF__SWITCH_BOX_DISABLE_BG_COLOR = self.PRIMARY_900_COLOR + + self.main.SF__SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR + self.main.SF__SELECTED_MARK_ACTIVE_HOVERED_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR + self.main.SF__SELECTED_MARK_ACTIVE_CLICKED_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR + self.main.SF__SELECTED_MARK_DISABLE_BG_COLOR = self.main.SF__SWITCH_BOX_DISABLE_BG_COLOR + + + # Sidebar quick settings + self.main.SQLS__TITLE_TEXT_COLOR = self.LIGHT_800_COLOR + + self.main.SQLS__BG_COLOR = self.LIGHT_300_COLOR + + self.main.SQLS__PRESETS_TAB_BG_HOVERED_COLOR = self.LIGHT_350_COLOR + self.main.SQLS__PRESETS_TAB_BG_CLICKED_COLOR = self.LIGHT_800_COLOR + self.main.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR = self.main.SIDEBAR_BG_COLOR + self.main.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR = self.main.SQLS__BG_COLOR + self.main.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE = self.LIGHT_600_COLOR + self.main.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + + self.main.SQLS__BOX_BG_COLOR = self.LIGHT_350_COLOR + self.main.SQLS__BOX_SECTION_TITLE_TEXT_COLOR = self.LIGHT_800_COLOR + self.main.SQLS__BOX_ARROWS_TEXT_COLOR = self.LIGHT_700_COLOR + + self.main.SQLS__DROPDOWN_MENU_BG_COLOR = self.LIGHT_500_COLOR + + + self.main.CONFIG_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR + self.main.CONFIG_BUTTON_HOVERED_BG_COLOR = self.LIGHT_800_COLOR + self.main.CONFIG_BUTTON_CLICKED_BG_COLOR = self.LIGHT_900_COLOR + # self.main.CONFIG_BUTTON_DISABLE_COLOR = self.LIGHT_900_COLOR + + self.main.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR + self.main.MINIMIZE_SIDEBAR_BUTTON_HOVERED_BG_COLOR = self.LIGHT_800_COLOR + self.main.MINIMIZE_SIDEBAR_BUTTON_CLICKED_BG_COLOR = self.LIGHT_900_COLOR + # self.main.MINIMIZE_SIDEBAR_BUTTON_DISABLE_COLOR = self.LIGHT_900_COLOR + + self.main.HELP_AND_INFO_BUTTON_BG_COLOR = self.main.MAIN_BG_COLOR + self.main.HELP_AND_INFO_BUTTON_HOVERED_BG_COLOR = self.LIGHT_350_COLOR + self.main.HELP_AND_INFO_BUTTON_CLICKED_BG_COLOR = self.LIGHT_450_COLOR + # self.main.HELP_AND_INFO_BUTTON_DISABLE_COLOR = self.LIGHT_900_COLOR + + + # Common + self.config_window.BASIC_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + self.config_window.LABELS_TEXT_COLOR = self.config_window.BASIC_TEXT_COLOR + self.config_window.LABELS_DESC_TEXT_COLOR = self.DARK_500_COLOR + + + # Top bar + self.config_window.TOP_BAR_BG_COLOR = self.DARK_850_COLOR + + + # Main + self.config_window.MAIN_BG_COLOR = self.DARK_950_COLOR + + # This is for fake border color + self.config_window.SB__WRAPPER_BG_COLOR = self.DARK_750_COLOR + + self.config_window.SB__BG_COLOR = self.DARK_888_COLOR + + self.config_window.SB__OPTIONMENU_BG_COLOR = self.DARK_925_COLOR + self.config_window.SB__OPTIONMENU_HOVERED_BG_COLOR = self.DARK_850_COLOR + + self.config_window.SB__SLIDER_BUTTON_COLOR = self.DARK_700_COLOR + self.config_window.SB__SLIDER_BUTTON_HOVERED_COLOR = self.DARK_600_COLOR + + self.config_window.SB__SWITCH_BOX_BG_COLOR = self.main.SF__SWITCH_BOX_BG_COLOR + self.config_window.SB__SWITCH_BOX_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR + + self.config_window.SB__CHECKBOX_BORDER_COLOR = self.DARK_500_COLOR + self.config_window.SB__CHECKBOX_HOVER_COLOR = self.DARK_800_COLOR + self.config_window.SB__CHECKBOX_CHECKED_COLOR = self.PRIMARY_700_COLOR + self.config_window.SB__CHECKBOX_CHECKMARK_COLOR = self.config_window.BASIC_TEXT_COLOR + + self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_COLOR = self.PRIMARY_700_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_HOVERED_COLOR = self.PRIMARY_500_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR = self.DARK_800_COLOR + + self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR = self.DARK_800_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_HOVERED_COLOR = self.DARK_700_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_CLICKED_COLOR = self.DARK_900_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR = self.DARK_850_COLOR + + self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_COLOR = self.PRIMARY_700_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_HOVERED_COLOR = self.PRIMARY_600_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_CLICKED_COLOR = self.PRIMARY_900_COLOR + # self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_DISABLED_COLOR = self.PRIMARY_900_COLOR + + + # Side menu + self.config_window.SIDE_MENU_BG_COLOR = self.config_window.MAIN_BG_COLOR + + self.config_window.SIDE_MENU_LABELS_BG_COLOR = self.config_window.SIDE_MENU_BG_COLOR + self.config_window.SIDE_MENU_LABELS_BG_FOR_FAKE_BORDER_COLOR = self.config_window.SIDE_MENU_BG_COLOR + self.config_window.SIDE_MENU_LABELS_HOVERED_BG_COLOR = self.DARK_850_COLOR + self.config_window.SIDE_MENU_LABELS_CLICKED_BG_COLOR = self.PRIMARY_900_COLOR + self.config_window.SIDE_MENU_LABELS_SELECTED_TEXT_COLOR = self.PRIMARY_300_COLOR + + self.config_window.SIDE_MENU_SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR diff --git a/vrct_gui/ui_managers/ImageFilenameManager.py b/vrct_gui/ui_managers/ImageFilenameManager.py new file mode 100644 index 00000000..82573f37 --- /dev/null +++ b/vrct_gui/ui_managers/ImageFilenameManager.py @@ -0,0 +1,54 @@ +class ImageFilenameManager(): + def __init__(self, theme:str ="Dark"): + if theme == "Dark": + return self._createDarkModeImages() + elif theme == "Light": + return self._createLightModeImages() + + + def _createDarkModeImages(self): + self.VRCT_LOGO = "vrct_logo_for_dark_mode.png" + self.VRCT_LOGO_MARK = "vrct_logo_mark_white.png" + + self.TRANSLATION_ICON = "translation_icon_white.png" + self.TRANSLATION_ICON_DISABLED = "translation_icon_disabled.png" + self.MIC_ICON = "mic_icon_white.png" + self.MIC_ICON_DISABLED = "mic_icon_disabled.png" + self.HEADPHONES_ICON = "headphones_icon_white.png" + self.HEADPHONES_ICON_DISABLED = "headphones_icon_disabled.png" + self.FOREGROUND_ICON = "foreground_icon_white.png" + self.FOREGROUND_ICON_DISABLED = "foreground_icon_disabled.png" + + self.NARROW_ARROW_DOWN = "narrow_arrow_down.png" + + self.CONFIGURATION_ICON = "configuration_icon_white.png" + self.CONFIGURATION_ICON_DISABLED = "configuration_icon_disabled.png" + + self.ARROW_LEFT = "arrow_left_white.png" + self.ARROW_LEFT_DISABLED = "arrow_left_disabled.png" + + self.HELP_ICON = "help_icon_white.png" + + def _createLightModeImages(self): + self.VRCT_LOGO = "vrct_logo_for_light_mode.png" + self.VRCT_LOGO_MARK = "vrct_logo_mark_black.png" + + + self.TRANSLATION_ICON = "translation_icon_white.png" + self.TRANSLATION_ICON_DISABLED = "translation_icon_disabled.png" + self.MIC_ICON = "mic_icon_white.png" + self.MIC_ICON_DISABLED = "mic_icon_disabled.png" + self.HEADPHONES_ICON = "headphones_icon_white.png" + self.HEADPHONES_ICON_DISABLED = "headphones_icon_disabled.png" + self.FOREGROUND_ICON = "foreground_icon_white.png" + self.FOREGROUND_ICON_DISABLED = "foreground_icon_disabled.png" + + self.NARROW_ARROW_DOWN = "narrow_arrow_down.png" + + self.CONFIGURATION_ICON = "configuration_icon_white.png" + self.CONFIGURATION_ICON_DISABLED = "configuration_icon_disabled.png" + + self.ARROW_LEFT = "arrow_left_white.png" + self.ARROW_LEFT_DISABLED = "arrow_left_disabled.png" + + self.HELP_ICON = "help_icon_white.png" \ No newline at end of file diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py new file mode 100644 index 00000000..8d013108 --- /dev/null +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -0,0 +1,184 @@ +from types import SimpleNamespace + +class UiScalingManager(): + def __init__(self, scaling_percentage): + scaling_float = int(scaling_percentage.replace("%", "")) / 100 + print(scaling_float) + self.SCALING_FLOAT = max(scaling_float, 0.4) + self.main = SimpleNamespace() + self.config_window = SimpleNamespace() + + return self._calculatedUiSizes() + + + + + def _calculatedUiSizes(self): + # Common + + # Main + self.main.TEXTBOX_PADX = self._calculateUiSize(16) + self.main.TEXTBOX_CORNER_RADIUS = self._calculateUiSize(6) + + self.main.TEXTBOX_TAB_CORNER_RADIUS = self._calculateUiSize(8) + self.main.TEXTBOX_TAB_FONT_SIZE = self._calculateUiSize(12) + self.main.TEXTBOX_TAB_PADX = self._calculateUiSize(10) + self.main.TEXTBOX_TAB_PADY = (self._calculateUiSize(4), self._calculateUiSize(10)) + + self.main.TEXTBOX_ENTRY_FONT_SIZE = self._calculateUiSize(16) + self.main.TEXTBOX_ENTRY_HEIGHT = self._calculateUiSize(40) + self.main.TEXTBOX_ENTRY_PADX = self.main.TEXTBOX_PADX + self.main.TEXTBOX_ENTRY_PADY = self._calculateUiSize(10) + self.main.TEXTBOX_ENTRY_IPADX = self._calculateUiSize(14) + self.main.TEXTBOX_ENTRY_IPADY = (self._calculateUiSize(2, True), self._calculateUiSize(3, True)) + + + # Sidebar + self.main.SIDEBAR_WIDTH = self._calculateUiSize(230) + self.main.COMPACT_MODE_SIDEBAR_WIDTH = self._calculateUiSize(60) + + # Sidebar Features + self.main.SF__LOGO_MAX_SIZE = self._calculateUiSize(120) + self.main.SF__LOGO_PADY = (self._calculateUiSize(12),self._calculateUiSize(8)) + self.main.SF__LOGO_HEIGHT_FOR_ADJUSTMENT = (self._calculateUiSize(8)) + + self.main.SF__LABELS_IPADY = self._calculateUiSize(16) + self.main.SF__COMPACT_MODE_ICON_PADY = self.main.SF__LABELS_IPADY + self.main.SF__LABEL_LEFT_PAD = self._calculateUiSize(20) + self.main.SF__LABEL_FONT_SIZE = self._calculateUiSize(16) + + self.main.SF__SWITCH_BOX_RIGHT_PAD = self._calculateUiSize(10) + self.main.SF__SWITCH_BOX_WIDTH = self._calculateUiSize(40) + self.main.SF__SWITCH_BOX_HEIGHT = self._calculateUiSize(16) + + self.main.SF__SELECTED_MARK_WIDTH = self._calculateUiSize(3, True) + + + # Sidebar Quick Language Settings, SQLS + self.main.SQLS__TITLE_FONT_SIZE = self._calculateUiSize(16) + self.main.SQLS__TITLE_PADY = (self._calculateUiSize(12), self._calculateUiSize(6)) + + self.main.SQLS__PRESET_TAB_NUMBER_FONT_SIZE = self._calculateUiSize(16) + + self.main.SQLS__BOX_SECTION_TITLE_FONT_SIZE = self._calculateUiSize(16) + self.main.SQLS__BOX_SECTION_TITLE_BOTTOM_PADY = self._calculateUiSize(10) + self.main.SQLS__BOX_IPADY = (self._calculateUiSize(8),self._calculateUiSize(18)) + self.main.SQLS__BOX_DROPDOWN_MENU_FONT_SIZE = self._calculateUiSize(14) + self.main.SQLS__BOX_DROPDOWN_MENU_WIDTH = self._calculateUiSize(200) + self.main.SQLS__BOX_ARROWS_PADY = self._calculateUiSize(10) + self.main.SQLS__BOX_ARROWS_IMAGE_SIZE = self.dupTuple(self._calculateUiSize(16)) + self.main.SQLS__BOX_ARROWS_DESC_FONT_SIZE = self._calculateUiSize(12) + self.main.SQLS__BOX_ARROWS_DESC_PADX = self._calculateUiSize(6) + self.main.SQLS__BOX_TOP_PADY = self._calculateUiSize(16) + + self.main.SIDEBAR_CONFIG_BUTTON_CORNER_RADIUS = self._calculateUiSize(6) + self.main.SIDEBAR_CONFIG_BUTTON_PADX = self._calculateUiSize(10) + self.main.SIDEBAR_CONFIG_BUTTON_PADY = self._calculateUiSize(10) + self.main.SIDEBAR_CONFIG_BUTTON_IPADY = self._calculateUiSize(8) + + + self.main.HELP_AND_INFO_BUTTON_CORNER_RADIUS = self._calculateUiSize(6) + self.main.HELP_AND_INFO_BUTTON_SIZE = self._calculateUiSize(24) + self.main.HELP_AND_INFO_BUTTON_PADX = (0, self._calculateUiSize(6)) + self.main.HELP_AND_INFO_BUTTON_PADY = (self._calculateUiSize(6),0) + self.main.HELP_AND_INFO_BUTTON_IPADXY = self._calculateUiSize(6) + + self.main.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X = int(self.main.TEXTBOX_PADX/2+self.main.TEXTBOX_CORNER_RADIUS*2) + self.main.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y = self._calculateUiSize(26) + + + + + # Top bar common + self.config_window.TOP_BAR__HEIGHT = self._calculateUiSize(40) + self.config_window.TOP_BAR__IPADY = self._calculateUiSize(12) + + # Top bar Side + self.config_window.TOP_BAR_SIDE__WIDTH = self._calculateUiSize(220) + self.config_window.TOP_BAR_SIDE__CONFIG_LOGO_MARK_SIZE = self.dupTuple(self._calculateUiSize(30)) + self.config_window.TOP_BAR_SIDE__CONFIG_TITLE_FONT_SIZE = self._calculateUiSize(22) + self.config_window.TOP_BAR_SIDE__CONFIG_TITLE_LEFT_PADX = int(self.config_window.TOP_BAR_SIDE__CONFIG_TITLE_FONT_SIZE + self._calculateUiSize(16)) + self.config_window.TOP_BAR_SIDE__TITLE_PADX= self._calculateUiSize(30) + + # Side menu + self.config_window.SIDE_MENU_TOP_PADY= self._calculateUiSize(54) + self.config_window.SIDE_MENU_LABELS_IPADX = self._calculateUiSize(20) + self.config_window.SIDE_MENU_LABELS_IPADY= self._calculateUiSize(8) + self.config_window.SIDE_MENU_LABELS_FONT_SIZE= self._calculateUiSize(18) + + + # Top bar Main + self.config_window.TOP_BAR_MAIN__TITLE_FONT_SIZE = self._calculateUiSize(22) + + + # Setting Box + self.config_window.SB__MAIN_WIDTH = self._calculateUiSize(720) + self.config_window.SB__TOP_PADY_IF_WITH_SECTION_TITLE = (self._calculateUiSize(24)) + self.config_window.SB__TOP_PADY_IF_WITHOUT_SECTION_TITLE = (self._calculateUiSize(64)) + self.config_window.SB__BOTTOM_PADY = (self._calculateUiSize(40)) + self.config_window.SB__IPADX = self._calculateUiSize(20) + self.config_window.SB__IPADY = self._calculateUiSize(12) + self.config_window.SB__BOTTOM_MARGIN = (0, self._calculateUiSize(60)) + + self.config_window.SB__SECTION_TITLE_FONT_SIZE = self._calculateUiSize(20) + self.config_window.SB__SECTION_TITLE_BOTTOM_PADY = (0, self._calculateUiSize(10)) + + self.config_window.SB__LABEL_FONT_SIZE = self._calculateUiSize(16) + self.config_window.SB__DESC_FONT_SIZE = self._calculateUiSize(14) + self.config_window.SB__DESC_TOP_PADY = self._calculateUiSize(2) + + + + self.config_window.SB__SELECTOR_FONT_SIZE = self._calculateUiSize(14) + self.config_window.SB__RADIO_BUTTON_FONT_SIZE = self.config_window.SB__SELECTOR_FONT_SIZE + self.config_window.SB__BUTTON_FONT_SIZE = self.config_window.SB__SELECTOR_FONT_SIZE + + + + self.config_window.SB__OPTION_MENU_FONT_SIZE = self.config_window.SB__SELECTOR_FONT_SIZE + self.config_window.SB__OPTIONMENU_HEIGHT = self._calculateUiSize(30) + self.config_window.SB__OPTIONMENU_WIDTH = self._calculateUiSize(200) + self.config_window.SB__DROPDOWN_MENU_WIDTH = self.config_window.SB__OPTIONMENU_WIDTH + self.config_window.SB__DROPDOWN_MENU_MAX_BUTTON_HEIGHT = int(self.config_window.SB__OPTION_MENU_FONT_SIZE + self._calculateUiSize(6)) + self.config_window.SB__DROPDOWN_MENU_FRAME_CORNER_RADIUS = self._calculateUiSize(10) + self.config_window.SB__DROPDOWN_MENU_FRAME_MAX_HEIGHT = self._calculateUiSize(200) + + + self.config_window.SB__SWITCH_WIDTH = self._calculateUiSize(50) + + self.config_window.SB__SWITCH_BOX_WIDTH = self._calculateUiSize(40) + self.config_window.SB__SWITCH_BOX_HEIGHT = self._calculateUiSize(16) + + self.config_window.SB__CHECKBOX_SIZE = self._calculateUiSize(24) + self.config_window.SB__CHECKBOX_BORDER_WIDTH = self._calculateUiSize(2) + self.config_window.SB__CHECKBOX_CORNER_RADIUS = self._calculateUiSize(4) + + self.config_window.SB__ENTRY_FONT_SIZE = self.config_window.SB__SELECTOR_FONT_SIZE + self.config_window.SB__ENTRY_HEIGHT = self._calculateUiSize(30) + + # SB__ENTRY_WIDTH_10 ... SB__ENTRY_WIDTH_200 + for i in range(10, 201, 10): + setattr(self.config_window, f'SB__ENTRY_WIDTH_{i}', self._calculateUiSize(i)) + + + self.config_window.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH = self.config_window.SB__ENTRY_WIDTH_50 + self.config_window.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT = self.config_window.SB__ENTRY_HEIGHT + self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_HEIGHT = self._calculateUiSize(40) + self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_LENGTH = self._calculateUiSize(2) + self.config_window.SB__PROGRESSBAR_X_SLIDER__BAR_WIDTH = self._calculateUiSize(200) + self.config_window.SB__PROGRESSBAR_X_SLIDER__PROGRESSBAR_HEIGHT = self._calculateUiSize(8) + self.config_window.SB__PROGRESSBAR_X_SLIDER__BAR_RIGHT_PADX = self._calculateUiSize(20) + + self.config_window.SB__PROGRESSBAR_X_SLIDER__BUTTON_RIGHT_PADX = self._calculateUiSize(20) + self.config_window.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY = self._calculateUiSize(10) + self.config_window.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE = self._calculateUiSize(20) + + + + def _calculateUiSize(self, default_size, is_allowed_odd: bool = False): + size = int(default_size * self.SCALING_FLOAT) + size += 1 if not is_allowed_odd and size % 2 != 0 else 0 + return size + + def dupTuple(self, value): + return (value, value) \ No newline at end of file diff --git a/vrct_gui/ui_managers/__init__.py b/vrct_gui/ui_managers/__init__.py new file mode 100644 index 00000000..f6a5abc0 --- /dev/null +++ b/vrct_gui/ui_managers/__init__.py @@ -0,0 +1,3 @@ +from .ColorThemeManager import ColorThemeManager +from .ImageFilenameManager import ImageFilenameManager +from .UiScalingManager import UiScalingManager \ No newline at end of file diff --git a/vrct_gui/ui_utils/__init__.py b/vrct_gui/ui_utils/__init__.py new file mode 100644 index 00000000..bc2a1337 --- /dev/null +++ b/vrct_gui/ui_utils/__init__.py @@ -0,0 +1 @@ +from .ui_utils import * \ No newline at end of file diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py new file mode 100644 index 00000000..eb45ea3d --- /dev/null +++ b/vrct_gui/ui_utils/ui_utils.py @@ -0,0 +1,150 @@ +from os import path as os_path +from PIL.Image import open as Image_open, LANCZOS + +from customtkinter import CTkFrame, CTkLabel, CTkImage + +def getImagePath(file_name): + # root\img\file_name + return os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "img", file_name) + +def getImageFileFromUiUtils(file_name): + # root\img\file_name + img = Image_open(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "img", file_name)) + return img + +def openImageKeepAspectRatio(file_name, desired_width): + img = getImageFileFromUiUtils(file_name) + wpercent = (desired_width/float(img.size[0])) + hsize = int((float(img.size[1])*float(wpercent))) + img = img.resize((desired_width,hsize), LANCZOS) + return (img, desired_width, hsize) + +def retag(tag, *args): + for widget in args: + widget.bindtags((tag,) + widget.bindtags()) + + +def getLatestWidth(target_widget): + target_widget.update_idletasks() + return target_widget.winfo_width() + +def getLatestHeight(target_widget): + target_widget.update_idletasks() + return target_widget.winfo_height() + +def getLongestText(settings): + max_length = max(len(item["text"]) for item in settings) + max_length = 0 + longest_text = "" + + for item in settings: + if len(item["text"]) > max_length: + max_length = len(item["text"]) + longest_text = item["text"] + return longest_text + +def bindEnterAndLeaveColor(target_widgets, enter_color, leave_color): + for target_widget in target_widgets: + target_widget.bind("", lambda e, widgets=target_widgets: [w.configure(fg_color=enter_color) for w in widgets], "+") + target_widget.bind("", lambda e, widgets=target_widgets: [w.configure(fg_color=leave_color) for w in widgets], "+") + + +def bindButtonPressColor(target_widgets, clicked_color, released_color): + for target_widget in target_widgets: + target_widget.bind("", lambda e, widgets=target_widgets: [w.configure(fg_color=clicked_color) for w in widgets], "+") + target_widget.bind("", lambda e, widgets=target_widgets: [w.configure(fg_color=released_color) for w in widgets], "+") + +def bindEnterAndLeaveFunction(target_widgets, enterFunction, leaveFunction): + for target_widget in target_widgets: + target_widget.bind("", enterFunction, "+") + target_widget.bind("", leaveFunction, "+") + +def bindButtonPressFunction(target_widgets, buttonPressedFunction): + for target_widget in target_widgets: + target_widget.bind("", buttonPressedFunction, "+") + +def bindButtonReleaseFunction(target_widgets, buttonReleasedFunction): + for target_widget in target_widgets: + target_widget.bind("", buttonReleasedFunction, "+") + +def bindButtonPressAndReleaseFunction(target_widgets, buttonPressedFunction, buttonReleasedFunction): + for target_widget in target_widgets: + target_widget.bind("", buttonPressedFunction, "+") + target_widget.bind("", buttonReleasedFunction, "+") + + +def bindButtonFunctionAndColor(target_widgets, enter_color, leave_color, clicked_color, buttonReleasedFunction): + bindEnterAndLeaveColor(target_widgets, enter_color, leave_color) + bindButtonPressColor(target_widgets, clicked_color, enter_color) + bindButtonReleaseFunction(target_widgets, buttonReleasedFunction) + +def unbindEventFromActiveTabWidget(active_tab_widget): + for event_name in ["", "", "", ""]: + active_tab_widget.unbind(event_name) + active_tab_widget.children["!ctklabel"].unbind(event_name) + +def setDefaultActiveTab(active_tab_widget, active_bg_color, active_text_color): + active_tab_widget.configure(fg_color=active_bg_color, cursor="") + active_tab_widget.children["!ctklabel"].configure(fg_color=active_bg_color, text_color=active_text_color) + unbindEventFromActiveTabWidget(active_tab_widget) + + +def switchActiveTabAndPassiveTab(active_tab_widget, current_active_tab_widget, current_active_tab_passive_function, hovered_color, clicked_color, passive_color): + + + active_tab_widget.configure(cursor="") + unbindEventFromActiveTabWidget(active_tab_widget) + + + rebindFunctionToTab(current_active_tab_widget, current_active_tab_passive_function, hovered_color, clicked_color, passive_color) + +def rebindFunctionToTab(passive_tab_widget, passive_tab_function, hovered_color, clicked_color, passive_color): + + passive_tab_widget.configure(cursor="hand2") + bindEnterAndLeaveColor([passive_tab_widget, passive_tab_widget.children["!ctklabel"]], hovered_color, passive_color) + bindButtonPressColor([passive_tab_widget, passive_tab_widget.children["!ctklabel"]], clicked_color, passive_color) + + bindButtonReleaseFunction([passive_tab_widget, passive_tab_widget.children["!ctklabel"]], passive_tab_function) + +def switchTabsColor(target_widget, tab_buttons, active_bg_color, active_text_color, passive_bg_color, passive_text_color): + # Change all tabs' color to passive color at first + for tab_button in tab_buttons: + tab_button.configure(fg_color=passive_bg_color) + tab_button.children["!ctklabel"].configure(fg_color=passive_bg_color, text_color=passive_text_color) + + # Then, set active color to the active tab + target_widget.configure(fg_color=active_bg_color) + target_widget.children["!ctklabel"].configure(fg_color=active_bg_color, text_color=active_text_color) + + + + + + +def createButtonWithImage(parent_widget, button_fg_color, button_enter_color, button_clicked_color, button_image_filename, button_image_size, button_ipadxy, button_command, corner_radius: int = 0 ,shape: str = "normal"): + button_wrapper = CTkFrame(parent_widget, corner_radius=corner_radius, fg_color=button_fg_color, height=0, width=0, cursor="hand2") + + button_widget = CTkLabel( + button_wrapper, + text=None, + height=0, + image=CTkImage(getImageFileFromUiUtils(button_image_filename),size=(button_image_size,button_image_size)), + ) + button_widget.grid(row=0, column=0, padx=button_ipadxy, pady=button_ipadxy) + + bindButtonFunctionAndColor( + target_widgets=[button_wrapper, button_widget], + enter_color=button_enter_color, + leave_color=button_fg_color, + clicked_color=button_clicked_color, + buttonReleasedFunction=button_command, + ) + + if shape == "circle": + # To round the corners of the button into a circle + button_wrapper.grid() + button_wrapper.configure(corner_radius=int(getLatestWidth(button_wrapper)/2)) + button_wrapper.grid_remove() + + + return button_wrapper diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py new file mode 100644 index 00000000..795f1b94 --- /dev/null +++ b/vrct_gui/vrct_gui.py @@ -0,0 +1,107 @@ +from types import SimpleNamespace + +from customtkinter import CTk, get_appearance_mode + +# from window_help_and_info import ToplevelWindowInformation + +from .ui_managers import ColorThemeManager, ImageFilenameManager, UiScalingManager +from ._changeMainWindowWidgetsStatus import _changeMainWindowWidgetsStatus +from ._printToTextbox import _printToTextbox + +from .main_window import createMainWindowWidgets +from .config_window import ConfigWindow + +from config import config + + +class VRCT_GUI(CTk): + def __init__(self): + super().__init__() + self.settings = SimpleNamespace() + theme = get_appearance_mode() if config.APPEARANCE_THEME == "System" else config.APPEARANCE_THEME + all_ctm = ColorThemeManager(theme) + all_uism = UiScalingManager(config.UI_SCALING) + image_filename = ImageFilenameManager(theme) + + common_args = { + "image_filename": image_filename, + "FONT_FAMILY": config.FONT_FAMILY, + } + + self.settings.main = SimpleNamespace( + ctm=all_ctm.main, + uism=all_uism.main, + IS_SIDEBAR_COMPACT_MODE=False, + COMPACT_MODE_ICON_SIZE=0, + **common_args + ) + + self.settings.config_window = SimpleNamespace( + ctm=all_ctm.config_window, + uism=all_uism.config_window, + IS_CONFIG_WINDOW_COMPACT_MODE=False, + **common_args + ) + + + self.YOUR_LANGUAGE = "Japanese\n(Japan)" + self.TARGET_LANGUAGE = "English\n(United States)" + + + self.config_window = ConfigWindow(vrct_gui=self, settings=self.settings.config_window) + self.config_window.withdraw() + + # self.information_window = ToplevelWindowInformation(self) + + + + def start(self): + createMainWindowWidgets(vrct_gui=self, settings=self.settings.main) + # self.printToTextbox(self.textbox_all, "Translation started. You: Japanese (japan). Target: English (American english)", "", "INFO") + # self.printToTextbox(self.textbox_all, "テキスト送信テスト", "Test send text", "SEND") + # self.printToTextbox(self.textbox_all, "こんにちは、こんにちは。", "Hi hi hello.", "SEND") + # self.printToTextbox(self.textbox_all, "Hi~ how are you doing.", "やあ~、元気かい?", "RECEIVE") + self.mainloop() + + + def quitVRCT(self): + self.quit() + self.destroy() + + + def openConfigWindow(self, e): + self.config_window.deiconify() + self.config_window.focus_set() + self.config_window.focus() + self.config_window.grab_set() + + def closeConfigWindow(self): + self.config_window.withdraw() + self.config_window.grab_release() + + + + def openHelpAndInfoWindow(self, e): + self.information_window.deiconify() + self.information_window.focus_set() + self.information_window.focus() + + def changeMainWindowWidgetsStatus(self, status, target_names): + _changeMainWindowWidgetsStatus( + vrct_gui=self, + settings=self.settings.main, + status=status, + target_names=target_names, + ) + + def printToTextbox(self, target_textbox, original_message, translated_message, tags=None): + _printToTextbox( + settings=self.settings.main, + target_textbox=target_textbox, + original_message=original_message, + translated_message=translated_message, + tags=tags, + ) + + +vrct_gui = VRCT_GUI() \ No newline at end of file diff --git a/window_help_and_info.py b/window_help_and_info.py new file mode 100644 index 00000000..cc0d3b7f --- /dev/null +++ b/window_help_and_info.py @@ -0,0 +1,154 @@ +import os +from customtkinter import CTkToplevel, CTkTextbox, CTkFont + +class ToplevelWindowInformation(CTkToplevel): + def __init__(self, parent, *args, **kwargs): + super().__init__(parent, *args, **kwargs) + self.withdraw() + self.parent = parent + self.grid_columnconfigure(0, weight=1) + self.grid_rowconfigure(0, weight=1) + # self.geometry(f"{500}x{300}") + self.minsize(500, 300) + + self.after(200, lambda: self.iconbitmap(os.path.join(os.path.dirname(__file__), "img", "app.ico"))) + self.title("Information") + # create textbox information + self.textbox_information = CTkTextbox( + self, + font=CTkFont(family=self.parent.FONT_FAMILY) + ) + self.textbox_information.grid(row=0, column=0, padx=(10, 10), pady=(10, 10), sticky="nsew") + textbox_information_message = """VRCT(v1.3.1) + +# 概要 +VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツールになります。 +翻訳エンジンを使用してメッセージとその翻訳部分を同時に送信することができます。 +(翻訳エンジンはDeepL,Google,Bingに対応) + +# 使用方法 + 初期設定時 + 0. VRChatのOSCを有効にする(重要) + + (任意) + 1. DeepLのAPIを使用するためにアカウント登録し、認証キーを取得する + 2. ギアアイコンのボタンでconfigウィンドウを開く + 3. ParameterタブのDeepL Auth Keyに認証キーを記載 + 4. configウィンドウを閉じる + + 通常使用時 + 1. メッセージボックスにメッセージを記入 + 2. Enterキーを押し、メッセージを送信する + +# その他の設定 + translation チェックボックス: 翻訳の有効無効 + voice2chatbox チェックボックス : マイクの音声を文字起こししてチャットボックスに送信する + speaker2log チェックボックス : スピーカーの音声から文字起こししてログに表示する + foreground チェックボックス: 最前面表示の有効無効 + + テキストボックス + logタブ + すべてのログを表示 + sendタブ + 送信したメッセージを表示 + receiveタブ + 受信したメッセージを表示 + systemタブ + 機能についてのメッセージを表示 + + configウィンドウ + UIタブ + Transparency: ウィンドウの透過度の調整 + Appearance Theme: ウィンドウテーマを選択 + UI Scaling: UIサイズを調整 + Font Family: 表示フォントを選択 + (New!) UI Language: UIの表示言語を選択 + Translationタブ + Select Translator: 翻訳エンジンの変更 + Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択 + Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択 + Transcriptionタブ + (New!) Input Mic Host: マイクのホストAPIを選択 + Input Mic Device: マイクを選択 + Input Mic Voice Language: 入力する音声の言語 + Input Mic Energy Threshold: 音声取得のしきい値 + (New!) Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化 + Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整 + Input Mic Record Timeout: 音声の区切りの無音時間 + Input Mic Phase Timeout: 文字起こしする音声時間の上限 + Input Mic Max Phrases: 保留する単語の上限 + (New!) Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC) + Input Speaker Device: スピーカーを選択 + Input Speaker Voice Language: 受信する音声の言語 + Input Speaker Energy Threshold: 音声取得のしきい値 + (New!) Check threshold point: (New!)Input Speaker Energy Thresholdのしきい値を視覚化 + Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整 + Input Speaker Record Timeout: 音声の区切りの無音時間 + Input Speaker Phase Timeout: 文字起こしする音声時間の上限 + Input Speaker Max Phrases: 保留する単語の上限 + Parameterタブ + OSC IP address: 変更不要 + OSC port: 変更不要 + DeepL Auth key: DeepLの認証キーの設定 + Message Format: 送信するメッセージのデコレーションの設定 + [message]がメッセージボックスに記入したメッセージに置換される + [translation]が翻訳されたメッセージに置換される + 初期フォーマット:"[message]([translation])" + Othersタブ + (New!) Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする + + 設定の初期化 + config.jsonを削除 + +# お問い合わせ +要望などはTwitterまで +https://twitter.com/misya_ai + +# アップデート履歴 +[2023-05-29: v0.1b] v0.1b リリース +[2023-05-30: v0.2b] +- configボタンをギアアイコンに変更 +- 詳細情報のボタンを追加 +- 翻訳機能有効無効のチェックボックスを追加 +- 最前面表示の有効無効のチェックボックスを追加 +- いくつかのバグを修正 +[2023-06-03: v0.3b] +- 全体的にUIを刷新 +- 透過機能を追加 +- テーマのLight/Dark/Systemのモードの変更機能を追加 +- UIのスケール変更機能を追加 +- フォントの変更機能を追加 +[2023-06-06: v0.4b] +- 翻訳エンジンを追加 +- 入力と出力の翻訳言語を選択できるように変更 +[2023-06-20: v1.0] +- マイクからの音声の文字起こし機能を追加 +- スピーカーからの音声の文字起こし機能を追加 +[2023-06-28: v1.1] +- いくつかのバクを修正 +- 翻訳/文字起こし言語の表記を略称からわかりやすい文字に変更 +- 文字起こしの処理の軽量化 +[2023-07-05: v1.2] +- 文字起こし精度の向上 +[2023-07-21: v1.3] +- UIの表示言語を日本語/英語を選択できる機能を追加 +- Energy Thresholdの視覚化機能を追加 +- 文字起こしの誤認識対策のため、Word Filterを追加 +- WASAPI以外のHostAPIでもマイクとして使用できるようにHostAPIを選択できる機能を追加 +- メッセージ送信後に書き込んだメッセージを空にするか選択できる機能を追加 +- バグ対策のため、translation/voice2chatbox/speaker2log/foregroundは起動時はOFFになるように変更 +- バグ対策のため、スピーカーについて既定デバイス以外を選択した時にERRORが出るように変更 +- 半角入力時に一部の文字が書き込めないバグを修正 +[2023-07-22: v1.3.1] +- UIの表示言語選択に韓国語を追加 + +# 注意事項 +再配布とかはやめてね +""" + + self.textbox_information.insert("end", textbox_information_message) + self.textbox_information.configure(state='disabled') + self.protocol("WM_DELETE_WINDOW", self.delete_window) + + def delete_window(self): + self.withdraw() \ No newline at end of file From 0780c7d75fd7506955d66812115f9bbd8dfd5d60 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 27 Aug 2023 05:09:55 +0900 Subject: [PATCH 022/355] =?UTF-8?q?[Update]=20gui=E3=81=AE=E8=B5=B7?= =?UTF-8?q?=E5=8B=95=E9=96=A2=E6=95=B0=E3=82=92=E5=88=86=E5=89=B2/model,co?= =?UTF-8?q?nfig=E3=82=92import?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 6 ++++-- vrct_gui/vrct_gui.py | 10 +++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/main.py b/main.py index ac80e5f5..71e2c4ce 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,10 @@ from vrct_gui import vrct_gui +from config import config +from model import model class VRCT(): def __init__(self): pass if __name__ == "__main__": - # vrct_gui = VRCT_GUI() - vrct_gui.start() \ No newline at end of file + vrct_gui.createUI() + vrct_gui.startMainLoop() \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 795f1b94..07cede7d 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -53,14 +53,10 @@ class VRCT_GUI(CTk): # self.information_window = ToplevelWindowInformation(self) - - - def start(self): + def createUI(self): createMainWindowWidgets(vrct_gui=self, settings=self.settings.main) - # self.printToTextbox(self.textbox_all, "Translation started. You: Japanese (japan). Target: English (American english)", "", "INFO") - # self.printToTextbox(self.textbox_all, "テキスト送信テスト", "Test send text", "SEND") - # self.printToTextbox(self.textbox_all, "こんにちは、こんにちは。", "Hi hi hello.", "SEND") - # self.printToTextbox(self.textbox_all, "Hi~ how are you doing.", "やあ~、元気かい?", "RECEIVE") + + def startMainLoop(self): self.mainloop() From d59703223183206c8b68b3bdb90ad20fbb4b8284 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 27 Aug 2023 05:13:02 +0900 Subject: [PATCH 023/355] =?UTF-8?q?[bugfix]=20=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/main_window/widgets/create_sidebar.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 1b290954..f58ecb8f 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -21,7 +21,6 @@ def createSidebar(settings, main_window): is_turned_on = getattr(main_window, "translation_switch_box").get() print(is_turned_on) toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.translation_selected_mark) - toggleTranslationFeatures = [toggleTranslationFeature] def toggleTranscriptionSendFeature(): is_turned_on = getattr(main_window, "transcription_send_switch_box").get() @@ -254,7 +253,7 @@ def createSidebar(settings, main_window): sidebar_features_settings = [ { "frame_attr_name": "translation_frame", - "command": lambda:[func() for func in toggleTranslationFeatures], + "command": toggleTranslationFeature, "switch_box_attr_name": "translation_switch_box", "toggle_switch_box_command": toggleTranslationSwitchBox, "label_attr_name": "label_translation", From 0c81d65fe434eb3fb8b387add16d73a14808ffa1 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 27 Aug 2023 06:13:47 +0900 Subject: [PATCH 024/355] =?UTF-8?q?[Add]=20translation=20checkbox=E3=81=A7?= =?UTF-8?q?config.ENABLE=5FTRANSLATION=E3=81=8CTrue/Flase=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E5=8C=96=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=AE=9F=E8=A3=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 28 ++++++++++++++++++++++++---- vrct_gui/vrct_gui.py | 2 +- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/main.py b/main.py index 71e2c4ce..c4770f22 100644 --- a/main.py +++ b/main.py @@ -2,9 +2,29 @@ from vrct_gui import vrct_gui from config import config from model import model -class VRCT(): - def __init__(self): - pass +# func print textbox +def logTranslationStatusChange(): + textbox_all = getattr(vrct_gui, "textbox_all") + textbox_system = getattr(vrct_gui, "textbox_system") + if config.ENABLE_TRANSLATION: + vrct_gui.printToTextbox(textbox_all, "翻訳機能をONにしました", "", "INFO") + vrct_gui.printToTextbox(textbox_system, "翻訳機能をONにしました", "", "INFO") + else: + vrct_gui.printToTextbox(textbox_all, "翻訳機能をOFFにしました", "", "INFO") + vrct_gui.printToTextbox(textbox_system, "翻訳機能をOFFにしました", "", "INFO") + +# command func +def toggleTranslationFeature(): + config.ENABLE_TRANSLATION = getattr(vrct_gui, "translation_switch_box").get() + logTranslationStatusChange() + +# create GUI +vrct_gui.createGUI() + +# set commands +widget = getattr(vrct_gui, "translation_switch_box") +widget.configure(command=toggleTranslationFeature) + + if __name__ == "__main__": - vrct_gui.createUI() vrct_gui.startMainLoop() \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 07cede7d..6bcb7198 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -53,7 +53,7 @@ class VRCT_GUI(CTk): # self.information_window = ToplevelWindowInformation(self) - def createUI(self): + def createGUI(self): createMainWindowWidgets(vrct_gui=self, settings=self.settings.main) def startMainLoop(self): From a0cabdd8d29eba0a7669a227823d58430aa71511 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 27 Aug 2023 07:40:19 +0900 Subject: [PATCH 025/355] [Update] transcription send (only main window) --- main.py | 96 ++++++++++++++++++++++++++++++++++++++++++++++++++++++-- model.py | 6 ++-- 2 files changed, 95 insertions(+), 7 deletions(-) diff --git a/main.py b/main.py index c4770f22..588e8f2e 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,36 @@ +from threading import Thread from vrct_gui import vrct_gui from config import config from model import model +# func transcription +def sendMicMessage(message): + if len(message) > 0: + translation = "" + if model.checkKeywords(message): + logDetectWordFilter(message) + return + elif config.ENABLE_TRANSLATION is False: + pass + elif model.getTranslatorStatus() is False: + pass + else: + translation = model.getInputTranslate(message) + + if config.ENABLE_OSC is True: + osc_message = config.MESSAGE_FORMAT.replace("[message]", message) + osc_message = osc_message.replace("[translation]", translation) + model.oscSendMessage(osc_message) + else: + logOSCError() + logTranscriptionSendMessage(message, translation) + +def startTranscriptionSendMessage(): + model.startMicTranscript(sendMicMessage) + +def stopTranscriptionSendMessage(): + model.stopMicTranscript() + # func print textbox def logTranslationStatusChange(): textbox_all = getattr(vrct_gui, "textbox_all") @@ -13,18 +42,79 @@ def logTranslationStatusChange(): vrct_gui.printToTextbox(textbox_all, "翻訳機能をOFFにしました", "", "INFO") vrct_gui.printToTextbox(textbox_system, "翻訳機能をOFFにしました", "", "INFO") +def logTranscriptionSendStatusChange(): + textbox_all = getattr(vrct_gui, "textbox_all") + textbox_system = getattr(vrct_gui, "textbox_system") + if config.ENABLE_TRANSCRIPTION_SEND: + vrct_gui.printToTextbox(textbox_all, "Voice2chatbox機能をONにしました", "", "INFO") + vrct_gui.printToTextbox(textbox_system, "Voice2chatbox機能をONにしました", "", "INFO") + else: + vrct_gui.printToTextbox(textbox_all, "Voice2chatbox機能をOFFにしました", "", "INFO") + vrct_gui.printToTextbox(textbox_system, "Voice2chatbox機能をOFFにしました", "", "INFO") + +def logTranscriptionSendMessage(message, translate): + textbox_all = getattr(vrct_gui, "textbox_all") + textbox_sent = getattr(vrct_gui, "textbox_sent") + vrct_gui.printToTextbox(textbox_all, message, translate, "SEND") + vrct_gui.printToTextbox(textbox_sent, message, translate, "SEND") + +def logDetectWordFilter(message): + textbox_all = getattr(vrct_gui, "textbox_all") + textbox_system = getattr(vrct_gui, "textbox_system") + vrct_gui.printToTextbox(textbox_all, f"Detect WordFilter :{message}", "", "INFO") + vrct_gui.printToTextbox(textbox_system, f"Detect WordFilter :{message}", "", "INFO") + +def logAuthenticationError(): + textbox_all = getattr(vrct_gui, "textbox_all") + textbox_system = getattr(vrct_gui, "textbox_system") + vrct_gui.printToTextbox(textbox_all, "Auth Key or language setting is incorrect", "", "INFO") + vrct_gui.printToTextbox(textbox_system, "Auth Key or language setting is incorrect", "", "INFO") + +def logOSCError(): + textbox_all = getattr(vrct_gui, "textbox_all") + textbox_system = getattr(vrct_gui, "textbox_system") + vrct_gui.printToTextbox(textbox_all, "OSC is not enabled, please enable OSC and rejoin", "", "INFO") + vrct_gui.printToTextbox(textbox_system, "OSC is not enabled, please enable OSC and rejoin", "", "INFO") + # command func def toggleTranslationFeature(): config.ENABLE_TRANSLATION = getattr(vrct_gui, "translation_switch_box").get() logTranslationStatusChange() +def toggleTranscriptionSendFeature(): + config.ENABLE_TRANSCRIPTION_SEND = getattr(vrct_gui, "transcription_send_switch_box").get() + if config.ENABLE_TRANSCRIPTION_SEND is True: + th_startTranscriptionSendMessage = Thread(target=startTranscriptionSendMessage) + th_startTranscriptionSendMessage.daemon = True + th_startTranscriptionSendMessage.start() + else: + th_stopTranscriptionSendMessage = Thread(target=stopTranscriptionSendMessage) + th_stopTranscriptionSendMessage.daemon = True + th_stopTranscriptionSendMessage.start() + logTranscriptionSendStatusChange() + # create GUI vrct_gui.createGUI() -# set commands -widget = getattr(vrct_gui, "translation_switch_box") -widget.configure(command=toggleTranslationFeature) +# init config +if model.authenticationTranslator() is False: + # error update Auth key + logAuthenticationError() +# set word filter +model.addKeywords() + +# check OSC started +model.checkOSCStarted() + +# check Software Updated +model.checkSoftwareUpdated() + +# set commands +translation_switch_box = getattr(vrct_gui, "translation_switch_box") +translation_switch_box.configure(command=toggleTranslationFeature) +transcription_send_switch_box = getattr(vrct_gui, "transcription_send_switch_box") +transcription_send_switch_box.configure(command=toggleTranscriptionSendFeature) if __name__ == "__main__": vrct_gui.startMainLoop() \ No newline at end of file diff --git a/model.py b/model.py index b61ee831..f9b9060b 100644 --- a/model.py +++ b/model.py @@ -79,8 +79,7 @@ class Model: target_language=config.INPUT_TARGET_LANG, message=message ) - message = config.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", translation) - return message + return translation def getOutputTranslate(self, message): translation = self.translator.translate( @@ -89,8 +88,7 @@ class Model: target_language=config.OUTPUT_TARGET_LANG, message=message ) - message = config.MESSAGE_FORMAT.replace("[message]", message).replace("[translation]", translation) - return message + return translation def addKeywords(self): for f in config.INPUT_MIC_WORD_FILTER: From c1b5154c97d314710c718526587f0c3ec8c8c9af Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 27 Aug 2023 09:54:42 +0900 Subject: [PATCH 026/355] [Update] transcription receive (only main window) --- main.py | 74 ++++++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 63 insertions(+), 11 deletions(-) diff --git a/main.py b/main.py index 588e8f2e..2c4ead9e 100644 --- a/main.py +++ b/main.py @@ -3,7 +3,7 @@ from vrct_gui import vrct_gui from config import config from model import model -# func transcription +# func transcription send message def sendMicMessage(message): if len(message) > 0: translation = "" @@ -13,29 +13,59 @@ def sendMicMessage(message): elif config.ENABLE_TRANSLATION is False: pass elif model.getTranslatorStatus() is False: - pass + logAuthenticationError() else: translation = model.getInputTranslate(message) - if config.ENABLE_OSC is True: - osc_message = config.MESSAGE_FORMAT.replace("[message]", message) - osc_message = osc_message.replace("[translation]", translation) - model.oscSendMessage(osc_message) - else: - logOSCError() - logTranscriptionSendMessage(message, translation) + if config.ENABLE_TRANSCRIPTION_SEND is True: + if config.ENABLE_OSC is True: + osc_message = config.MESSAGE_FORMAT.replace("[message]", message) + osc_message = osc_message.replace("[translation]", translation) + model.oscSendMessage(osc_message) + else: + logOSCError() + + logTranscriptionSendMessage(message, translation) def startTranscriptionSendMessage(): model.startMicTranscript(sendMicMessage) + vrct_gui.changeMainWindowWidgetsStatus("normal", "All") def stopTranscriptionSendMessage(): model.stopMicTranscript() + vrct_gui.changeMainWindowWidgetsStatus("normal", "All") + +# func transcription receive message +def receiveSpeakerMessage(message): + if len(message) > 0: + translation = "" + if config.ENABLE_TRANSLATION is False: + pass + elif model.getTranslatorStatus() is False: + logAuthenticationError() + else: + translation = model.getOutputTranslate(message) + + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + if config.ENABLE_NOTICE_XSOVERLAY is True: + xsoverlay_message = config.MESSAGE_FORMAT.replace("[message]", message) + xsoverlay_message = xsoverlay_message.replace("[translation]", translation) + model.notificationXSOverlay(xsoverlay_message) + logTranscriptionReceiveMessage(message, translation) + +def startTranscriptionReceiveMessage(): + model.startSpeakerTranscript(receiveSpeakerMessage) + vrct_gui.changeMainWindowWidgetsStatus("normal", "All") + +def stopTranscriptionReceiveMessage(): + model.stopSpeakerTranscript() + vrct_gui.changeMainWindowWidgetsStatus("normal", "All") # func print textbox def logTranslationStatusChange(): textbox_all = getattr(vrct_gui, "textbox_all") textbox_system = getattr(vrct_gui, "textbox_system") - if config.ENABLE_TRANSLATION: + if config.ENABLE_TRANSLATION is True: vrct_gui.printToTextbox(textbox_all, "翻訳機能をONにしました", "", "INFO") vrct_gui.printToTextbox(textbox_system, "翻訳機能をONにしました", "", "INFO") else: @@ -45,7 +75,7 @@ def logTranslationStatusChange(): def logTranscriptionSendStatusChange(): textbox_all = getattr(vrct_gui, "textbox_all") textbox_system = getattr(vrct_gui, "textbox_system") - if config.ENABLE_TRANSCRIPTION_SEND: + if config.ENABLE_TRANSCRIPTION_SEND is True: vrct_gui.printToTextbox(textbox_all, "Voice2chatbox機能をONにしました", "", "INFO") vrct_gui.printToTextbox(textbox_system, "Voice2chatbox機能をONにしました", "", "INFO") else: @@ -58,6 +88,12 @@ def logTranscriptionSendMessage(message, translate): vrct_gui.printToTextbox(textbox_all, message, translate, "SEND") vrct_gui.printToTextbox(textbox_sent, message, translate, "SEND") +def logTranscriptionReceiveMessage(message, translate): + textbox_all = getattr(vrct_gui, "textbox_all") + textbox_sent = getattr(vrct_gui, "textbox_received") + vrct_gui.printToTextbox(textbox_all, message, translate, "RECEIVE") + vrct_gui.printToTextbox(textbox_sent, message, translate, "RECEIVE") + def logDetectWordFilter(message): textbox_all = getattr(vrct_gui, "textbox_all") textbox_system = getattr(vrct_gui, "textbox_system") @@ -82,6 +118,7 @@ def toggleTranslationFeature(): logTranslationStatusChange() def toggleTranscriptionSendFeature(): + vrct_gui.changeMainWindowWidgetsStatus("disabled", "All") config.ENABLE_TRANSCRIPTION_SEND = getattr(vrct_gui, "transcription_send_switch_box").get() if config.ENABLE_TRANSCRIPTION_SEND is True: th_startTranscriptionSendMessage = Thread(target=startTranscriptionSendMessage) @@ -93,6 +130,19 @@ def toggleTranscriptionSendFeature(): th_stopTranscriptionSendMessage.start() logTranscriptionSendStatusChange() +def toggleTranscriptionReceiveFeature(): + vrct_gui.changeMainWindowWidgetsStatus("disabled", "All") + config.ENABLE_TRANSCRIPTION_RECEIVE = getattr(vrct_gui, "transcription_receive_switch_box").get() + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + th_startTranscriptionReceiveMessage = Thread(target=startTranscriptionReceiveMessage) + th_startTranscriptionReceiveMessage.daemon = True + th_startTranscriptionReceiveMessage.start() + else: + th_stopTranscriptionReceiveMessage = Thread(target=stopTranscriptionReceiveMessage) + th_stopTranscriptionReceiveMessage.daemon = True + th_stopTranscriptionReceiveMessage.start() + logTranscriptionSendStatusChange() + # create GUI vrct_gui.createGUI() @@ -115,6 +165,8 @@ translation_switch_box = getattr(vrct_gui, "translation_switch_box") translation_switch_box.configure(command=toggleTranslationFeature) transcription_send_switch_box = getattr(vrct_gui, "transcription_send_switch_box") transcription_send_switch_box.configure(command=toggleTranscriptionSendFeature) +transcription_receive_switch_box = getattr(vrct_gui, "transcription_receive_switch_box") +transcription_receive_switch_box.configure(command=toggleTranscriptionReceiveFeature) if __name__ == "__main__": vrct_gui.startMainLoop() \ No newline at end of file From a9b7073a082140443548054600ab8df06ba9d322 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 27 Aug 2023 10:53:10 +0900 Subject: [PATCH 027/355] [Update] foreground (only main window) --- main.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/main.py b/main.py index 2c4ead9e..5a488009 100644 --- a/main.py +++ b/main.py @@ -112,6 +112,16 @@ def logOSCError(): vrct_gui.printToTextbox(textbox_all, "OSC is not enabled, please enable OSC and rejoin", "", "INFO") vrct_gui.printToTextbox(textbox_system, "OSC is not enabled, please enable OSC and rejoin", "", "INFO") +def logForegroundStatusChange(): + textbox_all = getattr(vrct_gui, "textbox_all") + textbox_system = getattr(vrct_gui, "textbox_system") + if config.ENABLE_FOREGROUND is True: + vrct_gui.printToTextbox(textbox_all, "Start foreground", "", "INFO") + vrct_gui.printToTextbox(textbox_system, "Start foreground", "", "INFO") + else: + vrct_gui.printToTextbox(textbox_all, "Stop foreground", "", "INFO") + vrct_gui.printToTextbox(textbox_system, "Stop foreground", "", "INFO") + # command func def toggleTranslationFeature(): config.ENABLE_TRANSLATION = getattr(vrct_gui, "translation_switch_box").get() @@ -143,6 +153,14 @@ def toggleTranscriptionReceiveFeature(): th_stopTranscriptionReceiveMessage.start() logTranscriptionSendStatusChange() +def toggleForegroundFeature(): + config.ENABLE_FOREGROUND = getattr(vrct_gui, "foreground_switch_box").get() + if config.ENABLE_FOREGROUND is True: + vrct_gui.attributes("-topmost", True) + else: + vrct_gui.attributes("-topmost", False) + logForegroundStatusChange() + # create GUI vrct_gui.createGUI() @@ -167,6 +185,8 @@ transcription_send_switch_box = getattr(vrct_gui, "transcription_send_switch_box transcription_send_switch_box.configure(command=toggleTranscriptionSendFeature) transcription_receive_switch_box = getattr(vrct_gui, "transcription_receive_switch_box") transcription_receive_switch_box.configure(command=toggleTranscriptionReceiveFeature) +foreground_switch_box = getattr(vrct_gui, "foreground_switch_box") +foreground_switch_box.configure(command=toggleForegroundFeature) if __name__ == "__main__": vrct_gui.startMainLoop() \ No newline at end of file From 419766a2fc3f70cca1e0f61eab07119040e599cf Mon Sep 17 00:00:00 2001 From: misygauziya Date: Sun, 27 Aug 2023 13:30:03 +0900 Subject: [PATCH 028/355] [Update] message box (only main window) --- main.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 60 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 5a488009..f3f3d73d 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ from threading import Thread +import customtkinter from vrct_gui import vrct_gui from config import config from model import model @@ -25,7 +26,7 @@ def sendMicMessage(message): else: logOSCError() - logTranscriptionSendMessage(message, translation) + logSendMessage(message, translation) def startTranscriptionSendMessage(): model.startMicTranscript(sendMicMessage) @@ -51,7 +52,7 @@ def receiveSpeakerMessage(message): xsoverlay_message = config.MESSAGE_FORMAT.replace("[message]", message) xsoverlay_message = xsoverlay_message.replace("[translation]", translation) model.notificationXSOverlay(xsoverlay_message) - logTranscriptionReceiveMessage(message, translation) + logReceiveMessage(message, translation) def startTranscriptionReceiveMessage(): model.startSpeakerTranscript(receiveSpeakerMessage) @@ -61,6 +62,55 @@ def stopTranscriptionReceiveMessage(): model.stopSpeakerTranscript() vrct_gui.changeMainWindowWidgetsStatus("normal", "All") +# func message box +def sendChatMessage(message): + if len(message) > 0: + translation = "" + if config.ENABLE_TRANSLATION is False: + pass + elif model.getTranslatorStatus() is False: + logAuthenticationError() + else: + translation = model.getInputTranslate(message) + + # send OSC message + if config.ENABLE_OSC is True: + osc_message = config.MESSAGE_FORMAT.replace("[message]", message) + osc_message = osc_message.replace("[translation]", translation) + model.oscSendMessage(osc_message) + else: + logOSCError() + + # update textbox message log + logSendMessage(message, translation) + + # delete message in entry message box + if config.ENABLE_AUTO_CLEAR_CHATBOX is True: + entry_message_box = getattr(vrct_gui, "entry_message_box") + entry_message_box.delete(0, customtkinter.END) + +def messageBoxPressKeyEnter(e): + model.oscStopSendTyping() + entry_message_box = getattr(vrct_gui, "entry_message_box") + message = entry_message_box.get() + sendChatMessage(message) + +def messageBoxPressKeyAny(e): + model.oscStartSendTyping() + entry_message_box = getattr(vrct_gui, "entry_message_box") + if e.keysym != "??": + if len(e.char) != 0 and e.keysym in config.BREAK_KEYSYM_LIST: + entry_message_box.insert("end", e.char) + return "break" + +def foregroundOffForcefully(e): + if config.ENABLE_FOREGROUND: + vrct_gui.attributes("-topmost", False) + +def foregroundOnForcefully(e): + if config.ENABLE_FOREGROUND: + vrct_gui.attributes("-topmost", True) + # func print textbox def logTranslationStatusChange(): textbox_all = getattr(vrct_gui, "textbox_all") @@ -82,13 +132,13 @@ def logTranscriptionSendStatusChange(): vrct_gui.printToTextbox(textbox_all, "Voice2chatbox機能をOFFにしました", "", "INFO") vrct_gui.printToTextbox(textbox_system, "Voice2chatbox機能をOFFにしました", "", "INFO") -def logTranscriptionSendMessage(message, translate): +def logSendMessage(message, translate): textbox_all = getattr(vrct_gui, "textbox_all") textbox_sent = getattr(vrct_gui, "textbox_sent") vrct_gui.printToTextbox(textbox_all, message, translate, "SEND") vrct_gui.printToTextbox(textbox_sent, message, translate, "SEND") -def logTranscriptionReceiveMessage(message, translate): +def logReceiveMessage(message, translate): textbox_all = getattr(vrct_gui, "textbox_all") textbox_sent = getattr(vrct_gui, "textbox_received") vrct_gui.printToTextbox(textbox_all, message, translate, "RECEIVE") @@ -188,5 +238,11 @@ transcription_receive_switch_box.configure(command=toggleTranscriptionReceiveFea foreground_switch_box = getattr(vrct_gui, "foreground_switch_box") foreground_switch_box.configure(command=toggleForegroundFeature) +entry_message_box = getattr(vrct_gui, "entry_message_box") +entry_message_box.bind("", messageBoxPressKeyEnter) +entry_message_box.bind("", messageBoxPressKeyAny) +entry_message_box.bind("", foregroundOffForcefully) +entry_message_box.bind("", foregroundOnForcefully) + if __name__ == "__main__": vrct_gui.startMainLoop() \ No newline at end of file From cd415df8249315c2a5e8416220dfe0fbeed61385 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 27 Aug 2023 13:58:47 +0900 Subject: [PATCH 029/355] =?UTF-8?q?=E8=B5=B7=E5=8B=95=E6=99=82=E3=81=AB?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E7=94=BB=E9=9D=A2=E3=81=8C=E4=B8=80=E7=9E=AC?= =?UTF-8?q?=E8=A6=8B=E3=81=88=E3=82=8B=E3=81=AE=E3=82=92=E7=84=A1=E3=81=8F?= =?UTF-8?q?=E3=81=97=E3=81=9F=E3=80=82VRCT1.x=E3=81=A8=E5=90=8C=E3=81=98?= =?UTF-8?q?=E3=82=84=E3=82=8A=E6=96=B9=E3=81=A7=E3=81=84=E3=81=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/config_window/ConfigWindow.py | 1 + vrct_gui/vrct_gui.py | 2 -- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index bcee1594..2e524466 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -9,6 +9,7 @@ from config import config class ConfigWindow(CTkToplevel): def __init__(self, vrct_gui, settings): super().__init__() + self.withdraw() self.INPUT_MIC_RECORD_TIMEOUT = 3 self.INPUT_SOURCE_LANG = "aaaaaaaaa" diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 6bcb7198..245527a1 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -49,8 +49,6 @@ class VRCT_GUI(CTk): self.config_window = ConfigWindow(vrct_gui=self, settings=self.settings.config_window) - self.config_window.withdraw() - # self.information_window = ToplevelWindowInformation(self) def createGUI(self): From 644fc4dbb101241b971aab1eff88e7f7d141b08b Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 27 Aug 2023 14:02:30 +0900 Subject: [PATCH 030/355] =?UTF-8?q?=E3=83=A1=E3=82=A4=E3=83=B3=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=20Light=20theme=20=E8=89=B2=E8=AA=BF=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/ui_managers/ColorThemeManager.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index df0fbe50..5e229326 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -232,22 +232,22 @@ class ColorThemeManager(): self.main.TEXTBOX_TAB_BG_HOVERED_COLOR = self.LIGHT_300_COLOR self.main.TEXTBOX_TAB_BG_CLICKED_COLOR = self.LIGHT_350_COLOR self.main.TEXTBOX_TAB_TEXT_ACTIVE_COLOR = self.main.BASIC_TEXT_COLOR - self.main.TEXTBOX_TAB_TEXT_PASSIVE_COLOR = self.LIGHT_500_COLOR + self.main.TEXTBOX_TAB_TEXT_PASSIVE_COLOR = self.LIGHT_600_COLOR self.main.TEXTBOX_ENTRY_TEXT_COLOR = self.LIGHT_800_COLOR self.main.TEXTBOX_ENTRY_TEXT_DISABLED_COLOR = self.LIGHT_500_COLOR self.main.TEXTBOX_ENTRY_BG_COLOR = self.LIGHT_325_COLOR - self.main.TEXTBOX_ENTRY_BORDER_COLOR = self.LIGHT_450_COLOR + self.main.TEXTBOX_ENTRY_BORDER_COLOR = self.LIGHT_400_COLOR self.main.TEXTBOX_ENTRY_PLACEHOLDER_COLOR = self.LIGHT_600_COLOR self.main.TEXTBOX_ENTRY_PLACEHOLDER_DISABLED_COLOR = self.LIGHT_700_COLOR # Sidebar - self.main.SIDEBAR_BG_COLOR = self.LIGHT_375_COLOR + self.main.SIDEBAR_BG_COLOR = self.LIGHT_350_COLOR # Sidebar Features - self.main.SF__BG_COLOR = self.LIGHT_350_COLOR + self.main.SF__BG_COLOR = self.LIGHT_375_COLOR self.main.SF__HOVERED_BG_COLOR = self.LIGHT_300_COLOR self.main.SF__CLICKED_BG_COLOR = self.LIGHT_200_COLOR self.main.SF__TEXT_DISABLED_COLOR = self.LIGHT_500_COLOR From 32b01cf99cf793d5853c43c09a9265acf275e8a7 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Mon, 28 Aug 2023 06:50:20 +0900 Subject: [PATCH 031/355] =?UTF-8?q?[bugfix]=20=E7=BF=BB=E8=A8=B3=E6=9C=AA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=E6=99=82=E3=81=AB()=E3=81=8C=E6=AE=8B?= =?UTF-8?q?=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F=E3=83=90=E3=82=B0=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index f3f3d73d..9e3eb8c7 100644 --- a/main.py +++ b/main.py @@ -20,8 +20,11 @@ def sendMicMessage(message): if config.ENABLE_TRANSCRIPTION_SEND is True: if config.ENABLE_OSC is True: - osc_message = config.MESSAGE_FORMAT.replace("[message]", message) - osc_message = osc_message.replace("[translation]", translation) + if len(translation) > 0: + osc_message = config.MESSAGE_FORMAT.replace("[message]", message) + osc_message = osc_message.replace("[translation]", translation) + else: + osc_message = message model.oscSendMessage(osc_message) else: logOSCError() @@ -75,8 +78,11 @@ def sendChatMessage(message): # send OSC message if config.ENABLE_OSC is True: - osc_message = config.MESSAGE_FORMAT.replace("[message]", message) - osc_message = osc_message.replace("[translation]", translation) + if len(translation) > 0: + osc_message = config.MESSAGE_FORMAT.replace("[message]", message) + osc_message = osc_message.replace("[translation]", translation) + else: + osc_message = message model.oscSendMessage(osc_message) else: logOSCError() From 6e4dd14c3e102a8150f3146a24a5e001ad8ccb2f Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 28 Aug 2023 12:08:47 +0900 Subject: [PATCH 032/355] setting box: add Appearance tab(change the name general to appearance). add item Transparency. --- .../createSideMenuAndSettingsBoxContainers.py | 16 +- .../SettingBoxGenerator.py | 3 +- .../setting_box_containers/__init__.py | 2 +- .../setting_box_appearance/__init__.py | 1 + .../__tmp.py} | 2 +- .../createSettingBox_Appearance.py | 187 ++++++++++++++++++ .../setting_box_general/__init__.py | 1 - 7 files changed, 199 insertions(+), 13 deletions(-) create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/__init__.py rename vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/{setting_box_general/createSettingBox_General.py => setting_box_appearance/__tmp.py} (99%) create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py delete mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/__init__.py diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index 161d5e96..3a28865b 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -6,7 +6,7 @@ from .addConfigSideMenuItem import addConfigSideMenuItem from .createSettingBoxContainer import createSettingBoxContainer -from .setting_box_containers import createSettingBox_General +from .setting_box_containers import createSettingBox_Appearance def createSideMenuAndSettingsBoxContainers(config_window, settings): @@ -45,16 +45,14 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): side_menu_and_setting_box_containers_settings = [ { - "side_menu_tab_attr_name": "side_menu_tab_general", - "label_attr_name": "label_general", - "selected_mark_attr_name": "translation_selected_mark", - "text": "General", + "side_menu_tab_attr_name": "side_menu_tab_appearance", + "label_attr_name": "label_appearance", + "selected_mark_attr_name": "selected_mark_appearance", + "text": "Appearance", "setting_box_container_settings": { - "setting_box_container_attr_name": "setting_box_container_general", + "setting_box_container_attr_name": "setting_box_container_appearance", "setting_boxes": [ - { "section_title": None, "setting_box": createSettingBox_General }, - # { "section_title": "General Section Title", "setting_box": createSettingBox_General }, - # { "section_title": None, "setting_box": createSettingBox_General }, + { "section_title": None, "setting_box": createSettingBox_Appearance }, ] }, "activate_by_default": True, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py index 5e0a530d..c304fbc0 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py @@ -3,6 +3,7 @@ from ctk_scrollable_dropdown import CTkScrollableDropdown from vrct_gui.ui_utils import createButtonWithImage +from typing import Union class SettingBoxGenerator(): @@ -154,7 +155,7 @@ class SettingBoxGenerator(): - def createSettingBoxSlider(self, parent_widget, label_text, desc_text, slider_attr_name, slider_range, slider_number_of_steps, command, variable): + def createSettingBoxSlider(self, parent_widget, label_text, desc_text, slider_attr_name, slider_range, command, variable, slider_number_of_steps: Union[int, None] = None): (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, label_text, desc_text) setting_box_slider_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py index f992f8c0..d0733fd4 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py @@ -1 +1 @@ -from .setting_box_general import createSettingBox_General \ No newline at end of file +from .setting_box_appearance import createSettingBox_Appearance \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/__init__.py new file mode 100644 index 00000000..7dd435d8 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/__init__.py @@ -0,0 +1 @@ +from .createSettingBox_Appearance import createSettingBox_Appearance \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/createSettingBox_General.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/__tmp.py similarity index 99% rename from vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/createSettingBox_General.py rename to vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/__tmp.py index 70fd7bd3..51b4c0e6 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/createSettingBox_General.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/__tmp.py @@ -7,7 +7,7 @@ from ..SettingBoxGenerator import SettingBoxGenerator from config import config -def createSettingBox_General(setting_box_wrapper, config_window, settings): +def __tmp(setting_box_wrapper, config_window, settings): sbg = SettingBoxGenerator(config_window, settings) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py new file mode 100644 index 00000000..75fdab29 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -0,0 +1,187 @@ +from time import sleep + +from customtkinter import StringVar, IntVar + + +from ..SettingBoxGenerator import SettingBoxGenerator + +from config import config + +def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): + + + sbg = SettingBoxGenerator(config_window, settings) + + createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu + createSettingBoxSwitch = sbg.createSettingBoxSwitch + createSettingBoxCheckbox = sbg.createSettingBoxCheckbox + createSettingBoxSlider = sbg.createSettingBoxSlider + createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider + createSettingBoxEntry = sbg.createSettingBoxEntry + + + + # 関数名は変えるかもしれない。 + def slider_transparency_callback(value, slider_widget): + # self.parent.wm_attributes("-alpha", int(value/100)) + config.TRANSPARENCY = int(value) + + + + row=0 + config_window.sb__transparency = createSettingBoxSlider( + parent_widget=setting_box_wrapper, + label_text="Transparency", + desc_text="It'll change window's transparency. (50% ~ 100%)", + slider_attr_name="sb__transparency_slider", + slider_range=(50, 100), + command=lambda value: slider_transparency_callback(value, config_window.sb__transparency_slider), + variable=IntVar(value=config.TRANSPARENCY), + ) + config_window.sb__transparency.grid(row=row) + row+=1 + + + + # config_window.sb__dropdown_menu_1 = createSettingBoxDropdownMenu( + # parent_widget=setting_box_wrapper, + # label_text="Option Menu", + # desc_text="Select your preferences from the options menu.\nYou can choose your preferred language.", + # optionmenu_attr_name="optionmenu_attr_name_1", + # dropdown_menu_attr_name="dropdown_menu_attr_name_1", + # dropdown_menu_values=["tt", "Japanese", "English"], + # command=lambda value: dropdownMenuFun(value, config_window.optionmenu_attr_name_1, config_window.dropdown_menu_attr_name_1), + # variable=StringVar(value=config.INPUT_SOURCE_LANG) + # ) + # config_window.sb__dropdown_menu_1.grid(row=row) + # row+=1 + + + # config_window.sb__dropdown_menu_2 = createSettingBoxDropdownMenu( + # parent_widget=setting_box_wrapper, + # label_text="Option Menu", + # desc_text="Select your preferences from the options menu.\nYou can choose your preferred language.", + # optionmenu_attr_name="optionmenu_attr_name_2", + # dropdown_menu_attr_name="dropdown_menu_attr_name_1", + # dropdown_menu_values=["tt", "Japanese", "English"], + # command=lambda value: dropdownMenuFun(value, config_window.optionmenu_attr_name_2, config_window.dropdown_menu_attr_name_1), + # variable=StringVar(value=config.INPUT_SOURCE_LANG) + # ) + # config_window.sb__dropdown_menu_2.grid(row=row) + # row+=1 + + # config_window.sb__switch_1 = createSettingBoxSwitch( + # parent_widget=setting_box_wrapper, + # label_text="Switch", + # desc_text="Turning this switch on will bring happiness.\nAs for turning it off... I leave that to your imagination", + # switch_attr_name="switch_attr_name_1", + # command=lambda: switchFun(config_window.switch_attr_name_1), + # is_checked=True, + # ) + # config_window.sb__switch_1.grid(row=row) + # row+=1 + + # config_window.sb__checkbox_1 = createSettingBoxCheckbox( + # parent_widget=setting_box_wrapper, + # label_text="Checkbox", + # desc_text="Checkbox ticked, a checkmark.", + # checkbox_attr_name="checkbox_attr_name_1", + # command=lambda: checkboxFun(config_window.checkbox_attr_name_1), + # is_checked=False, + # ) + # config_window.sb__checkbox_1.grid(row=row) + # row+=1 + + # config_window.sb__slider_1 = createSettingBoxSlider( + # parent_widget=setting_box_wrapper, + # label_text="Slider", + # desc_text="Adjust using the slider; the balance is up to you.", + # slider_attr_name="slider_attr_name_1", + # slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), + # slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, + # command=lambda value: sliderFun(value, config_window.slider_attr_name_1), + # variable=IntVar(value=config_window.INPUT_SPEAKER_ENERGY_THRESHOLD), + # ) + # config_window.sb__slider_1.grid(row=row) + # row+=1 + + + # config_window.sb__progressbar_x_slider_1 = createSettingBoxProgressbarXSlider( + # parent_widget=setting_box_wrapper, + # label_text="Progressbar and Slider for check the threshold", + # desc_text="just the slider to modify the threshold for activating voice input.\nPress the microphone button to start input, and you can adjust it while monitoring the actual volume.", + # command=set_input_threshold, # ? + # variable=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), + # entry_attr_name="progressbar_x_slider__entry_attr_name_1", + + + # slider_attr_name="progressbar_x_slider__slider_attr_name_1", + # slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), + # slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, + + # progressbar_attr_name="progressbar_x_slider__progressbar_attr_name_1", + + # passive_button_attr_name="progressbar_x_slider__passive_button_attr_name_1", + # passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + # e, + # config_window.progressbar_x_slider__passive_button_attr_name_1, + # config_window.progressbar_x_slider__active_button_attr_name_1, + # is_turned_on=True, + # ), + # active_button_attr_name="progressbar_x_slider__active_button_attr_name_1", + # active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + # e, + # config_window.progressbar_x_slider__passive_button_attr_name_1, + # config_window.progressbar_x_slider__active_button_attr_name_1, + # is_turned_on=False, + # ), + # button_image_filename="mic_icon_white.png" + # ) + # config_window.sb__progressbar_x_slider_1.grid(row=row) + # row+=1 + + # config_window.sb__progressbar_x_slider_2 = createSettingBoxProgressbarXSlider( + # parent_widget=setting_box_wrapper, + # label_text="Progressbar and Slider for check the threshold2", + # desc_text="just the slider to modify the threshold for activating voice input.\nPress the microphone button to start input, and you can adjust it while monitoring the actual volume.", + # command=set_input_threshold, # ? + # variable=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), + + # entry_attr_name="progressbar_x_slider__entry_attr_name_2", + + + # slider_attr_name="progressbar_x_slider__slider_attr_name_2", + # slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), + # slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, + # progressbar_attr_name="progressbar_x_slider__progressbar_attr_name_2", + + # passive_button_attr_name="progressbar_x_slider__passive_button_attr_name_2", + # passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + # e, + # config_window.progressbar_x_slider__passive_button_attr_name_2, + # config_window.progressbar_x_slider__active_button_attr_name_2, + # is_turned_on=True, + # ), + # active_button_attr_name="progressbar_x_slider__active_button_attr_name_2", + # active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + # e, + # config_window.progressbar_x_slider__passive_button_attr_name_2, + # config_window.progressbar_x_slider__active_button_attr_name_2, + # is_turned_on=False, + # ), + # button_image_filename="headphones_icon_white.png" + # ) + # config_window.sb__progressbar_x_slider_2.grid(row=row) + # row+=1 + + # config_window.sb__entry_1 = createSettingBoxEntry( + # parent_widget=setting_box_wrapper, + # label_text="Entry", + # desc_text="Please input a numerical value.", + # entry_attr_name="entry_attr_name_1", + # entry_width=settings.uism.SB__ENTRY_WIDTH_100, + # entry_bind__Any_KeyRelease=lambda value: entryFun(value, config_window.entry_attr_name_1), + # entry_textvariable=IntVar(value=config_window.INPUT_MIC_PHRASE_TIMEOUT), + # ) + # config_window.sb__entry_1.grid(row=row, pady=0) + # row+=1 \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/__init__.py deleted file mode 100644 index c0c243b9..00000000 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_general/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from .createSettingBox_General import createSettingBox_General \ No newline at end of file From 9ec37061e59c0dc450e9b15cfc11284ba73202a0 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 28 Aug 2023 13:42:55 +0900 Subject: [PATCH 033/355] Setting box: add item appearance theme. and fixed a bit --- .../createSettingBox_Appearance.py | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index 75fdab29..33d144ec 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -22,20 +22,26 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): # 関数名は変えるかもしれない。 - def slider_transparency_callback(value, slider_widget): + # テーマ変更、フォント変更時、 Widget再生成か再起動かは検討中 + + + def slider_transparency_callback(value): # self.parent.wm_attributes("-alpha", int(value/100)) config.TRANSPARENCY = int(value) + def optionmenu_appearance_theme_callback(value): + config.APPEARANCE_THEME = value + row=0 config_window.sb__transparency = createSettingBoxSlider( parent_widget=setting_box_wrapper, label_text="Transparency", - desc_text="It'll change window's transparency. (50% ~ 100%)", + desc_text="It will change window's transparency. 50% to 100%. (Default: 100%)", slider_attr_name="sb__transparency_slider", slider_range=(50, 100), - command=lambda value: slider_transparency_callback(value, config_window.sb__transparency_slider), + command=lambda value: slider_transparency_callback(value), variable=IntVar(value=config.TRANSPARENCY), ) config_window.sb__transparency.grid(row=row) @@ -43,18 +49,18 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): - # config_window.sb__dropdown_menu_1 = createSettingBoxDropdownMenu( - # parent_widget=setting_box_wrapper, - # label_text="Option Menu", - # desc_text="Select your preferences from the options menu.\nYou can choose your preferred language.", - # optionmenu_attr_name="optionmenu_attr_name_1", - # dropdown_menu_attr_name="dropdown_menu_attr_name_1", - # dropdown_menu_values=["tt", "Japanese", "English"], - # command=lambda value: dropdownMenuFun(value, config_window.optionmenu_attr_name_1, config_window.dropdown_menu_attr_name_1), - # variable=StringVar(value=config.INPUT_SOURCE_LANG) - # ) - # config_window.sb__dropdown_menu_1.grid(row=row) - # row+=1 + config_window.sb__appearance_theme = createSettingBoxDropdownMenu( + parent_widget=setting_box_wrapper, + label_text="Theme", + desc_text="You can choose the color theme from \"Light\" and \"Dark\". If you select \"System\", It will adjust based on your Windows theme. (Default: System)", + optionmenu_attr_name="sb__appearance_theme_optionmenu", + dropdown_menu_attr_name="sb__appearance_theme_optionmenu_dropdown", + dropdown_menu_values=["Light", "Dark", "System"], + command=lambda value: optionmenu_appearance_theme_callback(value), + variable=StringVar(value=config.APPEARANCE_THEME) + ) + config_window.sb__appearance_theme.grid(row=row) + row+=1 # config_window.sb__dropdown_menu_2 = createSettingBoxDropdownMenu( From 84d76ed9a40c62aa5e8650d154c51718f30a0cc0 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 28 Aug 2023 14:43:34 +0900 Subject: [PATCH 034/355] Setting box: add items UI Scaling, Font Family, UI Language. add the function get_key_by_value to utils.py. and fixed a bit --- utils.py | 8 +- .../createSettingBox_Appearance.py | 187 +++++------------- 2 files changed, 61 insertions(+), 134 deletions(-) diff --git a/utils.py b/utils.py index 3033cb13..a68474fb 100644 --- a/utils.py +++ b/utils.py @@ -3,4 +3,10 @@ from PIL.Image import open as Image_open def getImageFile(file_name): img = Image_open(os_path.join(os_path.dirname(__file__), "img", file_name)) - return img \ No newline at end of file + return img + +def get_key_by_value(dictionary, value): + for key, val in dictionary.items(): + if val == value: + return key + return None \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index 33d144ec..e209e8af 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -1,6 +1,9 @@ from time import sleep from customtkinter import StringVar, IntVar +from tkinter import font as tk_font +from languages import selectable_languages +from utils import get_key_by_value from ..SettingBoxGenerator import SettingBoxGenerator @@ -13,12 +16,7 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): sbg = SettingBoxGenerator(config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu - createSettingBoxSwitch = sbg.createSettingBoxSwitch - createSettingBoxCheckbox = sbg.createSettingBoxCheckbox createSettingBoxSlider = sbg.createSettingBoxSlider - createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider - createSettingBoxEntry = sbg.createSettingBoxEntry - # 関数名は変えるかもしれない。 @@ -32,13 +30,22 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): def optionmenu_appearance_theme_callback(value): config.APPEARANCE_THEME = value + def optionmenu_ui_scaling_callback(value): + # self.optionmenu_ui_scaling.set(choice) + # new_scaling_float = int(choice.replace("%", "")) / 100 + config.UI_SCALING = value + def optionmenu_font_family_callback(value): + config.FONT_FAMILY = value + + def optionmenu_ui_language_callback(value): + config.UI_LANGUAGE = get_key_by_value(selectable_languages, value) row=0 config_window.sb__transparency = createSettingBoxSlider( parent_widget=setting_box_wrapper, label_text="Transparency", - desc_text="It will change window's transparency. 50% to 100%. (Default: 100%)", + desc_text="Change the window's transparency. 50% to 100%. (Default: 100%)", slider_attr_name="sb__transparency_slider", slider_range=(50, 100), command=lambda value: slider_transparency_callback(value), @@ -48,13 +55,12 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): row+=1 - config_window.sb__appearance_theme = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, label_text="Theme", - desc_text="You can choose the color theme from \"Light\" and \"Dark\". If you select \"System\", It will adjust based on your Windows theme. (Default: System)", + desc_text="Change the color theme from \"Light\" and \"Dark\". If you select \"System\", It will adjust based on your Windows theme. (Default: System)", optionmenu_attr_name="sb__appearance_theme_optionmenu", - dropdown_menu_attr_name="sb__appearance_theme_optionmenu_dropdown", + dropdown_menu_attr_name="sb__appearance_theme_dropdown", dropdown_menu_values=["Light", "Dark", "System"], command=lambda value: optionmenu_appearance_theme_callback(value), variable=StringVar(value=config.APPEARANCE_THEME) @@ -63,131 +69,46 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): row+=1 - # config_window.sb__dropdown_menu_2 = createSettingBoxDropdownMenu( - # parent_widget=setting_box_wrapper, - # label_text="Option Menu", - # desc_text="Select your preferences from the options menu.\nYou can choose your preferred language.", - # optionmenu_attr_name="optionmenu_attr_name_2", - # dropdown_menu_attr_name="dropdown_menu_attr_name_1", - # dropdown_menu_values=["tt", "Japanese", "English"], - # command=lambda value: dropdownMenuFun(value, config_window.optionmenu_attr_name_2, config_window.dropdown_menu_attr_name_1), - # variable=StringVar(value=config.INPUT_SOURCE_LANG) - # ) - # config_window.sb__dropdown_menu_2.grid(row=row) - # row+=1 - - # config_window.sb__switch_1 = createSettingBoxSwitch( - # parent_widget=setting_box_wrapper, - # label_text="Switch", - # desc_text="Turning this switch on will bring happiness.\nAs for turning it off... I leave that to your imagination", - # switch_attr_name="switch_attr_name_1", - # command=lambda: switchFun(config_window.switch_attr_name_1), - # is_checked=True, - # ) - # config_window.sb__switch_1.grid(row=row) - # row+=1 - - # config_window.sb__checkbox_1 = createSettingBoxCheckbox( - # parent_widget=setting_box_wrapper, - # label_text="Checkbox", - # desc_text="Checkbox ticked, a checkmark.", - # checkbox_attr_name="checkbox_attr_name_1", - # command=lambda: checkboxFun(config_window.checkbox_attr_name_1), - # is_checked=False, - # ) - # config_window.sb__checkbox_1.grid(row=row) - # row+=1 - - # config_window.sb__slider_1 = createSettingBoxSlider( - # parent_widget=setting_box_wrapper, - # label_text="Slider", - # desc_text="Adjust using the slider; the balance is up to you.", - # slider_attr_name="slider_attr_name_1", - # slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), - # slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, - # command=lambda value: sliderFun(value, config_window.slider_attr_name_1), - # variable=IntVar(value=config_window.INPUT_SPEAKER_ENERGY_THRESHOLD), - # ) - # config_window.sb__slider_1.grid(row=row) - # row+=1 + config_window.sb__ui_scaling = createSettingBoxDropdownMenu( + parent_widget=setting_box_wrapper, + label_text="UI Size", + desc_text="(Default: 100%)", + optionmenu_attr_name="sb__ui_scaling_optionmenu", + dropdown_menu_attr_name="sb__ui_scaling_dropdown", + dropdown_menu_values=["80%", "90%", "100%", "110%", "120%"], + command=lambda value: optionmenu_ui_scaling_callback(value), + variable=StringVar(value=config.UI_SCALING) + ) + config_window.sb__ui_scaling.grid(row=row) + row+=1 - # config_window.sb__progressbar_x_slider_1 = createSettingBoxProgressbarXSlider( - # parent_widget=setting_box_wrapper, - # label_text="Progressbar and Slider for check the threshold", - # desc_text="just the slider to modify the threshold for activating voice input.\nPress the microphone button to start input, and you can adjust it while monitoring the actual volume.", - # command=set_input_threshold, # ? - # variable=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), - # entry_attr_name="progressbar_x_slider__entry_attr_name_1", + # font_families = list(tk_font.families()) + config_window.sb__font_family = createSettingBoxDropdownMenu( + parent_widget=setting_box_wrapper, + label_text="Font Family", + desc_text="(Default: Yu Gothic UI)", + optionmenu_attr_name="sb__font_family_optionmenu", + dropdown_menu_attr_name="sb__font_family_dropdown", + dropdown_menu_values=["Font A", "Font B"], + # dropdown_menu_values=font_families, + command=lambda value: optionmenu_font_family_callback(value), + variable=StringVar(value=config.FONT_FAMILY) + ) + config_window.sb__font_family.grid(row=row) + row+=1 - # slider_attr_name="progressbar_x_slider__slider_attr_name_1", - # slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), - # slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, - - # progressbar_attr_name="progressbar_x_slider__progressbar_attr_name_1", - - # passive_button_attr_name="progressbar_x_slider__passive_button_attr_name_1", - # passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - # e, - # config_window.progressbar_x_slider__passive_button_attr_name_1, - # config_window.progressbar_x_slider__active_button_attr_name_1, - # is_turned_on=True, - # ), - # active_button_attr_name="progressbar_x_slider__active_button_attr_name_1", - # active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - # e, - # config_window.progressbar_x_slider__passive_button_attr_name_1, - # config_window.progressbar_x_slider__active_button_attr_name_1, - # is_turned_on=False, - # ), - # button_image_filename="mic_icon_white.png" - # ) - # config_window.sb__progressbar_x_slider_1.grid(row=row) - # row+=1 - - # config_window.sb__progressbar_x_slider_2 = createSettingBoxProgressbarXSlider( - # parent_widget=setting_box_wrapper, - # label_text="Progressbar and Slider for check the threshold2", - # desc_text="just the slider to modify the threshold for activating voice input.\nPress the microphone button to start input, and you can adjust it while monitoring the actual volume.", - # command=set_input_threshold, # ? - # variable=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), - - # entry_attr_name="progressbar_x_slider__entry_attr_name_2", - - - # slider_attr_name="progressbar_x_slider__slider_attr_name_2", - # slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), - # slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, - # progressbar_attr_name="progressbar_x_slider__progressbar_attr_name_2", - - # passive_button_attr_name="progressbar_x_slider__passive_button_attr_name_2", - # passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - # e, - # config_window.progressbar_x_slider__passive_button_attr_name_2, - # config_window.progressbar_x_slider__active_button_attr_name_2, - # is_turned_on=True, - # ), - # active_button_attr_name="progressbar_x_slider__active_button_attr_name_2", - # active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - # e, - # config_window.progressbar_x_slider__passive_button_attr_name_2, - # config_window.progressbar_x_slider__active_button_attr_name_2, - # is_turned_on=False, - # ), - # button_image_filename="headphones_icon_white.png" - # ) - # config_window.sb__progressbar_x_slider_2.grid(row=row) - # row+=1 - - # config_window.sb__entry_1 = createSettingBoxEntry( - # parent_widget=setting_box_wrapper, - # label_text="Entry", - # desc_text="Please input a numerical value.", - # entry_attr_name="entry_attr_name_1", - # entry_width=settings.uism.SB__ENTRY_WIDTH_100, - # entry_bind__Any_KeyRelease=lambda value: entryFun(value, config_window.entry_attr_name_1), - # entry_textvariable=IntVar(value=config_window.INPUT_MIC_PHRASE_TIMEOUT), - # ) - # config_window.sb__entry_1.grid(row=row, pady=0) - # row+=1 \ No newline at end of file + selectable_languages_values = list(selectable_languages.values()) + config_window.sb__ui_language = createSettingBoxDropdownMenu( + parent_widget=setting_box_wrapper, + label_text="UI Language", + desc_text="(Default: English)", + optionmenu_attr_name="sb__ui_language_optionmenu", + dropdown_menu_attr_name="sb__ui_language_dropdown", + dropdown_menu_values=selectable_languages_values, + command=lambda value: optionmenu_ui_language_callback(value), + variable=StringVar(value=selectable_languages[config.UI_LANGUAGE]), + ) + config_window.sb__ui_language.grid(row=row) + row+=1 \ No newline at end of file From 3cc8fa885a46c15dc3e020238aa8ce39cce9bc84 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 28 Aug 2023 15:35:28 +0900 Subject: [PATCH 035/355] chore: Remove the instance variable that are no longer in use. --- vrct_gui/config_window/ConfigWindow.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 2e524466..f0f27f5b 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -11,12 +11,6 @@ class ConfigWindow(CTkToplevel): super().__init__() self.withdraw() - self.INPUT_MIC_RECORD_TIMEOUT = 3 - self.INPUT_SOURCE_LANG = "aaaaaaaaa" - self.INPUT_SPEAKER_ENERGY_THRESHOLD = 300 - self.MAX_SPEAKER_ENERGY_THRESHOLD = 4000 - self.INPUT_MIC_PHRASE_TIMEOUT = 3 - # configure window self.title("test config_window.py") From 3a52c9ad1e927762df7af9fedf816749dfa430f4 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 28 Aug 2023 18:41:43 +0900 Subject: [PATCH 036/355] Setting box: add Transcription tab. add items Mic Host, Mic Device, and Mic Energy Threshold include Dynamic one. --- .../createSideMenuAndSettingsBoxContainers.py | 14 +- .../setting_box_containers/__init__.py | 3 +- .../{setting_box_appearance => }/__tmp.py | 2 +- .../setting_box_transcription/__init__.py | 1 + .../createSettingBox_Mic.py | 268 ++++++++++++++++++ 5 files changed, 279 insertions(+), 9 deletions(-) rename vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/{setting_box_appearance => }/__tmp.py (99%) create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/__init__.py create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index 3a28865b..e31ec0e8 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -6,7 +6,7 @@ from .addConfigSideMenuItem import addConfigSideMenuItem from .createSettingBoxContainer import createSettingBoxContainer -from .setting_box_containers import createSettingBox_Appearance +from .setting_box_containers import createSettingBox_Appearance, createSettingBox_Mic def createSideMenuAndSettingsBoxContainers(config_window, settings): @@ -55,12 +55,11 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): { "section_title": None, "setting_box": createSettingBox_Appearance }, ] }, - "activate_by_default": True, }, { "side_menu_tab_attr_name": "side_menu_tab_translation", "label_attr_name": "label_translation", - "selected_mark_attr_name": "transcription_send_selected_mark", + "selected_mark_attr_name": "selected_mark_translation", "text": "Translation", "setting_box_container_settings": { "setting_box_container_attr_name": "setting_box_container_translation", @@ -72,19 +71,20 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): { "side_menu_tab_attr_name": "side_menu_tab_transcription", "label_attr_name": "label_transcription", - "selected_mark_attr_name": "transcription_receive_selected_mark", + "selected_mark_attr_name": "selected_mark_transcription", "text": "Transcription", "setting_box_container_settings": { "setting_box_container_attr_name": "setting_box_container_transcription", "setting_boxes": [ - { "section_title": None, "setting_box": None }, + { "section_title": "Mic", "setting_box": createSettingBox_Mic }, ] }, + "activate_by_default": True, }, { "side_menu_tab_attr_name": "side_menu_tab_parameters", "label_attr_name": "label_parameters", - "selected_mark_attr_name": "foreground_selected_mark", + "selected_mark_attr_name": "selected_mark_foreground", "text": "Parameters", "setting_box_container_settings": { "setting_box_container_attr_name": "setting_box_container_parameters", @@ -96,7 +96,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): { "side_menu_tab_attr_name": "side_menu_tab_others", "label_attr_name": "label_others", - "selected_mark_attr_name": "foreground_selected_mark", + "selected_mark_attr_name": "selected_mark_foreground", "text": "Others", "setting_box_container_settings": { "setting_box_container_attr_name": "setting_box_container_others", diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py index d0733fd4..9b5a1f7e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py @@ -1 +1,2 @@ -from .setting_box_appearance import createSettingBox_Appearance \ No newline at end of file +from .setting_box_appearance import createSettingBox_Appearance +from .setting_box_transcription import createSettingBox_Mic \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/__tmp.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__tmp.py similarity index 99% rename from vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/__tmp.py rename to vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__tmp.py index 51b4c0e6..5f0c3abc 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/__tmp.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__tmp.py @@ -3,7 +3,7 @@ from time import sleep from customtkinter import StringVar, IntVar -from ..SettingBoxGenerator import SettingBoxGenerator +from .SettingBoxGenerator import SettingBoxGenerator from config import config diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/__init__.py new file mode 100644 index 00000000..8ab3fc7e --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/__init__.py @@ -0,0 +1 @@ +from .createSettingBox_Mic import createSettingBox_Mic \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py new file mode 100644 index 00000000..3decffc3 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -0,0 +1,268 @@ +from time import sleep + +from customtkinter import StringVar, IntVar + + +from ..SettingBoxGenerator import SettingBoxGenerator + +from config import config + +def createSettingBox_Mic(setting_box_wrapper, config_window, settings): + + + sbg = SettingBoxGenerator(config_window, settings) + + createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu + createSettingBoxSwitch = sbg.createSettingBoxSwitch + createSettingBoxCheckbox = sbg.createSettingBoxCheckbox + createSettingBoxSlider = sbg.createSettingBoxSlider + createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider + createSettingBoxEntry = sbg.createSettingBoxEntry + + + + # def dropdownMenuFun(selected_value): + # print(selected_value) + # config.INPUT_SOURCE_LANG = selected_value + + # def switchFun(_, switch_box_widget): + # print(switch_box_widget.get()) + + # def checkboxFun(_, checkbox_box_widget): + # print(checkbox_box_widget.get()) + + # def sliderFun(value): + # print(value) + + # def entryFun(value): + # config_window.INPUT_MIC_PHRASE_TIMEOUT = int(value) + # print(config_window.INPUT_MIC_PHRASE_TIMEOUT) + + def checkbox_input_speaker_threshold_check_callback(e, passive_button_wrapper_widget, active_button_wrapper_widget, is_turned_on): + print("is_turned_on", is_turned_on) + + if is_turned_on is True: + passive_button_widget = passive_button_wrapper_widget.children["!ctklabel"] + passive_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + passive_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + passive_button_wrapper_widget.update_idletasks() + sleep(1) + + passive_button_wrapper_widget.grid_remove() + active_button_wrapper_widget.grid() + + elif is_turned_on is False: + # active_button_widget = active_button_wrapper_widget.children["!ctklabel"] + # active_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + # active_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + # active_button_wrapper_widget.update_idletasks() + # sleep(3) + + active_button_wrapper_widget.grid_remove() + passive_button_wrapper_widget.grid() + + def optionmenu_mic_host_callback(value): + config.CHOICE_MIC_HOST = value + + def optionmenu_input_mic_device_callback(value): + config.CHOICE_MIC_DEVICE = value + + def slider_input_mic_energy_threshold_callback(value): + config.INPUT_MIC_ENERGY_THRESHOLD = int(value) + + def checkbox_input_mic_dynamic_energy_threshold_callback(checkbox_box_widget): + print(checkbox_box_widget.get()) + config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = checkbox_box_widget.get() + + row=0 + # Mic Host と Mic Device は一つの項目として引っ付ける予定 + config_window.sb__mic_host = createSettingBoxDropdownMenu( + parent_widget=setting_box_wrapper, + label_text="Mic Host", + desc_text="Select the mic host. (Default: ?)", + optionmenu_attr_name="sb__mic_host_optionmenu", + dropdown_menu_attr_name="sb__mic_host_dropdown", + # dropdown_menu_values=model.getListInputHost(), + dropdown_menu_values=["host1", "host2", "host3"], + command=lambda value: optionmenu_mic_host_callback(value), + variable=StringVar(value=config.CHOICE_MIC_HOST) + ) + config_window.sb__mic_host.grid(row=row) + row+=1 + + config_window.sb__mic_device = createSettingBoxDropdownMenu( + parent_widget=setting_box_wrapper, + label_text="Mic Device", + desc_text="Select the mic host. (Default: ?)", + optionmenu_attr_name="sb__mic_device_optionmenu", + dropdown_menu_attr_name="sb__mic_device_dropdown", + # dropdown_menu_values=model.getListInputDevice(), + dropdown_menu_values=["device1", "device2", "device3"], + command=lambda value: optionmenu_input_mic_device_callback(value), + variable=StringVar(value=config.CHOICE_MIC_DEVICE) + ) + config_window.sb__mic_device.grid(row=row) + row+=1 + + + config_window.sb__mic_energy_threshold = createSettingBoxProgressbarXSlider( + parent_widget=setting_box_wrapper, + label_text="Mic Energy Threshold", + desc_text="Slider to modify the threshold for activating voice input.\nPress the microphone button to start input and speak something, so you can adjust it while monitoring the actual volume. 0 to 2000 (Default: 300)", + command=slider_input_mic_energy_threshold_callback, + variable=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), + entry_attr_name="sb__progressbar_x_slider__entry_mic_energy_threshold", + + + slider_attr_name="progressbar_x_slider__slider_mic_energy_threshold", + slider_range=(0, config.MAX_MIC_ENERGY_THRESHOLD), + slider_number_of_steps=config.MAX_MIC_ENERGY_THRESHOLD, + + progressbar_attr_name="sb__progressbar_x_slider__progressbar_mic_energy_threshold", + + passive_button_attr_name="sb__progressbar_x_slider__passive_button_mic_energy_threshold", + passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + e, + config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold, + config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold, + is_turned_on=True, + ), + active_button_attr_name="sb__progressbar_x_slider__active_button_mic_energy_threshold", + active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + e, + config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold, + config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold, + is_turned_on=False, + ), + button_image_filename="mic_icon_white.png" + ) + config_window.sb__mic_energy_threshold.grid(row=row) + row+=1 + + # Mic Dynamic Energy Thresholdも上に引っ付ける予定 + config_window.sb__mic_dynamic_energy_threshold = createSettingBoxCheckbox( + parent_widget=setting_box_wrapper, + label_text="Mic Dynamic Energy Threshold", + desc_text="When this feature is selected, it will automatically adjust in a way that works well, based on the set Mic Energy Threshold.", + checkbox_attr_name="sb__checkbox_mic_dynamic_energy_threshold", + command=lambda: checkbox_input_mic_dynamic_energy_threshold_callback(config_window.sb__checkbox_mic_dynamic_energy_threshold), + is_checked=False, + ) + config_window.sb__mic_dynamic_energy_threshold.grid(row=row) + row+=1 + + # config_window.sb__switch_1 = createSettingBoxSwitch( + # parent_widget=setting_box_wrapper, + # label_text="Switch", + # desc_text="Turning this switch on will bring happiness.\nAs for turning it off... I leave that to your imagination", + # switch_attr_name="switch_attr_name_1", + # command=lambda: switchFun(config_window.switch_attr_name_1), + # is_checked=True, + # ) + # config_window.sb__switch_1.grid(row=row) + # row+=1 + + # config_window.sb__checkbox_1 = createSettingBoxCheckbox( + # parent_widget=setting_box_wrapper, + # label_text="Checkbox", + # desc_text="Checkbox ticked, a checkmark.", + # checkbox_attr_name="checkbox_attr_name_1", + # command=lambda: checkboxFun(config_window.checkbox_attr_name_1), + # is_checked=False, + # ) + # config_window.sb__checkbox_1.grid(row=row) + # row+=1 + + # config_window.sb__slider_1 = createSettingBoxSlider( + # parent_widget=setting_box_wrapper, + # label_text="Slider", + # desc_text="Adjust using the slider; the balance is up to you.", + # slider_attr_name="slider_attr_name_1", + # slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), + # slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, + # command=lambda value: sliderFun(value, config_window.slider_attr_name_1), + # variable=IntVar(value=config_window.INPUT_SPEAKER_ENERGY_THRESHOLD), + # ) + # config_window.sb__slider_1.grid(row=row) + # row+=1 + + + # config_window.sb__progressbar_x_slider_1 = createSettingBoxProgressbarXSlider( + # parent_widget=setting_box_wrapper, + # label_text="Progressbar and Slider for check the threshold", + # desc_text="just the slider to modify the threshold for activating voice input.\nPress the microphone button to start input, and you can adjust it while monitoring the actual volume.", + # command=set_input_threshold, # ? + # variable=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), + # entry_attr_name="progressbar_x_slider__entry_attr_name_1", + + + # slider_attr_name="progressbar_x_slider__slider_attr_name_1", + # slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), + # slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, + + # progressbar_attr_name="progressbar_x_slider__progressbar_attr_name_1", + + # passive_button_attr_name="progressbar_x_slider__passive_button_attr_name_1", + # passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + # e, + # config_window.progressbar_x_slider__passive_button_attr_name_1, + # config_window.progressbar_x_slider__active_button_attr_name_1, + # is_turned_on=True, + # ), + # active_button_attr_name="progressbar_x_slider__active_button_attr_name_1", + # active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + # e, + # config_window.progressbar_x_slider__passive_button_attr_name_1, + # config_window.progressbar_x_slider__active_button_attr_name_1, + # is_turned_on=False, + # ), + # button_image_filename="mic_icon_white.png" + # ) + # config_window.sb__progressbar_x_slider_1.grid(row=row) + # row+=1 + + # config_window.sb__progressbar_x_slider_2 = createSettingBoxProgressbarXSlider( + # parent_widget=setting_box_wrapper, + # label_text="Progressbar and Slider for check the threshold2", + # desc_text="just the slider to modify the threshold for activating voice input.\nPress the microphone button to start input, and you can adjust it while monitoring the actual volume.", + # command=set_input_threshold, # ? + # variable=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), + + # entry_attr_name="progressbar_x_slider__entry_attr_name_2", + + + # slider_attr_name="progressbar_x_slider__slider_attr_name_2", + # slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), + # slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, + # progressbar_attr_name="progressbar_x_slider__progressbar_attr_name_2", + + # passive_button_attr_name="progressbar_x_slider__passive_button_attr_name_2", + # passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + # e, + # config_window.progressbar_x_slider__passive_button_attr_name_2, + # config_window.progressbar_x_slider__active_button_attr_name_2, + # is_turned_on=True, + # ), + # active_button_attr_name="progressbar_x_slider__active_button_attr_name_2", + # active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + # e, + # config_window.progressbar_x_slider__passive_button_attr_name_2, + # config_window.progressbar_x_slider__active_button_attr_name_2, + # is_turned_on=False, + # ), + # button_image_filename="headphones_icon_white.png" + # ) + # config_window.sb__progressbar_x_slider_2.grid(row=row) + # row+=1 + + # config_window.sb__entry_1 = createSettingBoxEntry( + # parent_widget=setting_box_wrapper, + # label_text="Entry", + # desc_text="Please input a numerical value.", + # entry_attr_name="entry_attr_name_1", + # entry_width=settings.uism.SB__ENTRY_WIDTH_100, + # entry_bind__Any_KeyRelease=lambda value: entryFun(value, config_window.entry_attr_name_1), + # entry_textvariable=IntVar(value=config_window.INPUT_MIC_PHRASE_TIMEOUT), + # ) + # config_window.sb__entry_1.grid(row=row, pady=0) + # row+=1 \ No newline at end of file From cf4600acd853d15be36a94ed2a2a8d289e05bfcc Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 28 Aug 2023 21:21:56 +0900 Subject: [PATCH 037/355] Setting box: add items Mic Record Timeout, Mic Phrase Timeout, Mic Max Phrases and Mic Word Filter --- .../SettingBoxGenerator.py | 5 +- .../createSettingBox_Mic.py | 88 ++++++++++++++++++- 2 files changed, 91 insertions(+), 2 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py index c304fbc0..10f4cfe5 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py @@ -294,6 +294,9 @@ class SettingBoxGenerator(): setting_box_entry_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) setting_box_entry_frame.grid(row=0, column=1, padx=0, sticky="e") + def adjusted_command__for_entry_bind__Any_KeyRelease(e): + entry_bind__Any_KeyRelease(e.widget.get()) + entry_widget = CTkEntry( setting_box_entry_frame, width=entry_width, @@ -301,7 +304,7 @@ class SettingBoxGenerator(): textvariable=entry_textvariable, font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__ENTRY_FONT_SIZE, weight="normal"), ) - entry_widget.bind("", entry_bind__Any_KeyRelease) + entry_widget.bind("", adjusted_command__for_entry_bind__Any_KeyRelease) setattr(self.config_window, entry_attr_name, entry_widget) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 3decffc3..54a6b5d0 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -74,6 +74,32 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): print(checkbox_box_widget.get()) config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = checkbox_box_widget.get() + + def entry_input_mic_record_timeout_callback(value): + print(int(value)) + config.INPUT_MIC_RECORD_TIMEOUT = int(value) + + def entry_input_mic_phrase_timeout_callback(value): + print(int(value)) + config.INPUT_MIC_RECORD_TIMEOUT = int(value) + + def entry_input_mic_max_phrases_callback(value): + print(str(value)) + config.INPUT_MIC_MAX_PHRASES = str(value) + + def entry_input_mic_word_filters_callback(value): + word_filter = str(value) + word_filter = [w.strip() for w in word_filter.split(",") if len(w.strip()) > 0] + word_filter = ",".join(word_filter) + print(word_filter) + if len(word_filter) > 0: + config.INPUT_MIC_WORD_FILTER = word_filter.split(",") + else: + config.INPUT_MIC_WORD_FILTER = [] + # model.resetKeywordProcessor() + # model.addKeywords() + + row=0 # Mic Host と Mic Device は一つの項目として引っ付ける予定 config_window.sb__mic_host = createSettingBoxDropdownMenu( @@ -146,11 +172,71 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): desc_text="When this feature is selected, it will automatically adjust in a way that works well, based on the set Mic Energy Threshold.", checkbox_attr_name="sb__checkbox_mic_dynamic_energy_threshold", command=lambda: checkbox_input_mic_dynamic_energy_threshold_callback(config_window.sb__checkbox_mic_dynamic_energy_threshold), - is_checked=False, + is_checked=False ) config_window.sb__mic_dynamic_energy_threshold.grid(row=row) row+=1 + + # 以下3つも一つの項目にまとめるかもしれない + config_window.sb__mic_record_timeout = createSettingBoxEntry( + parent_widget=setting_box_wrapper, + label_text="Mic Record Timeout", + desc_text="(Default: 3)", + entry_attr_name="sb__entry_mic_record_timeout", + entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_bind__Any_KeyRelease=lambda value: entry_input_mic_record_timeout_callback(value), + entry_textvariable=IntVar(value=config.INPUT_MIC_RECORD_TIMEOUT), + ) + config_window.sb__mic_record_timeout.grid(row=row) + row+=1 + + config_window.sb__mic_phrase_timeout = createSettingBoxEntry( + parent_widget=setting_box_wrapper, + label_text="Mic Phrase Timeout", + desc_text="It will stop recording and send the recordings when the set second(s) is reached. (Default: 3)", + entry_attr_name="sb__entry_mic_phrase_timeout", + entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_bind__Any_KeyRelease=lambda value: entry_input_mic_phrase_timeout_callback(value), + entry_textvariable=IntVar(value=config.INPUT_MIC_PHRASE_TIMEOUT), + ) + config_window.sb__mic_phrase_timeout.grid(row=row) + row+=1 + + config_window.sb__mic_max_phrases = createSettingBoxEntry( + parent_widget=setting_box_wrapper, + label_text="Mic Max Phrases", + desc_text="It will stop recording and send the recordings when the set count of phrase(s) is reached. (Default: 10)", + entry_attr_name="sb__entry_mic_max_phrases", + entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_bind__Any_KeyRelease=lambda value: entry_input_mic_max_phrases_callback(value), + entry_textvariable=IntVar(value=config.INPUT_MIC_MAX_PHRASES), + ) + config_window.sb__mic_max_phrases.grid(row=row) + row+=1 + # __________ + + + + if len(config.INPUT_MIC_WORD_FILTER) > 0: + entry_textvariable=StringVar(value=",".join(config.INPUT_MIC_WORD_FILTER)) + else: + entry_textvariable=None + config_window.sb__mic_word_filter = createSettingBoxEntry( + parent_widget=setting_box_wrapper, + label_text="Mic Max Phrases", + desc_text="It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC", + entry_attr_name="sb__entry_mic_word_filter", + entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_bind__Any_KeyRelease=lambda value: entry_input_mic_word_filters_callback(value), + entry_textvariable=entry_textvariable, + ) + config_window.sb__mic_word_filter.grid(row=row) + row+=1 + + + + # config_window.sb__switch_1 = createSettingBoxSwitch( # parent_widget=setting_box_wrapper, # label_text="Switch", From 213b42fd56816b127334e1f494c1967f662aa2da Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 28 Aug 2023 21:23:58 +0900 Subject: [PATCH 038/355] fixed: I forgot to change the label text that word filter. --- .../setting_box_transcription/createSettingBox_Mic.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 54a6b5d0..ffa96775 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -224,7 +224,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): entry_textvariable=None config_window.sb__mic_word_filter = createSettingBoxEntry( parent_widget=setting_box_wrapper, - label_text="Mic Max Phrases", + label_text="Mic Word Filter", desc_text="It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC", entry_attr_name="sb__entry_mic_word_filter", entry_width=settings.uism.SB__ENTRY_WIDTH_100, From 18275b5700a5b3ca336dc32317600cc01f2bcbc7 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Mon, 28 Aug 2023 23:25:13 +0900 Subject: [PATCH 039/355] [Change] transcription_languages.py MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 言語の階層構造を変更 --- .../transcription/transcription_languages.py | 264 ++++++++++++------ 1 file changed, 175 insertions(+), 89 deletions(-) diff --git a/models/transcription/transcription_languages.py b/models/transcription/transcription_languages.py index 2cf6ebd3..26f2c3f6 100644 --- a/models/transcription/transcription_languages.py +++ b/models/transcription/transcription_languages.py @@ -1,91 +1,177 @@ transcription_lang = { - "Japanese (Japan)":"ja-JP", - "English (United States)":"en-US", - "English (United Kingdom)":"en-GB", - "Afrikaans (South Africa)":"af-ZA", - "Arabic (Algeria)":"ar-DZ", - "Arabic (Bahrain)":"ar-BH", - "Arabic (Egypt)":"ar-EG", - "Arabic (Israel)":"ar-IL", - "Arabic (Iraq)":"ar-IQ", - "Arabic (Jordan)":"ar-JO", - "Arabic (Kuwait)":"ar-KW", - "Arabic (Lebanon)":"ar-LB", - "Arabic (Morocco)":"ar-MA", - "Arabic (Oman)":"ar-OM", - "Arabic (State of Palestine)":"ar-PS", - "Arabic (Qatar)":"ar-QA", - "Arabic (Saudi Arabia)":"ar-SA", - "Arabic (Tunisia)":"ar-TN", - "Arabic (United Arab Emirates)":"ar-AE", - "Basque (Spain)":"eu-ES", - "Bulgarian (Bulgaria)":"bg-BG", - "Catalan (Spain)":"ca-ES", - "Chinese, Mandarin (Simplified, China)":"cmn-Hans-CN", - "Chinese, Mandarin (Simplified, Hong Kong)":"cmn-Hans-HK", - "Chinese, Mandarin (Traditional, Taiwan)":"cmn-Hant-TW", - "Chinese, Cantonese (Traditional Hong Kong)":"yue-Hant-HK", - "Croatian (Croatia)":"hr-HR", - "Czech (Czech Republic)":"cs-CZ", - "Danish (Denmark)":"da-DK", - "English (Australia)":"en-AU", - "English (Canada)":"en-CA", - "English (India)":"en-IN", - "English (Ireland)":"en-IE", - "English (New Zealand)":"en-NZ", - "English (Philippines)":"en-PH", - "English (South Africa)":"en-ZA", - "Persian (Iran)":"fa-IR", - "French (France)":"fr-FR", - "Filipino (Philippines)":"fil-PH", - "Galician (Spain)":"gl-ES", - "German (Germany)":"de-DE", - "Greek (Greece)":"el-GR", - "Finnish (Finland)":"fi-FI", - "Hebrew (Israel)":"he-IL", - "Hindi (India)":"hi-IN", - "Hungarian (Hungary)":"hu-HU", - "Indonesian (Indonesia)":"id-ID", - "Icelandic (Iceland)":"is-IS", - "Italian (Italy)":"it-IT", - "Italian (Switzerland)":"it-CH", - "Korean (South Korea)":"ko-KR", - "Lithuanian (Lithuania)":"lt-LT", - "Malay (Malaysia)":"ms-MY", - "Dutch (Netherlands)":"nl-NL", - "Norwegian Bokmål (Norway)":"nb-NO", - "Polish (Poland)":"pl-PL", - "Portuguese (Brazil)":"pt-BR", - "Portuguese (Portugal)":"pt-PT", - "Romanian (Romania)":"ro-RO", - "Russian (Russia)":"ru-RU", - "Serbian (Serbia)":"sr-RS", - "Slovak (Slovakia)":"sk-SK", - "Slovenian (Slovenia)":"sl-SI", - "Spanish (Argentina)":"es-AR", - "Spanish (Bolivia)":"es-BO", - "Spanish (Chile)":"es-CL", - "Spanish (Colombia)":"es-CO", - "Spanish (Costa Rica)":"es-CR", - "Spanish (Dominican Republic)":"es-DO", - "Spanish (Ecuador)":"es-EC", - "Spanish (El Salvador)":"es-SV", - "Spanish (Guatemala)":"es-GT", - "Spanish (Honduras)":"es-HN", - "Spanish (Mexico)":"es-MX", - "Spanish (Nicaragua)":"es-NI", - "Spanish (Panama)":"es-PA", - "Spanish (Paraguay)":"es-PY", - "Spanish (Peru)":"es-PE", - "Spanish (Puerto Rico)":"es-PR", - "Spanish (Spain)":"es-ES", - "Spanish (Uruguay)":"es-UY", - "Spanish (United States)":"es-US", - "Spanish (Venezuela)":"es-VE", - "Swedish (Sweden)":"sv-SE", - "Thai (Thailand)":"th-TH", - "Turkish (Turkey)":"tr-TR", - "Ukrainian (Ukraine)":"uk-UA", - "Vietnamese (Vietnam)":"vi-VN", - "Zulu (South Africa)":"zu-ZA" + "Afrikaans":{ + "South Africa":"af-ZA", + }, + "Arabic":{ + "Algeria":"ar-DZ", + "Bahrain":"ar-BH", + "Egypt":"ar-EG", + "Israel":"ar-IL", + "Iraq":"ar-IQ", + "Jordan":"ar-JO", + "Kuwait":"ar-KW", + "Lebanon":"ar-LB", + "Morocco":"ar-MA", + "Oman":"ar-OM", + "State of Palestine":"ar-PS", + "Qatar":"ar-QA", + "Saudi Arabia":"ar-SA", + "Tunisia":"ar-TN", + "United Arab Emirates":"ar-AE", + }, + "Basque":{ + "Spain":"eu-ES", + }, + "Bulgarian":{ + "Bulgaria":"bg-BG", + }, + "Catalan":{ + "Spain":"ca-ES", + }, + "Chinese":{ + "Mandarin (Simplified, China)":"cmn-Hans-CN", + "Mandarin (Simplified, Hong Kong)":"cmn-Hans-HK", + "Mandarin (Traditional, Taiwan)":"cmn-Hant-TW", + "Cantonese (Traditional Hong Kong)":"yue-Hant-HK", + }, + "Croatian":{ + "Croatia":"hr-HR", + }, + "Czech":{ + "Czech Republic":"cs-CZ", + }, + "Danish":{ + "Denmark":"da-DK", + }, + "Dutch":{ + "Netherlands":"nl-NL", + }, + "English": { + "United States":"en-US", + "United Kingdom":"en-GB", + "Australia":"en-AU", + "Canada":"en-CA", + "India":"en-IN", + "Ireland":"en-IE", + "New Zealand":"en-NZ", + "Philippines":"en-PH", + "South Africa":"en-ZA", + }, + "Filipino":{ + "Philippines":"fil-PH", + }, + "Finnish":{ + "Finland":"fi-FI", + }, + "French":{ + "France":"fr-FR", + }, + "Galician":{ + "Spain":"gl-ES", + }, + "German":{ + "Germany":"de-DE", + }, + "Greek":{ + "Greece":"el-GR", + }, + "Hebrew":{ + "Israel":"he-IL", + }, + "Hindi": { + "India":"hi-IN", + }, + "Hungarian":{ + "Hungary":"hu-HU", + }, + "Indonesian":{ + "Indonesia":"id-ID", + }, + "Icelandic":{ + "Iceland":"is-IS", + }, + "Italian":{ + "Italy":"it-IT", + "Switzerland":"it-CH", + }, + "Japanese":{ + "Japan":"ja-JP", + }, + "Korean":{ + "South Korea":"ko-KR", + }, + "Lithuanian":{ + "Lithuania":"lt-LT", + }, + "Malay":{ + "Malaysia":"ms-MY", + }, + "Norwegian":{ + "Norway":"nb-NO", + }, + "Persian":{ + "Iran":"fa-IR", + }, + "Polish":{ + "Poland":"pl-PL", + }, + "Portuguese":{ + "Brazil":"pt-BR", + "Portugal":"pt-PT", + }, + "Romanian":{ + "Romania":"ro-RO", + }, + "Russian":{ + "Russia":"ru-RU", + }, + "Serbian":{ + "Serbia":"sr-RS", + }, + "Slovak":{ + "Slovakia":"sk-SK", + }, + "Slovenian":{ + "Slovenia":"sl-SI", + }, + "Spanish":{ + "Argentina":"es-AR", + "Bolivia":"es-BO", + "Chile":"es-CL", + "Colombia":"es-CO", + "Costa Rica":"es-CR", + "Dominican Republic":"es-DO", + "Ecuador":"es-EC", + "El Salvador":"es-SV", + "Guatemala":"es-GT", + "Honduras":"es-HN", + "Mexico":"es-MX", + "Nicaragua":"es-NI", + "Panama":"es-PA", + "Paraguay":"es-PY", + "Peru":"es-PE", + "Puerto Rico":"es-PR", + "Spain":"es-ES", + "Uruguay":"es-UY", + "United States":"es-US", + "Venezuela":"es-VE", + }, + "Swedish":{ + "Sweden":"sv-SE", + }, + "Thai":{ + "Thailand":"th-TH", + }, + "Turkish":{ + "Turkey":"tr-TR", + }, + "Ukrainian":{ + "Ukraine":"uk-UA", + }, + "Vietnamese":{ + "Vietnam":"vi-VN", + }, + "Zulu":{ + "South Africa":"zu-ZA" + }, } \ No newline at end of file From c8e4df034949edaed4e7b6ee6599d56ba31d3c62 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Tue, 29 Aug 2023 04:48:44 +0900 Subject: [PATCH 040/355] =?UTF-8?q?=E9=80=94=E4=B8=AD=E3=81=BE=E3=81=A7?= =?UTF-8?q?=E7=BF=BB=E8=A8=B3Engine=E9=83=A8=E5=88=86=E3=81=8C=E3=81=BE?= =?UTF-8?q?=E3=81=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 119 ++++++++++-------- main.py | 95 ++++++++++++++ model.py | 17 ++- .../transcription_transcriber.py | 4 +- .../main_window/widgets/create_sidebar.py | 18 ++- vrct_gui/vrct_gui.py | 3 + 6 files changed, 194 insertions(+), 62 deletions(-) diff --git a/config.py b/config.py index 2b99e4dc..f90f5785 100644 --- a/config.py +++ b/config.py @@ -135,43 +135,43 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property - def INPUT_SOURCE_LANG(self): - return self._INPUT_SOURCE_LANG + def SOURCE_LANGUAGE(self): + return self._SOURCE_LANGUAGE - @INPUT_SOURCE_LANG.setter - def INPUT_SOURCE_LANG(self, value): - if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): - self._INPUT_SOURCE_LANG = value + @SOURCE_LANGUAGE.setter + def SOURCE_LANGUAGE(self, value): + if type(value) is str: + self._SOURCE_LANGUAGE = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property - def INPUT_TARGET_LANG(self): - return self._INPUT_TARGET_LANG + def SOURCE_COUNTRY(self): + return self._SOURCE_COUNTRY - @INPUT_TARGET_LANG.setter - def INPUT_TARGET_LANG(self, value): - if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): - self._INPUT_TARGET_LANG = value + @SOURCE_COUNTRY.setter + def SOURCE_COUNTRY(self, value): + if type(value) is str: + self._SOURCE_COUNTRY = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property - def OUTPUT_SOURCE_LANG(self): - return self._OUTPUT_SOURCE_LANG + def TARGET_LANGUAGE(self): + return self._TARGET_LANGUAGE - @OUTPUT_SOURCE_LANG.setter - def OUTPUT_SOURCE_LANG(self, value): - if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): - self._OUTPUT_SOURCE_LANG = value + @TARGET_LANGUAGE.setter + def TARGET_LANGUAGE(self, value): + if type(value) is str: + self._TARGET_LANGUAGE = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property - def OUTPUT_TARGET_LANG(self): - return self._OUTPUT_TARGET_LANG + def TARGET_COUNTRY(self): + return self._TARGET_COUNTRY - @OUTPUT_TARGET_LANG.setter - def OUTPUT_TARGET_LANG(self, value): - if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): - self._OUTPUT_TARGET_LANG = value + @TARGET_COUNTRY.setter + def TARGET_COUNTRY(self, value): + if type(value) is str: + self._TARGET_COUNTRY = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property @@ -194,16 +194,6 @@ class Config: self._CHOICE_MIC_DEVICE = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property - def INPUT_MIC_VOICE_LANGUAGE(self): - return self._INPUT_MIC_VOICE_LANGUAGE - - @INPUT_MIC_VOICE_LANGUAGE.setter - def INPUT_MIC_VOICE_LANGUAGE(self, value): - if value in list(transcription_lang.keys()): - self._INPUT_MIC_VOICE_LANGUAGE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property def INPUT_MIC_ENERGY_THRESHOLD(self): return self._INPUT_MIC_ENERGY_THRESHOLD @@ -276,16 +266,6 @@ class Config: self._CHOICE_SPEAKER_DEVICE = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property - def INPUT_SPEAKER_VOICE_LANGUAGE(self): - return self._INPUT_SPEAKER_VOICE_LANGUAGE - - @INPUT_SPEAKER_VOICE_LANGUAGE.setter - def INPUT_SPEAKER_VOICE_LANGUAGE(self, value): - if value in list(transcription_lang.keys()): - self._INPUT_SPEAKER_VOICE_LANGUAGE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property def INPUT_SPEAKER_ENERGY_THRESHOLD(self): return self._INPUT_SPEAKER_ENERGY_THRESHOLD @@ -432,6 +412,36 @@ class Config: def MAX_SPEAKER_ENERGY_THRESHOLD(self): return self._MAX_SPEAKER_ENERGY_THRESHOLD + @property + def SELECTED_TAB_NO(self): + return self._SELECTED_TAB_NO + + @SELECTED_TAB_NO.setter + def SELECTED_TAB_NO(self, value): + if type(value) is str: + self._SELECTED_TAB_NO = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def SELECTED_TAB_YOUR_LANGUAGES(self): + return self._SELECTED_TAB_YOUR_LANGUAGES + + @SELECTED_TAB_YOUR_LANGUAGES.setter + def SELECTED_TAB_YOUR_LANGUAGES(self, value): + if type(value) is dict: + self._SELECTED_TAB_YOUR_LANGUAGES = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + def SELECTED_TAB_TARGET_LANGUAGES(self): + return self._SELECTED_TAB_TARGET_LANGUAGES + + @SELECTED_TAB_TARGET_LANGUAGES.setter + def SELECTED_TAB_TARGET_LANGUAGES(self, value): + if type(value) is dict: + self._SELECTED_TAB_TARGET_LANGUAGES = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + def init_config(self): self._VERSION = "1.3.2" self._PATH_CONFIG = "./config.json" @@ -445,13 +455,12 @@ class Config: self._FONT_FAMILY = "Yu Gothic UI" self._UI_LANGUAGE = "en" self._CHOICE_TRANSLATOR = translatorEngine[0] - self._INPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[0] - self._INPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[1] - self._OUTPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[1] - self._OUTPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[0] + self._SOURCE_LANGUAGE = "Japanese" + self._SOURCE_COUNTRY = "Japan" + self._TARGET_LANGUAGE = "English" + self._TARGET_COUNTRY = "United States" self._CHOICE_MIC_HOST = getDefaultInputDevice()["host"]["name"] self._CHOICE_MIC_DEVICE = getDefaultInputDevice()["device"]["name"] - self._INPUT_MIC_VOICE_LANGUAGE = list(transcription_lang.keys())[0] self._INPUT_MIC_ENERGY_THRESHOLD = 300 self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = True self._INPUT_MIC_RECORD_TIMEOUT = 3 @@ -459,7 +468,6 @@ class Config: self._INPUT_MIC_MAX_PHRASES = 10 self._INPUT_MIC_WORD_FILTER = [] self._CHOICE_SPEAKER_DEVICE = getDefaultOutputDevice()["name"] - self._INPUT_SPEAKER_VOICE_LANGUAGE = list(transcription_lang.keys())[1] self._INPUT_SPEAKER_ENERGY_THRESHOLD = 300 self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = True self._INPUT_SPEAKER_RECORD_TIMEOUT = 3 @@ -485,6 +493,17 @@ class Config: ] self._MAX_MIC_ENERGY_THRESHOLD = 2000 self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 + self._SELECTED_TAB_NO = "tab_1" + self._SELECTED_TAB_YOUR_LANGUAGES = { + "tab_1":"Japanese\n(Japan)", + "tab_2":"Japanese\n(Japan)", + "tab_3":"Japanese\n(Japan)", + } + self._SELECTED_TAB_TARGET_LANGUAGES = { + "tab_1":"English\n(United States)", + "tab_2":"English\n(United States)", + "tab_3":"English\n(United States)", + } def load_config(self): if os_path.isfile(self.PATH_CONFIG) is not False: diff --git a/main.py b/main.py index 9e3eb8c7..3c98b8de 100644 --- a/main.py +++ b/main.py @@ -3,6 +3,8 @@ import customtkinter from vrct_gui import vrct_gui from config import config from model import model +from models.translation.translation_languages import translatorEngine, translation_lang +from models.transcription.transcription_languages import transcription_lang # func transcription send message def sendMicMessage(message): @@ -117,6 +119,86 @@ def foregroundOnForcefully(e): if config.ENABLE_FOREGROUND: vrct_gui.attributes("-topmost", True) +# func select languages +def getListLanguageAndCountry(): + langs = [] + for lang in model.SUPPORTED_LANGUAGES: + for country in transcription_lang[lang]: + langs.append(f"{lang}\n({country})") + return langs + +def getLanguageAndState(select): + parts = select.split("\n") + language = parts[0] + country = parts[1][1:-1] + return language, country + +def setYourLanguageAndCountry(select): + languages = config.SELECTED_TAB_YOUR_LANGUAGES + languages[config.SELECTED_TAB_NO] = select + config.SELECTED_TAB_YOUR_LANGUAGES = languages + + language, country = getLanguageAndState(select) + config.SOURCE_LANGUAGE = language + config.SOURCE_COUNTRY = country + +def setTargetLanguageAndCountry(select): + languages = config.SELECTED_TAB_TARGET_LANGUAGES + languages[config.SELECTED_TAB_NO] = select + config.SELECTED_TAB_TARGET_LANGUAGES = languages + + language, country = getLanguageAndState(select) + config.TARGET_LANGUAGE = language + config.TARGET_COUNTRY = country + +def callbackSelectedTabNo1(): + config.SELECTED_TAB_NO = "tab_1" + vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] + vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] + + languages = config.SELECTED_TAB_YOUR_LANGUAGES + select = languages[config.SELECTED_TAB_NO] + language, country = getLanguageAndState(select) + config.SOURCE_LANGUAGE = language + config.SOURCE_COUNTRY = country + languages = config.SELECTED_TAB_TARGET_LANGUAGES + select = languages[config.SELECTED_TAB_NO] + language, country = getLanguageAndState(select) + config.TARGET_LANGUAGE = language + config.TARGET_COUNTRY = country + +def callbackSelectedTabNo2(): + config.SELECTED_TAB_NO = "tab_2" + vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] + vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] + + languages = config.SELECTED_TAB_YOUR_LANGUAGES + select = languages[config.SELECTED_TAB_NO] + language, country = getLanguageAndState(select) + config.SOURCE_LANGUAGE = language + config.SOURCE_COUNTRY = country + languages = config.SELECTED_TAB_TARGET_LANGUAGES + select = languages[config.SELECTED_TAB_NO] + language, country = getLanguageAndState(select) + config.TARGET_LANGUAGE = language + config.TARGET_COUNTRY = country + +def callbackSelectedTabNo3(): + config.SELECTED_TAB_NO = "tab_3" + vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] + vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] + + languages = config.SELECTED_TAB_YOUR_LANGUAGES + select = languages[config.SELECTED_TAB_NO] + language, country = getLanguageAndState(select) + config.SOURCE_LANGUAGE = language + config.SOURCE_COUNTRY = country + languages = config.SELECTED_TAB_TARGET_LANGUAGES + select = languages[config.SELECTED_TAB_NO] + language, country = getLanguageAndState(select) + config.TARGET_LANGUAGE = language + config.TARGET_COUNTRY = country + # func print textbox def logTranslationStatusChange(): textbox_all = getattr(vrct_gui, "textbox_all") @@ -250,5 +332,18 @@ entry_message_box.bind("", messageBoxPressKeyAny) entry_message_box.bind("", foregroundOffForcefully) entry_message_box.bind("", foregroundOnForcefully) +sqls__optionmenu_your_language = getattr(vrct_gui, "sqls__optionmenu_your_language") +sqls__optionmenu_your_language.configure(values=getListLanguageAndCountry()) +sqls__optionmenu_your_language.configure(command=setYourLanguageAndCountry) + +sqls__optionmenu_target_language = getattr(vrct_gui, "sqls__optionmenu_target_language") +sqls__optionmenu_target_language.configure(values=getListLanguageAndCountry()) +sqls__optionmenu_target_language.configure(command=setTargetLanguageAndCountry) + +vrct_gui.CALLBACK_SELECTED_TAB_NO_1 = callbackSelectedTabNo1 +vrct_gui.CALLBACK_SELECTED_TAB_NO_2 = callbackSelectedTabNo2 +vrct_gui.CALLBACK_SELECTED_TAB_NO_3 = callbackSelectedTabNo3 + + if __name__ == "__main__": vrct_gui.startMainLoop() \ No newline at end of file diff --git a/model.py b/model.py index f9b9060b..eabcd022 100644 --- a/model.py +++ b/model.py @@ -29,6 +29,15 @@ class threadFnc(Thread): self.fnc(*self._args, **self._kwargs) class Model: + # Languages available for both transcription and translation + SUPPORTED_LANGUAGES = [ + 'Afrikaans', 'Arabic', 'Basque', 'Bulgarian', 'Catalan', 'Chinese', 'Croatian', + 'Czech', 'Danish', 'Dutch', 'English', 'Filipino', 'Finnish', 'French', 'German', + 'Greek', 'Hebrew', 'Hindi', 'Hungarian', 'Indonesian', 'Italian', 'Japanese', + 'Korean', 'Lithuanian', 'Malay', 'Norwegian', 'Polish', 'Portuguese', 'Romanian', + 'Russian', 'Serbian', 'Slovak', 'Slovenian', 'Spanish', 'Swedish', 'Thai', 'Turkish', + 'Ukrainian', 'Vietnamese' + ] _instance = None def __new__(cls): @@ -75,8 +84,8 @@ class Model: def getInputTranslate(self, message): translation = self.translator.translate( translator_name=config.CHOICE_TRANSLATOR, - source_language=config.INPUT_SOURCE_LANG, - target_language=config.INPUT_TARGET_LANG, + source_language=config.SOURCE_LANGUAGE, + target_language=config.TARGET_LANGUAGE, message=message ) return translation @@ -170,7 +179,7 @@ class Model: max_phrases=config.INPUT_MIC_MAX_PHRASES, ) def sendMicTranscript(): - mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.INPUT_MIC_VOICE_LANGUAGE) + mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) message = mic_transcriber.getTranscript() fnc(message) @@ -222,7 +231,7 @@ class Model: max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, ) def sendSpkTranscript(): - spk_transcriber.transcribeAudioQueue(spk_audio_queue, config.INPUT_SPEAKER_VOICE_LANGUAGE) + spk_transcriber.transcribeAudioQueue(spk_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) message = spk_transcriber.getTranscript() fnc(message) diff --git a/models/transcription/transcription_transcriber.py b/models/transcription/transcription_transcriber.py index e0b0eb6f..b058f4ec 100644 --- a/models/transcription/transcription_transcriber.py +++ b/models/transcription/transcription_transcriber.py @@ -27,7 +27,7 @@ class AudioTranscriber: "process_data_func": self.processSpeakerData if speaker else self.processSpeakerData } - def transcribeAudioQueue(self, audio_queue, language): + def transcribeAudioQueue(self, audio_queue, language, country): # while True: audio, time_spoken = audio_queue.get() self.updateLastSampleAndPhraseStatus(audio, time_spoken) @@ -37,7 +37,7 @@ class AudioTranscriber: # fd, path = tempfile.mkstemp(suffix=".wav") # os.close(fd) audio_data = self.audio_sources["process_data_func"]() - text = self.audio_recognizer.recognize_google(audio_data, language=transcription_lang[language]) + text = self.audio_recognizer.recognize_google(audio_data, language=transcription_lang[language][country]) except Exception as e: pass finally: diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index f58ecb8f..ed8fb9fb 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -120,22 +120,28 @@ def createSidebar(settings, main_window): def switchToPreset1(e): print("1") - main_window.YOUR_LANGUAGE = "Japanese\n(Japan)" - main_window.TARGET_LANGUAGE = "English\n(United States)" + if callable(main_window.CALLBACK_SELECTED_TAB_NO_1) is True: + main_window.CALLBACK_SELECTED_TAB_NO_1() + # main_window.YOUR_LANGUAGE = "Japanese\n(Japan)" + # main_window.TARGET_LANGUAGE = "English\n(United States)" target_active_widget = getattr(main_window, "sqls__presets_button_1") switchPresetTabFunction(target_active_widget) def switchToPreset2(e): print("2") - main_window.YOUR_LANGUAGE = "English\n(United States)" - main_window.TARGET_LANGUAGE = "Japanese\n(Japan)" + if callable(main_window.CALLBACK_SELECTED_TAB_NO_2) is True: + main_window.CALLBACK_SELECTED_TAB_NO_2() + # main_window.YOUR_LANGUAGE = "English\n(United States)" + # main_window.TARGET_LANGUAGE = "Japanese\n(Japan)" target_active_widget = getattr(main_window, "sqls__presets_button_2") switchPresetTabFunction(target_active_widget) def switchToPreset3(e): print("3") - main_window.YOUR_LANGUAGE = "Japanese\n(Japan)" - main_window.TARGET_LANGUAGE = "Chinese, Cantonese\n(Traditional Hong Kong)" + if callable(main_window.CALLBACK_SELECTED_TAB_NO_3) is True: + main_window.CALLBACK_SELECTED_TAB_NO_3() + # main_window.YOUR_LANGUAGE = "Japanese\n(Japan)" + # main_window.TARGET_LANGUAGE = "Chinese, Cantonese\n(Traditional Hong Kong)" target_active_widget = getattr(main_window, "sqls__presets_button_3") switchPresetTabFunction(target_active_widget) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 245527a1..c1b05610 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -47,6 +47,9 @@ class VRCT_GUI(CTk): self.YOUR_LANGUAGE = "Japanese\n(Japan)" self.TARGET_LANGUAGE = "English\n(United States)" + self.CALLBACK_SELECTED_TAB_NO_1 = None + self.CALLBACK_SELECTED_TAB_NO_2 = None + self.CALLBACK_SELECTED_TAB_NO_3 = None self.config_window = ConfigWindow(vrct_gui=self, settings=self.settings.config_window) # self.information_window = ToplevelWindowInformation(self) From f5049b3f659036e6a2c544fdc64ff55469b864db Mon Sep 17 00:00:00 2001 From: misygauziya Date: Tue, 29 Aug 2023 12:12:51 +0900 Subject: [PATCH 041/355] =?UTF-8?q?[Update]=20=E7=BF=BB=E8=A8=B3=E3=82=A8?= =?UTF-8?q?=E3=83=B3=E3=82=B8=E3=83=B3=E3=82=92=E8=87=AA=E5=8B=95=E3=81=A7?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 45 +++++++++++++++------------------------------ model.py | 31 +++++++++++++++++++++++++++++-- 2 files changed, 44 insertions(+), 32 deletions(-) diff --git a/main.py b/main.py index 3c98b8de..8ef83a7f 100644 --- a/main.py +++ b/main.py @@ -3,8 +3,6 @@ import customtkinter from vrct_gui import vrct_gui from config import config from model import model -from models.translation.translation_languages import translatorEngine, translation_lang -from models.transcription.transcription_languages import transcription_lang # func transcription send message def sendMicMessage(message): @@ -120,84 +118,71 @@ def foregroundOnForcefully(e): vrct_gui.attributes("-topmost", True) # func select languages -def getListLanguageAndCountry(): - langs = [] - for lang in model.SUPPORTED_LANGUAGES: - for country in transcription_lang[lang]: - langs.append(f"{lang}\n({country})") - return langs - -def getLanguageAndState(select): - parts = select.split("\n") - language = parts[0] - country = parts[1][1:-1] - return language, country - def setYourLanguageAndCountry(select): languages = config.SELECTED_TAB_YOUR_LANGUAGES languages[config.SELECTED_TAB_NO] = select config.SELECTED_TAB_YOUR_LANGUAGES = languages - - language, country = getLanguageAndState(select) + language, country = model.getLanguageAndCountry(select) config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) def setTargetLanguageAndCountry(select): languages = config.SELECTED_TAB_TARGET_LANGUAGES languages[config.SELECTED_TAB_NO] = select config.SELECTED_TAB_TARGET_LANGUAGES = languages - - language, country = getLanguageAndState(select) + language, country = model.getLanguageAndCountry(select) config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) def callbackSelectedTabNo1(): config.SELECTED_TAB_NO = "tab_1" vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] - languages = config.SELECTED_TAB_YOUR_LANGUAGES select = languages[config.SELECTED_TAB_NO] - language, country = getLanguageAndState(select) + language, country = model.getLanguageAndCountry(select) config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country languages = config.SELECTED_TAB_TARGET_LANGUAGES select = languages[config.SELECTED_TAB_NO] - language, country = getLanguageAndState(select) + language, country = model.getLanguageAndCountry(select) config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) def callbackSelectedTabNo2(): config.SELECTED_TAB_NO = "tab_2" vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] - languages = config.SELECTED_TAB_YOUR_LANGUAGES select = languages[config.SELECTED_TAB_NO] - language, country = getLanguageAndState(select) + language, country = model.getLanguageAndCountry(select) config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country languages = config.SELECTED_TAB_TARGET_LANGUAGES select = languages[config.SELECTED_TAB_NO] - language, country = getLanguageAndState(select) + language, country = model.getLanguageAndCountry(select) config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) def callbackSelectedTabNo3(): config.SELECTED_TAB_NO = "tab_3" vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] - languages = config.SELECTED_TAB_YOUR_LANGUAGES select = languages[config.SELECTED_TAB_NO] - language, country = getLanguageAndState(select) + language, country = model.getLanguageAndCountry(select) config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country languages = config.SELECTED_TAB_TARGET_LANGUAGES select = languages[config.SELECTED_TAB_NO] - language, country = getLanguageAndState(select) + language, country = model.getLanguageAndCountry(select) config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) # func print textbox def logTranslationStatusChange(): @@ -333,11 +318,11 @@ entry_message_box.bind("", foregroundOffForcefully) entry_message_box.bind("", foregroundOnForcefully) sqls__optionmenu_your_language = getattr(vrct_gui, "sqls__optionmenu_your_language") -sqls__optionmenu_your_language.configure(values=getListLanguageAndCountry()) +sqls__optionmenu_your_language.configure(values=model.getListLanguageAndCountry()) sqls__optionmenu_your_language.configure(command=setYourLanguageAndCountry) sqls__optionmenu_target_language = getattr(vrct_gui, "sqls__optionmenu_target_language") -sqls__optionmenu_target_language.configure(values=getListLanguageAndCountry()) +sqls__optionmenu_target_language.configure(values=model.getListLanguageAndCountry()) sqls__optionmenu_target_language.configure(command=setTargetLanguageAndCountry) vrct_gui.CALLBACK_SELECTED_TAB_NO_1 = callbackSelectedTabNo1 diff --git a/model.py b/model.py index eabcd022..19bb3820 100644 --- a/model.py +++ b/model.py @@ -11,6 +11,8 @@ from models.transcription.transcription_recorder import SelectedMicRecorder, Sel from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder from models.transcription.transcription_transcriber import AudioTranscriber from models.xsoverlay.notification import xsoverlayForVRCT +from models.translation.translation_languages import translatorEngine, translation_lang +from models.transcription.transcription_languages import transcription_lang from config import config class threadFnc(Thread): @@ -75,6 +77,31 @@ class Model: config.AUTH_KEYS = auth_keys return result + @staticmethod + def getListLanguageAndCountry(): + langs = [] + for lang in model.SUPPORTED_LANGUAGES: + for country in transcription_lang[lang]: + langs.append(f"{lang}\n({country})") + return langs + + @staticmethod + def getLanguageAndCountry(select): + parts = select.split("\n") + language = parts[0] + country = parts[1][1:-1] + return language, country + + @staticmethod + def findTranslationEngine(source_lang, target_lang): + compatible_engines = [] + for engine in translatorEngine: + source_languages = translation_lang.get(engine, {}).get("source", {}) + target_languages = translation_lang.get(engine, {}).get("target", {}) + if source_lang in source_languages and target_lang in target_languages: + compatible_engines.append(engine) + return compatible_engines[0] + def getTranslatorStatus(self): return self.translator.translator_status[config.CHOICE_TRANSLATOR] @@ -93,8 +120,8 @@ class Model: def getOutputTranslate(self, message): translation = self.translator.translate( translator_name=config.CHOICE_TRANSLATOR, - source_language=config.OUTPUT_SOURCE_LANG, - target_language=config.OUTPUT_TARGET_LANG, + source_language=config.TARGET_LANGUAGE, + target_language=config.SOURCE_LANGUAGE, message=message ) return translation From bcc2f69b83eac9dd0e8a47a1ccdbc779ce082438 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Tue, 29 Aug 2023 12:43:29 +0900 Subject: [PATCH 042/355] [Update] remove DeepL(auth) --- models/translation/translation_languages.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/models/translation/translation_languages.py b/models/translation/translation_languages.py index 1b68bf81..a18eb5a1 100644 --- a/models/translation/translation_languages.py +++ b/models/translation/translation_languages.py @@ -1,4 +1,5 @@ -translatorEngine = ["DeepL(web)", "DeepL(auth)", "Google(web)", "Bing(web)"] +# translatorEngine = ["DeepL(web)", "DeepL(auth)", "Google(web)", "Bing(web)"] +translatorEngine = ["DeepL(web)", "Google(web)", "Bing(web)"] translation_lang = {} dict_deepl_web_languages = { "Japanese":"JA", From 78ef7404aa7fb68879fcbc881537cc06739931e5 Mon Sep 17 00:00:00 2001 From: misygauziya Date: Tue, 29 Aug 2023 15:25:56 +0900 Subject: [PATCH 043/355] =?UTF-8?q?[Update]=20=E8=B5=B7=E5=8B=95=E6=99=82?= =?UTF-8?q?=E3=81=AB=E5=89=8D=E5=9B=9E=E3=81=AEtab=E3=82=92=E9=81=B8?= =?UTF-8?q?=E6=8A=9E=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 14 +++++++------- main.py | 16 +++++++++++++--- vrct_gui/main_window/widgets/create_sidebar.py | 12 ++++++------ 3 files changed, 26 insertions(+), 16 deletions(-) diff --git a/config.py b/config.py index f90f5785..9d8d2ae0 100644 --- a/config.py +++ b/config.py @@ -493,16 +493,16 @@ class Config: ] self._MAX_MIC_ENERGY_THRESHOLD = 2000 self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 - self._SELECTED_TAB_NO = "tab_1" + self._SELECTED_TAB_NO = "1" self._SELECTED_TAB_YOUR_LANGUAGES = { - "tab_1":"Japanese\n(Japan)", - "tab_2":"Japanese\n(Japan)", - "tab_3":"Japanese\n(Japan)", + "1":"Japanese\n(Japan)", + "2":"Japanese\n(Japan)", + "3":"Japanese\n(Japan)", } self._SELECTED_TAB_TARGET_LANGUAGES = { - "tab_1":"English\n(United States)", - "tab_2":"English\n(United States)", - "tab_3":"English\n(United States)", + "1":"English\n(United States)", + "2":"English\n(United States)", + "3":"English\n(United States)", } def load_config(self): diff --git a/main.py b/main.py index 8ef83a7f..a1ca52b8 100644 --- a/main.py +++ b/main.py @@ -1,8 +1,10 @@ from threading import Thread import customtkinter +from customtkinter import StringVar from vrct_gui import vrct_gui from config import config from model import model +from vrct_gui.ui_utils import setDefaultActiveTab # func transcription send message def sendMicMessage(message): @@ -137,7 +139,7 @@ def setTargetLanguageAndCountry(select): config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) def callbackSelectedTabNo1(): - config.SELECTED_TAB_NO = "tab_1" + config.SELECTED_TAB_NO = "1" vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] languages = config.SELECTED_TAB_YOUR_LANGUAGES @@ -153,7 +155,7 @@ def callbackSelectedTabNo1(): config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) def callbackSelectedTabNo2(): - config.SELECTED_TAB_NO = "tab_2" + config.SELECTED_TAB_NO = "2" vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] languages = config.SELECTED_TAB_YOUR_LANGUAGES @@ -169,7 +171,7 @@ def callbackSelectedTabNo2(): config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) def callbackSelectedTabNo3(): - config.SELECTED_TAB_NO = "tab_3" + config.SELECTED_TAB_NO = "3" vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] languages = config.SELECTED_TAB_YOUR_LANGUAGES @@ -320,15 +322,23 @@ entry_message_box.bind("", foregroundOnForcefully) sqls__optionmenu_your_language = getattr(vrct_gui, "sqls__optionmenu_your_language") sqls__optionmenu_your_language.configure(values=model.getListLanguageAndCountry()) sqls__optionmenu_your_language.configure(command=setYourLanguageAndCountry) +sqls__optionmenu_your_language.configure(variable=StringVar(value=config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO])) sqls__optionmenu_target_language = getattr(vrct_gui, "sqls__optionmenu_target_language") sqls__optionmenu_target_language.configure(values=model.getListLanguageAndCountry()) sqls__optionmenu_target_language.configure(command=setTargetLanguageAndCountry) +sqls__optionmenu_target_language.configure(variable=StringVar(value=config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO])) vrct_gui.CALLBACK_SELECTED_TAB_NO_1 = callbackSelectedTabNo1 vrct_gui.CALLBACK_SELECTED_TAB_NO_2 = callbackSelectedTabNo2 vrct_gui.CALLBACK_SELECTED_TAB_NO_3 = callbackSelectedTabNo3 +vrct_gui.current_active_preset_tab = getattr(vrct_gui, f"sqls__presets_button_{config.SELECTED_TAB_NO}") +setDefaultActiveTab( + active_tab_widget=vrct_gui.current_active_preset_tab, + active_bg_color=vrct_gui.settings.main.ctm.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR, + active_text_color=vrct_gui.settings.main.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR +) if __name__ == "__main__": vrct_gui.startMainLoop() \ No newline at end of file diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index ed8fb9fb..1ac12a85 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -513,12 +513,12 @@ def createSidebar(settings, main_window): column+=1 # Set default active preset tab - main_window.current_active_preset_tab = getattr(main_window, "sqls__presets_button_1") - setDefaultActiveTab( - active_tab_widget=main_window.current_active_preset_tab, - active_bg_color=settings.ctm.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR, - active_text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR - ) + # main_window.current_active_preset_tab = getattr(main_window, "sqls__presets_button_1") + # setDefaultActiveTab( + # active_tab_widget=main_window.current_active_preset_tab, + # active_bg_color=settings.ctm.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR, + # active_text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR + # ) # Quick Language settings BOX From 6b593b99d692a917e22796e2883aa222e84ea369 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 29 Aug 2023 15:32:23 +0900 Subject: [PATCH 044/355] Setting box: add items Speaker Device, Speaker Energy Threshold include Dynamic one, Speaker Record Timeout, Speaker Phrase Timeout, Speaker Max Phrases. Fixed typo and remove the code that is not using anymore in createSettingBox_Mic.py. --- .../createSideMenuAndSettingsBoxContainers.py | 3 +- .../setting_box_containers/__init__.py | 2 +- .../setting_box_transcription/__init__.py | 3 +- .../createSettingBox_Mic.py | 147 +-------------- .../createSettingBox_Speaker.py | 170 ++++++++++++++++++ 5 files changed, 180 insertions(+), 145 deletions(-) create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index e31ec0e8..60f4cc1e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -6,7 +6,7 @@ from .addConfigSideMenuItem import addConfigSideMenuItem from .createSettingBoxContainer import createSettingBoxContainer -from .setting_box_containers import createSettingBox_Appearance, createSettingBox_Mic +from .setting_box_containers import createSettingBox_Appearance, createSettingBox_Mic, createSettingBox_Speaker def createSideMenuAndSettingsBoxContainers(config_window, settings): @@ -77,6 +77,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): "setting_box_container_attr_name": "setting_box_container_transcription", "setting_boxes": [ { "section_title": "Mic", "setting_box": createSettingBox_Mic }, + { "section_title": "Speaker", "setting_box": createSettingBox_Speaker }, ] }, "activate_by_default": True, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py index 9b5a1f7e..a5fa59f6 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py @@ -1,2 +1,2 @@ from .setting_box_appearance import createSettingBox_Appearance -from .setting_box_transcription import createSettingBox_Mic \ No newline at end of file +from .setting_box_transcription import createSettingBox_Mic, createSettingBox_Speaker \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/__init__.py index 8ab3fc7e..5383094e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/__init__.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/__init__.py @@ -1 +1,2 @@ -from .createSettingBox_Mic import createSettingBox_Mic \ No newline at end of file +from .createSettingBox_Mic import createSettingBox_Mic +from .createSettingBox_Speaker import createSettingBox_Speaker \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index ffa96775..200acb8d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -20,24 +20,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): createSettingBoxEntry = sbg.createSettingBoxEntry - - # def dropdownMenuFun(selected_value): - # print(selected_value) - # config.INPUT_SOURCE_LANG = selected_value - - # def switchFun(_, switch_box_widget): - # print(switch_box_widget.get()) - - # def checkboxFun(_, checkbox_box_widget): - # print(checkbox_box_widget.get()) - - # def sliderFun(value): - # print(value) - - # def entryFun(value): - # config_window.INPUT_MIC_PHRASE_TIMEOUT = int(value) - # print(config_window.INPUT_MIC_PHRASE_TIMEOUT) - def checkbox_input_speaker_threshold_check_callback(e, passive_button_wrapper_widget, active_button_wrapper_widget, is_turned_on): print("is_turned_on", is_turned_on) @@ -81,11 +63,11 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): def entry_input_mic_phrase_timeout_callback(value): print(int(value)) - config.INPUT_MIC_RECORD_TIMEOUT = int(value) + config.INPUT_MIC_PHRASE_TIMEOUT = int(value) def entry_input_mic_max_phrases_callback(value): - print(str(value)) - config.INPUT_MIC_MAX_PHRASES = str(value) + print(int(value)) + config.INPUT_MIC_MAX_PHRASES = int(value) def entry_input_mic_word_filters_callback(value): word_filter = str(value) @@ -119,7 +101,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): config_window.sb__mic_device = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, label_text="Mic Device", - desc_text="Select the mic host. (Default: ?)", + desc_text="Select the mic devise. (Default: ?)", optionmenu_attr_name="sb__mic_device_optionmenu", dropdown_menu_attr_name="sb__mic_device_dropdown", # dropdown_menu_values=model.getListInputDevice(), @@ -232,123 +214,4 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): entry_textvariable=entry_textvariable, ) config_window.sb__mic_word_filter.grid(row=row) - row+=1 - - - - - # config_window.sb__switch_1 = createSettingBoxSwitch( - # parent_widget=setting_box_wrapper, - # label_text="Switch", - # desc_text="Turning this switch on will bring happiness.\nAs for turning it off... I leave that to your imagination", - # switch_attr_name="switch_attr_name_1", - # command=lambda: switchFun(config_window.switch_attr_name_1), - # is_checked=True, - # ) - # config_window.sb__switch_1.grid(row=row) - # row+=1 - - # config_window.sb__checkbox_1 = createSettingBoxCheckbox( - # parent_widget=setting_box_wrapper, - # label_text="Checkbox", - # desc_text="Checkbox ticked, a checkmark.", - # checkbox_attr_name="checkbox_attr_name_1", - # command=lambda: checkboxFun(config_window.checkbox_attr_name_1), - # is_checked=False, - # ) - # config_window.sb__checkbox_1.grid(row=row) - # row+=1 - - # config_window.sb__slider_1 = createSettingBoxSlider( - # parent_widget=setting_box_wrapper, - # label_text="Slider", - # desc_text="Adjust using the slider; the balance is up to you.", - # slider_attr_name="slider_attr_name_1", - # slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), - # slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, - # command=lambda value: sliderFun(value, config_window.slider_attr_name_1), - # variable=IntVar(value=config_window.INPUT_SPEAKER_ENERGY_THRESHOLD), - # ) - # config_window.sb__slider_1.grid(row=row) - # row+=1 - - - # config_window.sb__progressbar_x_slider_1 = createSettingBoxProgressbarXSlider( - # parent_widget=setting_box_wrapper, - # label_text="Progressbar and Slider for check the threshold", - # desc_text="just the slider to modify the threshold for activating voice input.\nPress the microphone button to start input, and you can adjust it while monitoring the actual volume.", - # command=set_input_threshold, # ? - # variable=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), - # entry_attr_name="progressbar_x_slider__entry_attr_name_1", - - - # slider_attr_name="progressbar_x_slider__slider_attr_name_1", - # slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), - # slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, - - # progressbar_attr_name="progressbar_x_slider__progressbar_attr_name_1", - - # passive_button_attr_name="progressbar_x_slider__passive_button_attr_name_1", - # passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - # e, - # config_window.progressbar_x_slider__passive_button_attr_name_1, - # config_window.progressbar_x_slider__active_button_attr_name_1, - # is_turned_on=True, - # ), - # active_button_attr_name="progressbar_x_slider__active_button_attr_name_1", - # active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - # e, - # config_window.progressbar_x_slider__passive_button_attr_name_1, - # config_window.progressbar_x_slider__active_button_attr_name_1, - # is_turned_on=False, - # ), - # button_image_filename="mic_icon_white.png" - # ) - # config_window.sb__progressbar_x_slider_1.grid(row=row) - # row+=1 - - # config_window.sb__progressbar_x_slider_2 = createSettingBoxProgressbarXSlider( - # parent_widget=setting_box_wrapper, - # label_text="Progressbar and Slider for check the threshold2", - # desc_text="just the slider to modify the threshold for activating voice input.\nPress the microphone button to start input, and you can adjust it while monitoring the actual volume.", - # command=set_input_threshold, # ? - # variable=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), - - # entry_attr_name="progressbar_x_slider__entry_attr_name_2", - - - # slider_attr_name="progressbar_x_slider__slider_attr_name_2", - # slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), - # slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, - # progressbar_attr_name="progressbar_x_slider__progressbar_attr_name_2", - - # passive_button_attr_name="progressbar_x_slider__passive_button_attr_name_2", - # passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - # e, - # config_window.progressbar_x_slider__passive_button_attr_name_2, - # config_window.progressbar_x_slider__active_button_attr_name_2, - # is_turned_on=True, - # ), - # active_button_attr_name="progressbar_x_slider__active_button_attr_name_2", - # active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - # e, - # config_window.progressbar_x_slider__passive_button_attr_name_2, - # config_window.progressbar_x_slider__active_button_attr_name_2, - # is_turned_on=False, - # ), - # button_image_filename="headphones_icon_white.png" - # ) - # config_window.sb__progressbar_x_slider_2.grid(row=row) - # row+=1 - - # config_window.sb__entry_1 = createSettingBoxEntry( - # parent_widget=setting_box_wrapper, - # label_text="Entry", - # desc_text="Please input a numerical value.", - # entry_attr_name="entry_attr_name_1", - # entry_width=settings.uism.SB__ENTRY_WIDTH_100, - # entry_bind__Any_KeyRelease=lambda value: entryFun(value, config_window.entry_attr_name_1), - # entry_textvariable=IntVar(value=config_window.INPUT_MIC_PHRASE_TIMEOUT), - # ) - # config_window.sb__entry_1.grid(row=row, pady=0) - # row+=1 \ No newline at end of file + row+=1 \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py new file mode 100644 index 00000000..b7d6574a --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -0,0 +1,170 @@ +from time import sleep + +from customtkinter import StringVar, IntVar + + +from ..SettingBoxGenerator import SettingBoxGenerator + +from config import config + +def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): + + + sbg = SettingBoxGenerator(config_window, settings) + + createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu + createSettingBoxSwitch = sbg.createSettingBoxSwitch + createSettingBoxCheckbox = sbg.createSettingBoxCheckbox + createSettingBoxSlider = sbg.createSettingBoxSlider + createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider + createSettingBoxEntry = sbg.createSettingBoxEntry + + + def checkbox_input_speaker_threshold_check_callback(e, passive_button_wrapper_widget, active_button_wrapper_widget, is_turned_on): + print("is_turned_on", is_turned_on) + + if is_turned_on is True: + passive_button_widget = passive_button_wrapper_widget.children["!ctklabel"] + passive_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + passive_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + passive_button_wrapper_widget.update_idletasks() + sleep(1) + + passive_button_wrapper_widget.grid_remove() + active_button_wrapper_widget.grid() + + elif is_turned_on is False: + # active_button_widget = active_button_wrapper_widget.children["!ctklabel"] + # active_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + # active_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + # active_button_wrapper_widget.update_idletasks() + # sleep(3) + + active_button_wrapper_widget.grid_remove() + passive_button_wrapper_widget.grid() + + def optionmenu_input_speaker_device_callback(value): + config.CHOICE_SPEAKER_DEVICE = value + + def slider_input_speaker_energy_threshold_callback(value): + config.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) + + def checkbox_input_speaker_dynamic_energy_threshold_callback(checkbox_box_widget): + print(checkbox_box_widget.get()) + config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = checkbox_box_widget.get() + + + def entry_input_speaker_record_timeout_callback(value): + print(int(value)) + config.INPUT_SPEAKER_RECORD_TIMEOUT = int(value) + + def entry_input_speaker_phrase_timeout_callback(value): + print(int(value)) + config.INPUT_SPEAKER_PHRASE_TIMEOUT = int(value) + + def entry_input_speaker_max_phrases_callback(value): + print(int(value)) + config.INPUT_SPEAKER_MAX_PHRASES = int(value) + + + + row=0 + config_window.sb__speaker_device = createSettingBoxDropdownMenu( + parent_widget=setting_box_wrapper, + label_text="Speaker Device", + desc_text="Select the speaker devise. (Default: ?)", + optionmenu_attr_name="sb__speaker_device_optionmenu", + dropdown_menu_attr_name="sb__speaker_device_dropdown", + # dropdown_menu_values=model.getListOutputDevice(), + dropdown_menu_values=["device1", "device2", "device3"], + command=lambda value: optionmenu_input_speaker_device_callback(value), + variable=StringVar(value=config.CHOICE_SPEAKER_DEVICE) + ) + config_window.sb__speaker_device.grid(row=row) + row+=1 + + + config_window.sb__speaker_energy_threshold = createSettingBoxProgressbarXSlider( + parent_widget=setting_box_wrapper, + label_text="Speaker Energy Threshold", + desc_text="Slider to modify the threshold for activating voice input.\nPress the headphones mark button to start input and speak something, so you can adjust it while monitoring the actual volume. 0 to 4000 (Default: 300)", + command=slider_input_speaker_energy_threshold_callback, + variable=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), + entry_attr_name="sb__progressbar_x_slider__entry_speaker_energy_threshold", + + + slider_attr_name="progressbar_x_slider__slider_speaker_energy_threshold", + slider_range=(0, config.MAX_SPEAKER_ENERGY_THRESHOLD), + slider_number_of_steps=config.MAX_SPEAKER_ENERGY_THRESHOLD, + + progressbar_attr_name="sb__progressbar_x_slider__progressbar_speaker_energy_threshold", + + passive_button_attr_name="sb__progressbar_x_slider__passive_button_speaker_energy_threshold", + passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + e, + config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold, + config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold, + is_turned_on=True, + ), + active_button_attr_name="sb__progressbar_x_slider__active_button_speaker_energy_threshold", + active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + e, + config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold, + config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold, + is_turned_on=False, + ), + button_image_filename="headphones_icon_white.png" + ) + config_window.sb__speaker_energy_threshold.grid(row=row) + row+=1 + + # Speaker Dynamic Energy Thresholdも上に引っ付ける予定 + config_window.sb__speaker_dynamic_energy_threshold = createSettingBoxCheckbox( + parent_widget=setting_box_wrapper, + label_text="Speaker Dynamic Energy Threshold", + desc_text="When this feature is selected, it will automatically adjust in a way that works well, based on the set Speaker Energy Threshold.", + checkbox_attr_name="sb__checkbox_speaker_dynamic_energy_threshold", + command=lambda: checkbox_input_speaker_dynamic_energy_threshold_callback(config_window.sb__checkbox_speaker_dynamic_energy_threshold), + is_checked=False + ) + config_window.sb__speaker_dynamic_energy_threshold.grid(row=row) + row+=1 + + + # 以下3つも一つの項目にまとめるかもしれない + config_window.sb__speaker_record_timeout = createSettingBoxEntry( + parent_widget=setting_box_wrapper, + label_text="Speaker Record Timeout", + desc_text="(Default: 3)", + entry_attr_name="sb__entry_speaker_record_timeout", + entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_record_timeout_callback(value), + entry_textvariable=IntVar(value=config.INPUT_SPEAKER_RECORD_TIMEOUT), + ) + config_window.sb__speaker_record_timeout.grid(row=row) + row+=1 + + config_window.sb__speaker_phrase_timeout = createSettingBoxEntry( + parent_widget=setting_box_wrapper, + label_text="Speaker Phrase Timeout", + desc_text="It will stop recording and receive the recordings when the set second(s) is reached. (Default: 3)", + entry_attr_name="sb__entry_speaker_phrase_timeout", + entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_phrase_timeout_callback(value), + entry_textvariable=IntVar(value=config.INPUT_SPEAKER_PHRASE_TIMEOUT), + ) + config_window.sb__speaker_phrase_timeout.grid(row=row) + row+=1 + + config_window.sb__speaker_max_phrases = createSettingBoxEntry( + parent_widget=setting_box_wrapper, + label_text="Speaker Max Phrases", + desc_text="It will stop recording and receive the recordings when the set count of phrase(s) is reached. (Default: 10)", + entry_attr_name="sb__entry_speaker_max_phrases", + entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_max_phrases_callback(value), + entry_textvariable=IntVar(value=config.INPUT_SPEAKER_MAX_PHRASES), + ) + config_window.sb__speaker_max_phrases.grid(row=row) + row+=1 + # __________ \ No newline at end of file From 4b9a90e6d3848f1fd3775e3a564f9c6a2ecdb211 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 29 Aug 2023 15:48:04 +0900 Subject: [PATCH 045/355] Chore: Change the name of variable optionmenu_attr_name and dropdown_menu_attr_name in Setting Boxes. --- .../createSettingBox_Appearance.py | 16 ++++++++-------- .../createSettingBox_Mic.py | 8 ++++---- .../createSettingBox_Speaker.py | 4 ++-- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index e209e8af..209d278b 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -59,8 +59,8 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): parent_widget=setting_box_wrapper, label_text="Theme", desc_text="Change the color theme from \"Light\" and \"Dark\". If you select \"System\", It will adjust based on your Windows theme. (Default: System)", - optionmenu_attr_name="sb__appearance_theme_optionmenu", - dropdown_menu_attr_name="sb__appearance_theme_dropdown", + optionmenu_attr_name="sb__optionmenu_appearance_theme", + dropdown_menu_attr_name="sb__dropdown_appearance_theme", dropdown_menu_values=["Light", "Dark", "System"], command=lambda value: optionmenu_appearance_theme_callback(value), variable=StringVar(value=config.APPEARANCE_THEME) @@ -73,8 +73,8 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): parent_widget=setting_box_wrapper, label_text="UI Size", desc_text="(Default: 100%)", - optionmenu_attr_name="sb__ui_scaling_optionmenu", - dropdown_menu_attr_name="sb__ui_scaling_dropdown", + optionmenu_attr_name="sb__optionmenu_ui_scaling", + dropdown_menu_attr_name="sb__dropdown_ui_scaling", dropdown_menu_values=["80%", "90%", "100%", "110%", "120%"], command=lambda value: optionmenu_ui_scaling_callback(value), variable=StringVar(value=config.UI_SCALING) @@ -88,8 +88,8 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): parent_widget=setting_box_wrapper, label_text="Font Family", desc_text="(Default: Yu Gothic UI)", - optionmenu_attr_name="sb__font_family_optionmenu", - dropdown_menu_attr_name="sb__font_family_dropdown", + optionmenu_attr_name="sb__optionmenu_font_family", + dropdown_menu_attr_name="sb__dropdown_font_family", dropdown_menu_values=["Font A", "Font B"], # dropdown_menu_values=font_families, command=lambda value: optionmenu_font_family_callback(value), @@ -104,8 +104,8 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): parent_widget=setting_box_wrapper, label_text="UI Language", desc_text="(Default: English)", - optionmenu_attr_name="sb__ui_language_optionmenu", - dropdown_menu_attr_name="sb__ui_language_dropdown", + optionmenu_attr_name="sb__optionmenu_ui_language", + dropdown_menu_attr_name="sb__dropdown_ui_language", dropdown_menu_values=selectable_languages_values, command=lambda value: optionmenu_ui_language_callback(value), variable=StringVar(value=selectable_languages[config.UI_LANGUAGE]), diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 200acb8d..a30c4b85 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -88,8 +88,8 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): parent_widget=setting_box_wrapper, label_text="Mic Host", desc_text="Select the mic host. (Default: ?)", - optionmenu_attr_name="sb__mic_host_optionmenu", - dropdown_menu_attr_name="sb__mic_host_dropdown", + optionmenu_attr_name="sb__optionmenu_mic_host", + dropdown_menu_attr_name="sb__dropdown_mic_host", # dropdown_menu_values=model.getListInputHost(), dropdown_menu_values=["host1", "host2", "host3"], command=lambda value: optionmenu_mic_host_callback(value), @@ -102,8 +102,8 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): parent_widget=setting_box_wrapper, label_text="Mic Device", desc_text="Select the mic devise. (Default: ?)", - optionmenu_attr_name="sb__mic_device_optionmenu", - dropdown_menu_attr_name="sb__mic_device_dropdown", + optionmenu_attr_name="sb__optionmenu_mic_device", + dropdown_menu_attr_name="sb__dropdown_mic_device", # dropdown_menu_values=model.getListInputDevice(), dropdown_menu_values=["device1", "device2", "device3"], command=lambda value: optionmenu_input_mic_device_callback(value), diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index b7d6574a..64ee5fb4 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -73,8 +73,8 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): parent_widget=setting_box_wrapper, label_text="Speaker Device", desc_text="Select the speaker devise. (Default: ?)", - optionmenu_attr_name="sb__speaker_device_optionmenu", - dropdown_menu_attr_name="sb__speaker_device_dropdown", + optionmenu_attr_name="sb__optionmenu_speaker_device", + dropdown_menu_attr_name="sb__dropdown_speaker_device", # dropdown_menu_values=model.getListOutputDevice(), dropdown_menu_values=["device1", "device2", "device3"], command=lambda value: optionmenu_input_speaker_device_callback(value), From 957c48caeb4be52798c4dece06680c9864a3c4fd Mon Sep 17 00:00:00 2001 From: misygauziya Date: Tue, 29 Aug 2023 16:15:54 +0900 Subject: [PATCH 046/355] =?UTF-8?q?[Update]=20main=20window=E3=81=AE?= =?UTF-8?q?=E3=81=BF=E5=87=A6=E7=90=86=E3=81=AE=E5=AE=9F=E8=A3=85=E5=AE=8C?= =?UTF-8?q?=E4=BA=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 34 +++++++++++-------- vrct_gui/_changeMainWindowWidgetsStatus.py | 8 +++-- .../main_window/widgets/create_sidebar.py | 16 ++++----- vrct_gui/vrct_gui.py | 4 +++ 4 files changed, 37 insertions(+), 25 deletions(-) diff --git a/main.py b/main.py index a1ca52b8..cfd6c49d 100644 --- a/main.py +++ b/main.py @@ -207,6 +207,16 @@ def logTranscriptionSendStatusChange(): vrct_gui.printToTextbox(textbox_all, "Voice2chatbox機能をOFFにしました", "", "INFO") vrct_gui.printToTextbox(textbox_system, "Voice2chatbox機能をOFFにしました", "", "INFO") +def logTranscriptionReceiveStatusChange(): + textbox_all = getattr(vrct_gui, "textbox_all") + textbox_system = getattr(vrct_gui, "textbox_system") + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + vrct_gui.printToTextbox(textbox_all, "Speaker2chatbox機能をONにしました", "", "INFO") + vrct_gui.printToTextbox(textbox_system, "Speaker2chatbox機能をONにしました", "", "INFO") + else: + vrct_gui.printToTextbox(textbox_all, "Speaker2chatbox機能をOFFにしました", "", "INFO") + vrct_gui.printToTextbox(textbox_system, "Speaker2chatbox機能をOFFにしました", "", "INFO") + def logSendMessage(message, translate): textbox_all = getattr(vrct_gui, "textbox_all") textbox_sent = getattr(vrct_gui, "textbox_sent") @@ -248,11 +258,11 @@ def logForegroundStatusChange(): vrct_gui.printToTextbox(textbox_system, "Stop foreground", "", "INFO") # command func -def toggleTranslationFeature(): +def callbackToggleTranslation(): config.ENABLE_TRANSLATION = getattr(vrct_gui, "translation_switch_box").get() logTranslationStatusChange() -def toggleTranscriptionSendFeature(): +def callbackToggleTranscriptionSend(): vrct_gui.changeMainWindowWidgetsStatus("disabled", "All") config.ENABLE_TRANSCRIPTION_SEND = getattr(vrct_gui, "transcription_send_switch_box").get() if config.ENABLE_TRANSCRIPTION_SEND is True: @@ -265,7 +275,7 @@ def toggleTranscriptionSendFeature(): th_stopTranscriptionSendMessage.start() logTranscriptionSendStatusChange() -def toggleTranscriptionReceiveFeature(): +def callbackToggleTranscriptionReceive(): vrct_gui.changeMainWindowWidgetsStatus("disabled", "All") config.ENABLE_TRANSCRIPTION_RECEIVE = getattr(vrct_gui, "transcription_receive_switch_box").get() if config.ENABLE_TRANSCRIPTION_RECEIVE is True: @@ -276,9 +286,9 @@ def toggleTranscriptionReceiveFeature(): th_stopTranscriptionReceiveMessage = Thread(target=stopTranscriptionReceiveMessage) th_stopTranscriptionReceiveMessage.daemon = True th_stopTranscriptionReceiveMessage.start() - logTranscriptionSendStatusChange() + logTranscriptionReceiveStatusChange() -def toggleForegroundFeature(): +def callbackToggleForeground(): config.ENABLE_FOREGROUND = getattr(vrct_gui, "foreground_switch_box").get() if config.ENABLE_FOREGROUND is True: vrct_gui.attributes("-topmost", True) @@ -303,15 +313,11 @@ model.checkOSCStarted() # check Software Updated model.checkSoftwareUpdated() -# set commands -translation_switch_box = getattr(vrct_gui, "translation_switch_box") -translation_switch_box.configure(command=toggleTranslationFeature) -transcription_send_switch_box = getattr(vrct_gui, "transcription_send_switch_box") -transcription_send_switch_box.configure(command=toggleTranscriptionSendFeature) -transcription_receive_switch_box = getattr(vrct_gui, "transcription_receive_switch_box") -transcription_receive_switch_box.configure(command=toggleTranscriptionReceiveFeature) -foreground_switch_box = getattr(vrct_gui, "foreground_switch_box") -foreground_switch_box.configure(command=toggleForegroundFeature) +# set UI and callback +vrct_gui.CALLBACK_TOGGLE_TRANSLATION = callbackToggleTranslation +vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = callbackToggleTranscriptionSend +vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = callbackToggleTranscriptionReceive +vrct_gui.CALLBACK_TOGGLE_FOREGROUND = callbackToggleForeground entry_message_box = getattr(vrct_gui, "entry_message_box") entry_message_box.bind("", messageBoxPressKeyEnter) diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index 602555c2..facca16a 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -85,7 +85,8 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, status, target_names): vrct_gui.sqls__container_title.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) vrct_gui.sqls__title_text_your_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) vrct_gui.sqls__title_text_target_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) - vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE) + if settings.IS_SIDEBAR_COMPACT_MODE is False: + vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE) vrct_gui.sqls__optionmenu_your_language.configure(state="disabled") vrct_gui.sqls__optionmenu_target_language.configure(state="disabled") @@ -93,8 +94,9 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, status, target_names): vrct_gui.sqls__container_title.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) vrct_gui.sqls__title_text_your_language.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) vrct_gui.sqls__title_text_target_language.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) - vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) - vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) + if settings.IS_SIDEBAR_COMPACT_MODE is False: + vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) + vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) vrct_gui.sqls__optionmenu_your_language.configure(state="normal") vrct_gui.sqls__optionmenu_target_language.configure(state="normal") diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 1ac12a85..0a24336b 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -18,30 +18,30 @@ def createSidebar(settings, main_window): def toggleTranslationFeature(): + if callable(main_window.CALLBACK_TOGGLE_TRANSLATION) is True: + main_window.CALLBACK_TOGGLE_TRANSLATION() is_turned_on = getattr(main_window, "translation_switch_box").get() print(is_turned_on) toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.translation_selected_mark) def toggleTranscriptionSendFeature(): + if callable(main_window.CALLBACK_TOGGLE_TRANSCRIPTION_SEND) is True: + main_window.CALLBACK_TOGGLE_TRANSCRIPTION_SEND() is_turned_on = getattr(main_window, "transcription_send_switch_box").get() print(is_turned_on) toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_send_selected_mark) - if is_turned_on is True: - changeMainWindowWidgetsStatus("disabled", "All") - sleep(1.5) - changeMainWindowWidgetsStatus("normal", "All") def toggleTranscriptionReceiveFeature(): + if callable(main_window.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE) is True: + main_window.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE() is_turned_on = getattr(main_window, "transcription_receive_switch_box").get() print(is_turned_on) toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_receive_selected_mark) - if is_turned_on is True: - changeMainWindowWidgetsStatus("disabled", "All") - sleep(1.5) - changeMainWindowWidgetsStatus("normal", "All") def toggleForegroundFeature(): + if callable(main_window.CALLBACK_TOGGLE_FOREGROUND) is True: + main_window.CALLBACK_TOGGLE_FOREGROUND() is_turned_on = getattr(main_window, "foreground_switch_box").get() print(is_turned_on) toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.foreground_selected_mark) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index c1b05610..29fb5bc2 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -47,6 +47,10 @@ class VRCT_GUI(CTk): self.YOUR_LANGUAGE = "Japanese\n(Japan)" self.TARGET_LANGUAGE = "English\n(United States)" + self.CALLBACK_TOGGLE_TRANSLATION = None + self.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = None + self.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = None + self.CALLBACK_TOGGLE_FOREGROUND = None self.CALLBACK_SELECTED_TAB_NO_1 = None self.CALLBACK_SELECTED_TAB_NO_2 = None self.CALLBACK_SELECTED_TAB_NO_3 = None From 246409fe2c5f0f1f383e8bfef13a1681398c7493 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 29 Aug 2023 16:54:51 +0900 Subject: [PATCH 047/355] Setting box: add Others tab. add items Auto Clear The Message Box, Notification XSOverlay and Message Format. --- .../createSideMenuAndSettingsBoxContainers.py | 8 +-- .../setting_box_containers/__init__.py | 3 +- .../setting_box_others/__init__.py | 1 + .../createSettingBox_Others.py | 72 +++++++++++++++++++ vrct_gui/ui_managers/UiScalingManager.py | 2 +- 5 files changed, 80 insertions(+), 6 deletions(-) create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/__init__.py create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index 60f4cc1e..fe965a39 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -6,7 +6,7 @@ from .addConfigSideMenuItem import addConfigSideMenuItem from .createSettingBoxContainer import createSettingBoxContainer -from .setting_box_containers import createSettingBox_Appearance, createSettingBox_Mic, createSettingBox_Speaker +from .setting_box_containers import createSettingBox_Appearance, createSettingBox_Mic, createSettingBox_Speaker, createSettingBox_Others def createSideMenuAndSettingsBoxContainers(config_window, settings): @@ -80,7 +80,6 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): { "section_title": "Speaker", "setting_box": createSettingBox_Speaker }, ] }, - "activate_by_default": True, }, { "side_menu_tab_attr_name": "side_menu_tab_parameters", @@ -97,14 +96,15 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): { "side_menu_tab_attr_name": "side_menu_tab_others", "label_attr_name": "label_others", - "selected_mark_attr_name": "selected_mark_foreground", + "selected_mark_attr_name": "selected_mark_others", "text": "Others", "setting_box_container_settings": { "setting_box_container_attr_name": "setting_box_container_others", "setting_boxes": [ - { "section_title": None, "setting_box": None }, + { "section_title": None, "setting_box": createSettingBox_Others }, ] }, + "activate_by_default": True, }, ] diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py index a5fa59f6..ba34d66c 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py @@ -1,2 +1,3 @@ from .setting_box_appearance import createSettingBox_Appearance -from .setting_box_transcription import createSettingBox_Mic, createSettingBox_Speaker \ No newline at end of file +from .setting_box_transcription import createSettingBox_Mic, createSettingBox_Speaker +from .setting_box_others import createSettingBox_Others \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/__init__.py new file mode 100644 index 00000000..c115d627 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/__init__.py @@ -0,0 +1 @@ +from .createSettingBox_Others import createSettingBox_Others \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py new file mode 100644 index 00000000..a813d3f4 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -0,0 +1,72 @@ +from time import sleep + +from customtkinter import StringVar, IntVar + + +from ..SettingBoxGenerator import SettingBoxGenerator + +from config import config + +def createSettingBox_Others(setting_box_wrapper, config_window, settings): + + + sbg = SettingBoxGenerator(config_window, settings) + + createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu + createSettingBoxSwitch = sbg.createSettingBoxSwitch + createSettingBoxCheckbox = sbg.createSettingBoxCheckbox + createSettingBoxSlider = sbg.createSettingBoxSlider + createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider + createSettingBoxEntry = sbg.createSettingBoxEntry + + + # 関数名 chatbox から messagebox に変える予定 config.ENABLE_AUTO_CLEAR_CHATBOX も MESSAGEBOXに変えるかな。 + def checkbox_auto_clear_chatbox_callback(checkbox_box_widget): + print(checkbox_box_widget.get()) + config.ENABLE_AUTO_CLEAR_CHATBOX = checkbox_box_widget.get() + + def checkbox_notice_xsoverlay_callback(checkbox_box_widget): + print(checkbox_box_widget.get()) + config.ENABLE_NOTICE_XSOVERLAY = checkbox_box_widget.get() + + def entry_message_format_callback(value): + if len(value) > 0: + config.MESSAGE_FORMAT = value + + + row=0 + config_window.sb__auto_clear_chatbox = createSettingBoxCheckbox( + parent_widget=setting_box_wrapper, + label_text="Auto Clear The Message Box", + desc_text="Clear the message box after sending your message.", + checkbox_attr_name="sb__checkbox_auto_clear_chatbox", + command=lambda: checkbox_auto_clear_chatbox_callback(config_window.sb__checkbox_auto_clear_chatbox), + is_checked=False + ) + config_window.sb__auto_clear_chatbox.grid(row=row) + row+=1 + + + config_window.sb__notice_xsoverlay = createSettingBoxCheckbox( + parent_widget=setting_box_wrapper, + label_text="Notification XSOverlay (VR Only)", + desc_text="Notify received messages by using XSOverlay's notification feature.", + checkbox_attr_name="sb__checkbox_notice_xsoverlay", + command=lambda: checkbox_notice_xsoverlay_callback(config_window.sb__checkbox_notice_xsoverlay), + is_checked=False + ) + config_window.sb__notice_xsoverlay.grid(row=row) + row+=1 + + + config_window.sb__message_format = createSettingBoxEntry( + parent_widget=setting_box_wrapper, + label_text="Message Format", + desc_text="You can change the decoration of the message you want to send. (Default: \"[message]([translation])\" )", + entry_attr_name="sb__entry_message_format", + entry_width=settings.uism.SB__ENTRY_WIDTH_250, + entry_bind__Any_KeyRelease=lambda value: entry_message_format_callback(value), + entry_textvariable=StringVar(value=config.MESSAGE_FORMAT), + ) + config_window.sb__message_format.grid(row=row) + row+=1 \ No newline at end of file diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 8d013108..2cbb2142 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -157,7 +157,7 @@ class UiScalingManager(): self.config_window.SB__ENTRY_HEIGHT = self._calculateUiSize(30) # SB__ENTRY_WIDTH_10 ... SB__ENTRY_WIDTH_200 - for i in range(10, 201, 10): + for i in range(10, 301, 10): setattr(self.config_window, f'SB__ENTRY_WIDTH_{i}', self._calculateUiSize(i)) From 47b80da50212ca9453b0ca64903e422f5ed674ba Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 29 Aug 2023 17:01:48 +0900 Subject: [PATCH 048/355] Chore: Remove the code that is no longer in use and __tmp.py from the setting_box_containers directory because it's no longer needed. --- .../setting_box_containers/__tmp.py | 225 ------------------ .../createSettingBox_Others.py | 4 - .../createSettingBox_Mic.py | 2 - .../createSettingBox_Speaker.py | 2 - 4 files changed, 233 deletions(-) delete mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__tmp.py diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__tmp.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__tmp.py deleted file mode 100644 index 5f0c3abc..00000000 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__tmp.py +++ /dev/null @@ -1,225 +0,0 @@ -from time import sleep - -from customtkinter import StringVar, IntVar - - -from .SettingBoxGenerator import SettingBoxGenerator - -from config import config - -def __tmp(setting_box_wrapper, config_window, settings): - - - sbg = SettingBoxGenerator(config_window, settings) - - createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu - createSettingBoxSwitch = sbg.createSettingBoxSwitch - createSettingBoxCheckbox = sbg.createSettingBoxCheckbox - createSettingBoxSlider = sbg.createSettingBoxSlider - createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider - createSettingBoxEntry = sbg.createSettingBoxEntry - - - - def checkbox_input_speaker_threshold_check_callback(e, passive_button_wrapper_widget, active_button_wrapper_widget, is_turned_on): - print("is_turned_on", is_turned_on) - - if is_turned_on is True: - passive_button_widget = passive_button_wrapper_widget.children["!ctklabel"] - passive_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - passive_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - passive_button_wrapper_widget.update_idletasks() - sleep(1) - - passive_button_wrapper_widget.grid_remove() - active_button_wrapper_widget.grid() - - elif is_turned_on is False: - # active_button_widget = active_button_wrapper_widget.children["!ctklabel"] - # active_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - # active_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - # active_button_wrapper_widget.update_idletasks() - # sleep(3) - - active_button_wrapper_widget.grid_remove() - passive_button_wrapper_widget.grid() - - # def slider_input_speaker_energy_threshold_callback(self, value): - # config_window.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) - - def set_input_threshold(value): - print(value) - config.INPUT_SPEAKER_ENERGY_THRESHOLD = value - print(config.INPUT_SPEAKER_ENERGY_THRESHOLD) - - - - - - - - - - def dropdownMenuFun(selected_value, optionmenu_widget, dropdown_menu_widget): - print(selected_value) - config.INPUT_SOURCE_LANG = selected_value - - def switchFun(_, switch_box_widget): - print(switch_box_widget.get()) - - def checkboxFun(_, checkbox_box_widget): - print(checkbox_box_widget.get()) - - def sliderFun(_, value, slider_widget): - print(value) - - def entryFun(e, entry_widget): - print(e.widget.get()) - config_window.INPUT_MIC_PHRASE_TIMEOUT = int(e.widget.get()) - print(config_window.INPUT_MIC_PHRASE_TIMEOUT) - - - - row=0 - config_window.sb__dropdown_menu_1 = createSettingBoxDropdownMenu( - parent_widget=setting_box_wrapper, - label_text="Option Menu", - desc_text="Select your preferences from the options menu.\nYou can choose your preferred language.", - optionmenu_attr_name="optionmenu_attr_name_1", - dropdown_menu_attr_name="dropdown_menu_attr_name_1", - dropdown_menu_values=["tt", "Japanese", "English"], - command=lambda value: dropdownMenuFun(value, config_window.optionmenu_attr_name_1, config_window.dropdown_menu_attr_name_1), - variable=StringVar(value=config.INPUT_SOURCE_LANG) - ) - config_window.sb__dropdown_menu_1.grid(row=row) - row+=1 - - - config_window.sb__dropdown_menu_2 = createSettingBoxDropdownMenu( - parent_widget=setting_box_wrapper, - label_text="Option Menu", - desc_text="Select your preferences from the options menu.\nYou can choose your preferred language.", - optionmenu_attr_name="optionmenu_attr_name_2", - dropdown_menu_attr_name="dropdown_menu_attr_name_1", - dropdown_menu_values=["tt", "Japanese", "English"], - command=lambda value: dropdownMenuFun(value, config_window.optionmenu_attr_name_2, config_window.dropdown_menu_attr_name_1), - variable=StringVar(value=config.INPUT_SOURCE_LANG) - ) - config_window.sb__dropdown_menu_2.grid(row=row) - row+=1 - - config_window.sb__switch_1 = createSettingBoxSwitch( - parent_widget=setting_box_wrapper, - label_text="Switch", - desc_text="Turning this switch on will bring happiness.\nAs for turning it off... I leave that to your imagination", - switch_attr_name="switch_attr_name_1", - command=lambda: switchFun(config_window.switch_attr_name_1), - is_checked=True, - ) - config_window.sb__switch_1.grid(row=row) - row+=1 - - config_window.sb__checkbox_1 = createSettingBoxCheckbox( - parent_widget=setting_box_wrapper, - label_text="Checkbox", - desc_text="Checkbox ticked, a checkmark.", - checkbox_attr_name="checkbox_attr_name_1", - command=lambda: checkboxFun(config_window.checkbox_attr_name_1), - is_checked=False, - ) - config_window.sb__checkbox_1.grid(row=row) - row+=1 - - config_window.sb__slider_1 = createSettingBoxSlider( - parent_widget=setting_box_wrapper, - label_text="Slider", - desc_text="Adjust using the slider; the balance is up to you.", - slider_attr_name="slider_attr_name_1", - slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), - slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, - command=lambda value: sliderFun(value, config_window.slider_attr_name_1), - variable=IntVar(value=config_window.INPUT_SPEAKER_ENERGY_THRESHOLD), - ) - config_window.sb__slider_1.grid(row=row) - row+=1 - - - config_window.sb__progressbar_x_slider_1 = createSettingBoxProgressbarXSlider( - parent_widget=setting_box_wrapper, - label_text="Progressbar and Slider for check the threshold", - desc_text="just the slider to modify the threshold for activating voice input.\nPress the microphone button to start input, and you can adjust it while monitoring the actual volume.", - command=set_input_threshold, # ? - variable=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), - entry_attr_name="progressbar_x_slider__entry_attr_name_1", - - - slider_attr_name="progressbar_x_slider__slider_attr_name_1", - slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), - slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, - - progressbar_attr_name="progressbar_x_slider__progressbar_attr_name_1", - - passive_button_attr_name="progressbar_x_slider__passive_button_attr_name_1", - passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - e, - config_window.progressbar_x_slider__passive_button_attr_name_1, - config_window.progressbar_x_slider__active_button_attr_name_1, - is_turned_on=True, - ), - active_button_attr_name="progressbar_x_slider__active_button_attr_name_1", - active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - e, - config_window.progressbar_x_slider__passive_button_attr_name_1, - config_window.progressbar_x_slider__active_button_attr_name_1, - is_turned_on=False, - ), - button_image_filename="mic_icon_white.png" - ) - config_window.sb__progressbar_x_slider_1.grid(row=row) - row+=1 - - config_window.sb__progressbar_x_slider_2 = createSettingBoxProgressbarXSlider( - parent_widget=setting_box_wrapper, - label_text="Progressbar and Slider for check the threshold2", - desc_text="just the slider to modify the threshold for activating voice input.\nPress the microphone button to start input, and you can adjust it while monitoring the actual volume.", - command=set_input_threshold, # ? - variable=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), - - entry_attr_name="progressbar_x_slider__entry_attr_name_2", - - - slider_attr_name="progressbar_x_slider__slider_attr_name_2", - slider_range=(0, config_window.MAX_SPEAKER_ENERGY_THRESHOLD), - slider_number_of_steps=config_window.MAX_SPEAKER_ENERGY_THRESHOLD, - progressbar_attr_name="progressbar_x_slider__progressbar_attr_name_2", - - passive_button_attr_name="progressbar_x_slider__passive_button_attr_name_2", - passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - e, - config_window.progressbar_x_slider__passive_button_attr_name_2, - config_window.progressbar_x_slider__active_button_attr_name_2, - is_turned_on=True, - ), - active_button_attr_name="progressbar_x_slider__active_button_attr_name_2", - active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - e, - config_window.progressbar_x_slider__passive_button_attr_name_2, - config_window.progressbar_x_slider__active_button_attr_name_2, - is_turned_on=False, - ), - button_image_filename="headphones_icon_white.png" - ) - config_window.sb__progressbar_x_slider_2.grid(row=row) - row+=1 - - config_window.sb__entry_1 = createSettingBoxEntry( - parent_widget=setting_box_wrapper, - label_text="Entry", - desc_text="Please input a numerical value.", - entry_attr_name="entry_attr_name_1", - entry_width=settings.uism.SB__ENTRY_WIDTH_100, - entry_bind__Any_KeyRelease=lambda value: entryFun(value, config_window.entry_attr_name_1), - entry_textvariable=IntVar(value=config_window.INPUT_MIC_PHRASE_TIMEOUT), - ) - config_window.sb__entry_1.grid(row=row, pady=0) - row+=1 \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index a813d3f4..efa6ea9b 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -12,11 +12,7 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings): sbg = SettingBoxGenerator(config_window, settings) - createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu - createSettingBoxSwitch = sbg.createSettingBoxSwitch createSettingBoxCheckbox = sbg.createSettingBoxCheckbox - createSettingBoxSlider = sbg.createSettingBoxSlider - createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider createSettingBoxEntry = sbg.createSettingBoxEntry diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index a30c4b85..8e523568 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -13,9 +13,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): sbg = SettingBoxGenerator(config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu - createSettingBoxSwitch = sbg.createSettingBoxSwitch createSettingBoxCheckbox = sbg.createSettingBoxCheckbox - createSettingBoxSlider = sbg.createSettingBoxSlider createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider createSettingBoxEntry = sbg.createSettingBoxEntry diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 64ee5fb4..64f7dc9f 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -13,9 +13,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): sbg = SettingBoxGenerator(config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu - createSettingBoxSwitch = sbg.createSettingBoxSwitch createSettingBoxCheckbox = sbg.createSettingBoxCheckbox - createSettingBoxSlider = sbg.createSettingBoxSlider createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider createSettingBoxEntry = sbg.createSettingBoxEntry From fcb66fa0c8d38390c33ab735419d57a8eaede801 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 29 Aug 2023 19:53:09 +0900 Subject: [PATCH 049/355] Setting box: add Advanced Settings tab (formerly Parameters). add items OSC IP Address and OSC Port. --- .../createSideMenuAndSettingsBoxContainers.py | 26 +++++----- .../setting_box_containers/__init__.py | 3 +- .../setting_box_advanced_settings/__init__.py | 1 + .../createSettingBox_AdvancedSettings.py | 48 +++++++++++++++++++ 4 files changed, 64 insertions(+), 14 deletions(-) create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/__init__.py create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index fe965a39..dec611be 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -6,7 +6,7 @@ from .addConfigSideMenuItem import addConfigSideMenuItem from .createSettingBoxContainer import createSettingBoxContainer -from .setting_box_containers import createSettingBox_Appearance, createSettingBox_Mic, createSettingBox_Speaker, createSettingBox_Others +from .setting_box_containers import createSettingBox_Appearance, createSettingBox_Mic, createSettingBox_Speaker, createSettingBox_Others, createSettingBox_AdvancedSettings def createSideMenuAndSettingsBoxContainers(config_window, settings): @@ -81,18 +81,6 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): ] }, }, - { - "side_menu_tab_attr_name": "side_menu_tab_parameters", - "label_attr_name": "label_parameters", - "selected_mark_attr_name": "selected_mark_foreground", - "text": "Parameters", - "setting_box_container_settings": { - "setting_box_container_attr_name": "setting_box_container_parameters", - "setting_boxes": [ - { "section_title": None, "setting_box": None }, - ] - }, - }, { "side_menu_tab_attr_name": "side_menu_tab_others", "label_attr_name": "label_others", @@ -104,6 +92,18 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): { "section_title": None, "setting_box": createSettingBox_Others }, ] }, + }, + { + "side_menu_tab_attr_name": "side_menu_tab_advanced", + "label_attr_name": "label_advanced", + "selected_mark_attr_name": "selected_mark_advanced", + "text": "Advanced Settings", + "setting_box_container_settings": { + "setting_box_container_attr_name": "setting_box_container_advanced", + "setting_boxes": [ + { "section_title": None, "setting_box": createSettingBox_AdvancedSettings }, + ] + }, "activate_by_default": True, }, ] diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py index ba34d66c..9f316e34 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py @@ -1,3 +1,4 @@ from .setting_box_appearance import createSettingBox_Appearance from .setting_box_transcription import createSettingBox_Mic, createSettingBox_Speaker -from .setting_box_others import createSettingBox_Others \ No newline at end of file +from .setting_box_others import createSettingBox_Others +from .setting_box_advanced_settings import createSettingBox_AdvancedSettings \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/__init__.py new file mode 100644 index 00000000..0ceb6231 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/__init__.py @@ -0,0 +1 @@ +from .createSettingBox_AdvancedSettings import createSettingBox_AdvancedSettings \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py new file mode 100644 index 00000000..e82087a6 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py @@ -0,0 +1,48 @@ +from time import sleep + +from customtkinter import StringVar, IntVar + + +from ..SettingBoxGenerator import SettingBoxGenerator + +from config import config + +def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settings): + + + sbg = SettingBoxGenerator(config_window, settings) + + createSettingBoxEntry = sbg.createSettingBoxEntry + + + def entry_ip_address_callback(value): + config.OSC_IP_ADDRESS = str(value) + + def entry_port_callback(value): + config.OSC_PORT = int(value) + + row=0 + config_window.sb__ip_address = createSettingBoxEntry( + parent_widget=setting_box_wrapper, + label_text="OSC IP Address", + desc_text="(Default: 127.0.0.1)", + entry_attr_name="sb__entry_ip_address", + entry_width=settings.uism.SB__ENTRY_WIDTH_150, + entry_bind__Any_KeyRelease=lambda value: entry_ip_address_callback(value), + entry_textvariable=StringVar(value=config.OSC_IP_ADDRESS), + ) + config_window.sb__ip_address.grid(row=row) + row+=1 + + + config_window.sb__port = createSettingBoxEntry( + parent_widget=setting_box_wrapper, + label_text="OSC Port", + desc_text="(Default: 9000)", + entry_attr_name="sb__entry_port", + entry_width=settings.uism.SB__ENTRY_WIDTH_150, + entry_bind__Any_KeyRelease=lambda value: entry_port_callback(value), + entry_textvariable=IntVar(value=config.OSC_PORT), + ) + config_window.sb__port.grid(row=row) + row+=1 \ No newline at end of file From ff306ed53d1c16ac62f029d91d05b7c81e3a6330 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 30 Aug 2023 02:14:50 +0900 Subject: [PATCH 050/355] Setting box: add Translation tab. add item DeepL Auth Key. --- .../createSideMenuAndSettingsBoxContainers.py | 4 +- .../setting_box_containers/__init__.py | 3 +- .../setting_box_translation/__init__.py | 1 + .../createSettingBox_Translation.py | 40 +++++++++++++++++++ 4 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/__init__.py create mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index dec611be..c7a0bdd3 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -6,7 +6,7 @@ from .addConfigSideMenuItem import addConfigSideMenuItem from .createSettingBoxContainer import createSettingBoxContainer -from .setting_box_containers import createSettingBox_Appearance, createSettingBox_Mic, createSettingBox_Speaker, createSettingBox_Others, createSettingBox_AdvancedSettings +from .setting_box_containers import createSettingBox_Appearance, createSettingBox_Translation, createSettingBox_Mic, createSettingBox_Speaker, createSettingBox_Others, createSettingBox_AdvancedSettings def createSideMenuAndSettingsBoxContainers(config_window, settings): @@ -64,7 +64,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): "setting_box_container_settings": { "setting_box_container_attr_name": "setting_box_container_translation", "setting_boxes": [ - { "section_title": None, "setting_box": None }, + { "section_title": None, "setting_box": createSettingBox_Translation }, ] }, }, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py index 9f316e34..920beff6 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py @@ -1,4 +1,5 @@ from .setting_box_appearance import createSettingBox_Appearance from .setting_box_transcription import createSettingBox_Mic, createSettingBox_Speaker from .setting_box_others import createSettingBox_Others -from .setting_box_advanced_settings import createSettingBox_AdvancedSettings \ No newline at end of file +from .setting_box_advanced_settings import createSettingBox_AdvancedSettings +from .setting_box_translation import createSettingBox_Translation \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/__init__.py new file mode 100644 index 00000000..d965431e --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/__init__.py @@ -0,0 +1 @@ +from .createSettingBox_Translation import createSettingBox_Translation \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py new file mode 100644 index 00000000..29ece0b4 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -0,0 +1,40 @@ +from time import sleep + +from customtkinter import StringVar, IntVar + + +from ..SettingBoxGenerator import SettingBoxGenerator + +from config import config + +def createSettingBox_Translation(setting_box_wrapper, config_window, settings): + + + sbg = SettingBoxGenerator(config_window, settings) + + createSettingBoxEntry = sbg.createSettingBoxEntry + + + def deepl_authkey_callback(value): + print(str(value)) + # config.AUTH_KEYS["DeepL(auth)"] = str(value) + # if len(value) > 0: + # if model.authenticationTranslator(choice_translator="DeepL(auth)", auth_key=value) is True: + # print_textbox(self.parent.textbox_message_log, "Auth key update completed", "INFO") + # print_textbox(self.parent.textbox_message_system_log, "Auth key update completed", "INFO") + # else: + # pass + + + row=0 + config_window.sb__deepl_authkey = createSettingBoxEntry( + parent_widget=setting_box_wrapper, + label_text="DeepL Auth Key", + desc_text="", + entry_attr_name="sb__deepl_authkey", + entry_width=settings.uism.SB__ENTRY_WIDTH_300, + entry_bind__Any_KeyRelease=lambda value: deepl_authkey_callback(value), + entry_textvariable=StringVar(value=config.AUTH_KEYS["DeepL(auth)"]), + ) + config_window.sb__deepl_authkey.grid(row=row) + row+=1 \ No newline at end of file From 572889990ad5942268090a6647956feb592779ed Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 30 Aug 2023 02:35:35 +0900 Subject: [PATCH 051/355] =?UTF-8?q?[Refactor]=20config=5Fwindow.=20setting?= =?UTF-8?q?=5Fbox=E5=9B=9E=E3=82=8A=E3=81=AE=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=82=84=E9=96=A2=E6=95=B0=E5=90=8D=E3=81=AE=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=80=82=E4=BD=95=E3=82=92=E3=81=A9=E3=81=86=E8=AA=AD?= =?UTF-8?q?=E3=81=BF=E8=BE=BC=E3=82=93=E3=81=A7=E3=81=84=E3=82=8B=E3=81=AE?= =?UTF-8?q?=E3=81=8B=E5=B0=91=E3=81=97=E3=81=A0=E3=81=91=E5=88=86=E3=81=8B?= =?UTF-8?q?=E3=82=8A=E3=82=84=E3=81=99=E3=81=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...eMenuItem.py => _addConfigSideMenuItem.py} | 2 +- ...ainer.py => _createSettingBoxContainer.py} | 2 +- .../createSideMenuAndSettingsBoxContainers.py | 20 ++++++++++--------- ...oxGenerator.py => _SettingBoxGenerator.py} | 2 +- .../setting_box_containers/__init__.py | 5 ----- .../createSettingBox_AdvancedSettings.py | 7 ++----- .../createSettingBox_Appearance.py | 7 ++----- .../createSettingBox_Others.py | 7 ++----- .../createSettingBox_Mic.py | 7 ++----- .../createSettingBox_Speaker.py | 7 ++----- .../createSettingBox_Translation.py | 7 ++----- 11 files changed, 26 insertions(+), 47 deletions(-) rename vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/{addConfigSideMenuItem.py => _addConfigSideMenuItem.py} (97%) rename vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/{createSettingBoxContainer.py => _createSettingBoxContainer.py} (97%) rename vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/{SettingBoxGenerator.py => _SettingBoxGenerator.py} (99%) delete mode 100644 vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/addConfigSideMenuItem.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py similarity index 97% rename from vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/addConfigSideMenuItem.py rename to vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py index 1f518a07..cb2f79b2 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/addConfigSideMenuItem.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py @@ -4,7 +4,7 @@ from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButto -def addConfigSideMenuItem(config_window, settings, side_menu_settings, side_menu_row, all_side_menu_tab_attr_name): +def _addConfigSideMenuItem(config_window, settings, side_menu_settings, side_menu_row, all_side_menu_tab_attr_name): def switchActiveAndPassiveSettingBoxContainerTabsColor(target_active_widget): diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSettingBoxContainer.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py similarity index 97% rename from vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSettingBoxContainer.py rename to vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py index 7935e7d6..a8219006 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSettingBoxContainer.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py @@ -1,7 +1,7 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel -def createSettingBoxContainer(config_window, settings, setting_box_container_settings): +def _createSettingBoxContainer(config_window, settings, setting_box_container_settings): def createSectionTitle(container_widget, section_title): diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index c7a0bdd3..08ef24a7 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -2,11 +2,15 @@ from customtkinter import CTkFrame, CTkScrollableFrame from ....ui_utils import setDefaultActiveTab -from .addConfigSideMenuItem import addConfigSideMenuItem -from .createSettingBoxContainer import createSettingBoxContainer +from ._addConfigSideMenuItem import _addConfigSideMenuItem +from ._createSettingBoxContainer import _createSettingBoxContainer -from .setting_box_containers import createSettingBox_Appearance, createSettingBox_Translation, createSettingBox_Mic, createSettingBox_Speaker, createSettingBox_Others, createSettingBox_AdvancedSettings +from .setting_box_containers.setting_box_appearance import createSettingBox_Appearance +from .setting_box_containers.setting_box_transcription import createSettingBox_Mic, createSettingBox_Speaker +from .setting_box_containers.setting_box_others import createSettingBox_Others +from .setting_box_containers.setting_box_advanced_settings import createSettingBox_AdvancedSettings +from .setting_box_containers.setting_box_translation import createSettingBox_Translation def createSideMenuAndSettingsBoxContainers(config_window, settings): @@ -55,6 +59,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): { "section_title": None, "setting_box": createSettingBox_Appearance }, ] }, + "activate_by_default": True, }, { "side_menu_tab_attr_name": "side_menu_tab_translation", @@ -104,7 +109,6 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): { "section_title": None, "setting_box": createSettingBox_AdvancedSettings }, ] }, - "activate_by_default": True, }, ] @@ -112,7 +116,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): side_menu_row=0 for sm_and_sbc_setting in side_menu_and_setting_box_containers_settings: - addConfigSideMenuItem( + _addConfigSideMenuItem( config_window=config_window, settings=settings, side_menu_settings=sm_and_sbc_setting, @@ -122,7 +126,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): side_menu_row+=1 - createSettingBoxContainer( + _createSettingBoxContainer( config_window=config_window, settings=settings, setting_box_container_settings=sm_and_sbc_setting["setting_box_container_settings"], @@ -143,6 +147,4 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): # Set default active setting box container config_window.current_active_setting_box_container = getattr(config_window, sm_and_sbc_setting["setting_box_container_settings"]["setting_box_container_attr_name"]) - config_window.current_active_setting_box_container.grid() - - + config_window.current_active_setting_box_container.grid() \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py similarity index 99% rename from vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py rename to vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 10f4cfe5..ebeca5ac 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -6,7 +6,7 @@ from vrct_gui.ui_utils import createButtonWithImage from typing import Union -class SettingBoxGenerator(): +class _SettingBoxGenerator(): def __init__(self, config_window, settings): self.IS_CONFIG_WINDOW_COMPACT_MODE = settings.IS_CONFIG_WINDOW_COMPACT_MODE diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py deleted file mode 100644 index 920beff6..00000000 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -from .setting_box_appearance import createSettingBox_Appearance -from .setting_box_transcription import createSettingBox_Mic, createSettingBox_Speaker -from .setting_box_others import createSettingBox_Others -from .setting_box_advanced_settings import createSettingBox_AdvancedSettings -from .setting_box_translation import createSettingBox_Translation \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py index e82087a6..b28b75a4 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py @@ -3,15 +3,12 @@ from time import sleep from customtkinter import StringVar, IntVar -from ..SettingBoxGenerator import SettingBoxGenerator +from .._SettingBoxGenerator import _SettingBoxGenerator from config import config def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settings): - - - sbg = SettingBoxGenerator(config_window, settings) - + sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxEntry = sbg.createSettingBoxEntry diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index 209d278b..77bdb10c 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -6,15 +6,12 @@ from languages import selectable_languages from utils import get_key_by_value -from ..SettingBoxGenerator import SettingBoxGenerator +from .._SettingBoxGenerator import _SettingBoxGenerator from config import config def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): - - - sbg = SettingBoxGenerator(config_window, settings) - + sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxSlider = sbg.createSettingBoxSlider diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index efa6ea9b..f65858c6 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -3,15 +3,12 @@ from time import sleep from customtkinter import StringVar, IntVar -from ..SettingBoxGenerator import SettingBoxGenerator +from .._SettingBoxGenerator import _SettingBoxGenerator from config import config def createSettingBox_Others(setting_box_wrapper, config_window, settings): - - - sbg = SettingBoxGenerator(config_window, settings) - + sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxCheckbox = sbg.createSettingBoxCheckbox createSettingBoxEntry = sbg.createSettingBoxEntry diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 8e523568..8cc5f1d1 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -3,15 +3,12 @@ from time import sleep from customtkinter import StringVar, IntVar -from ..SettingBoxGenerator import SettingBoxGenerator +from .._SettingBoxGenerator import _SettingBoxGenerator from config import config def createSettingBox_Mic(setting_box_wrapper, config_window, settings): - - - sbg = SettingBoxGenerator(config_window, settings) - + sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxCheckbox = sbg.createSettingBoxCheckbox createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 64f7dc9f..9069abeb 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -3,15 +3,12 @@ from time import sleep from customtkinter import StringVar, IntVar -from ..SettingBoxGenerator import SettingBoxGenerator +from .._SettingBoxGenerator import _SettingBoxGenerator from config import config def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): - - - sbg = SettingBoxGenerator(config_window, settings) - + sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxCheckbox = sbg.createSettingBoxCheckbox createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index 29ece0b4..1bc4ba0d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -3,15 +3,12 @@ from time import sleep from customtkinter import StringVar, IntVar -from ..SettingBoxGenerator import SettingBoxGenerator +from .._SettingBoxGenerator import _SettingBoxGenerator from config import config def createSettingBox_Translation(setting_box_wrapper, config_window, settings): - - - sbg = SettingBoxGenerator(config_window, settings) - + sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxEntry = sbg.createSettingBoxEntry From 13d4a84e7f730b3a80a22ddab377ee30bea07e82 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 30 Aug 2023 15:13:33 +0900 Subject: [PATCH 052/355] =?UTF-8?q?add=20view.py:=20main.py=E3=81=8B?= =?UTF-8?q?=E3=82=89UI=E3=81=AE=E5=88=9D=E6=9C=9F=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92view.py=E7=A7=BB=E5=8B=95=E3=80=82?= =?UTF-8?q?=E3=81=9D=E3=81=AE=E4=BB=96=E4=B8=80=E9=83=A8=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E5=90=8D=E3=81=AE=E5=A4=89=E6=9B=B4=E3=81=AA=E3=81=A9=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 58 +++++++------------ view.py | 43 ++++++++++++++ vrct_gui/config_window/ConfigWindow.py | 2 - .../createSideMenuAndSettingsBoxContainers.py | 4 +- .../main_window/widgets/create_sidebar.py | 10 +--- .../main_window/widgets/create_textbox.py | 4 +- vrct_gui/ui_utils/__init__.py | 3 +- vrct_gui/ui_utils/ui_utils.py | 2 +- vrct_gui/vrct_gui.py | 10 ++++ 9 files changed, 82 insertions(+), 54 deletions(-) create mode 100644 view.py diff --git a/main.py b/main.py index cfd6c49d..1d84ce3e 100644 --- a/main.py +++ b/main.py @@ -1,10 +1,10 @@ from threading import Thread import customtkinter -from customtkinter import StringVar from vrct_gui import vrct_gui from config import config from model import model -from vrct_gui.ui_utils import setDefaultActiveTab + +from view import viewInitializer # func transcription send message def sendMicMessage(message): @@ -111,14 +111,6 @@ def messageBoxPressKeyAny(e): entry_message_box.insert("end", e.char) return "break" -def foregroundOffForcefully(e): - if config.ENABLE_FOREGROUND: - vrct_gui.attributes("-topmost", False) - -def foregroundOnForcefully(e): - if config.ENABLE_FOREGROUND: - vrct_gui.attributes("-topmost", True) - # func select languages def setYourLanguageAndCountry(select): languages = config.SELECTED_TAB_YOUR_LANGUAGES @@ -314,36 +306,28 @@ model.checkOSCStarted() model.checkSoftwareUpdated() # set UI and callback -vrct_gui.CALLBACK_TOGGLE_TRANSLATION = callbackToggleTranslation -vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = callbackToggleTranscriptionSend -vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = callbackToggleTranscriptionReceive -vrct_gui.CALLBACK_TOGGLE_FOREGROUND = callbackToggleForeground +viewInitializer( + sidebar_features={ + "callback_toggle_translation": callbackToggleTranslation, + "callback_toggle_transcription_send": callbackToggleTranscriptionSend, + "callback_toggle_transcription_receive": callbackToggleTranscriptionReceive, + "callback_toggle_foreground": callbackToggleForeground, + }, -entry_message_box = getattr(vrct_gui, "entry_message_box") -entry_message_box.bind("", messageBoxPressKeyEnter) -entry_message_box.bind("", messageBoxPressKeyAny) -entry_message_box.bind("", foregroundOffForcefully) -entry_message_box.bind("", foregroundOnForcefully) + language_presets={ + "callback_your_language": setYourLanguageAndCountry, + "callback_target_language": setTargetLanguageAndCountry, + "values": model.getListLanguageAndCountry(), -sqls__optionmenu_your_language = getattr(vrct_gui, "sqls__optionmenu_your_language") -sqls__optionmenu_your_language.configure(values=model.getListLanguageAndCountry()) -sqls__optionmenu_your_language.configure(command=setYourLanguageAndCountry) -sqls__optionmenu_your_language.configure(variable=StringVar(value=config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO])) + "callback_selected_tab_no_1": callbackSelectedTabNo1, + "callback_selected_tab_no_2": callbackSelectedTabNo2, + "callback_selected_tab_no_3": callbackSelectedTabNo3, + }, -sqls__optionmenu_target_language = getattr(vrct_gui, "sqls__optionmenu_target_language") -sqls__optionmenu_target_language.configure(values=model.getListLanguageAndCountry()) -sqls__optionmenu_target_language.configure(command=setTargetLanguageAndCountry) -sqls__optionmenu_target_language.configure(variable=StringVar(value=config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO])) - -vrct_gui.CALLBACK_SELECTED_TAB_NO_1 = callbackSelectedTabNo1 -vrct_gui.CALLBACK_SELECTED_TAB_NO_2 = callbackSelectedTabNo2 -vrct_gui.CALLBACK_SELECTED_TAB_NO_3 = callbackSelectedTabNo3 - -vrct_gui.current_active_preset_tab = getattr(vrct_gui, f"sqls__presets_button_{config.SELECTED_TAB_NO}") -setDefaultActiveTab( - active_tab_widget=vrct_gui.current_active_preset_tab, - active_bg_color=vrct_gui.settings.main.ctm.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR, - active_text_color=vrct_gui.settings.main.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR + entry_message_box={ + "bind_Return": messageBoxPressKeyEnter, + "bind_Any_KeyPress": messageBoxPressKeyAny, + }, ) if __name__ == "__main__": diff --git a/view.py b/view.py new file mode 100644 index 00000000..e902a09b --- /dev/null +++ b/view.py @@ -0,0 +1,43 @@ +from customtkinter import StringVar +from vrct_gui import vrct_gui + +from config import config + +def viewInitializer(sidebar_features, language_presets, entry_message_box): + + vrct_gui.CALLBACK_TOGGLE_TRANSLATION = sidebar_features["callback_toggle_translation"], + vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features["callback_toggle_transcription_send"], + vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features["callback_toggle_transcription_receive"], + vrct_gui.CALLBACK_TOGGLE_FOREGROUND = sidebar_features["callback_toggle_foreground"], + + + vrct_gui.sqls__optionmenu_your_language.configure(values=language_presets["values"]) + vrct_gui.sqls__optionmenu_your_language.configure(variable=StringVar(value=config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO])) + vrct_gui.sqls__optionmenu_target_language.configure(values=language_presets["values"]) + vrct_gui.sqls__optionmenu_target_language.configure(variable=StringVar(value=config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO])) + + vrct_gui.CALLBACK_SELECTED_TAB_NO_1 = language_presets["callback_selected_tab_no_1"] + vrct_gui.CALLBACK_SELECTED_TAB_NO_2 = language_presets["callback_selected_tab_no_2"] + vrct_gui.CALLBACK_SELECTED_TAB_NO_3 = language_presets["callback_selected_tab_no_3"] + vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) + + + def foregroundOffForcefully(e): + if config.ENABLE_FOREGROUND: + vrct_gui.attributes("-topmost", False) + + def foregroundOnForcefully(e): + if config.ENABLE_FOREGROUND: + vrct_gui.attributes("-topmost", True) + + + entry_message_box = getattr(vrct_gui, "entry_message_box") + entry_message_box.bind("", lambda: entry_message_box["bind_Return"]) + entry_message_box.bind("", lambda: entry_message_box["bind_Any_KeyPress"]) + entry_message_box.bind("", foregroundOffForcefully) + entry_message_box.bind("", foregroundOnForcefully) + + + + + diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index f0f27f5b..3e95aa62 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -2,8 +2,6 @@ from .widgets import createConfigWindowTitle, createSettingBoxTitle, createSideM from customtkinter import CTkToplevel -from ..ui_utils import setDefaultActiveTab - from config import config class ConfigWindow(CTkToplevel): diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index 08ef24a7..2c47507f 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -1,6 +1,6 @@ from customtkinter import CTkFrame, CTkScrollableFrame -from ....ui_utils import setDefaultActiveTab +from ....ui_utils import _setDefaultActiveTab from ._addConfigSideMenuItem import _addConfigSideMenuItem from ._createSettingBoxContainer import _createSettingBoxContainer @@ -138,7 +138,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): # Set default active side menu tab config_window.main_current_active_config_title.configure(text=sm_and_sbc_setting["text"]) config_window.current_active_side_menu_tab = getattr(config_window, sm_and_sbc_setting["side_menu_tab_attr_name"]) - setDefaultActiveTab( + _setDefaultActiveTab( active_tab_widget=config_window.current_active_side_menu_tab, active_bg_color=settings.ctm.SIDE_MENU_LABELS_BG_COLOR, active_text_color=settings.ctm.SIDE_MENU_LABELS_SELECTED_TEXT_COLOR diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 0a24336b..86e6b75f 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -1,6 +1,6 @@ from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkSwitch, CTkImage, StringVar -from ...ui_utils import getImageFileFromUiUtils, openImageKeepAspectRatio, retag, getLatestHeight, bindEnterAndLeaveColor, bindButtonPressColor, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressAndReleaseFunction, setDefaultActiveTab, bindButtonFunctionAndColor, switchActiveTabAndPassiveTab, switchTabsColor +from ...ui_utils import getImageFileFromUiUtils, openImageKeepAspectRatio, retag, getLatestHeight, bindEnterAndLeaveColor, bindButtonPressColor, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressAndReleaseFunction, bindButtonFunctionAndColor, switchActiveTabAndPassiveTab, switchTabsColor from time import sleep @@ -512,14 +512,6 @@ def createSidebar(settings, main_window): column+=1 - # Set default active preset tab - # main_window.current_active_preset_tab = getattr(main_window, "sqls__presets_button_1") - # setDefaultActiveTab( - # active_tab_widget=main_window.current_active_preset_tab, - # active_bg_color=settings.ctm.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR, - # active_text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR - # ) - # Quick Language settings BOX main_window.sqls__box_frame = CTkFrame(main_window.sqls__container, corner_radius=0, fg_color=settings.ctm.SQLS__BG_COLOR, width=0, height=0) diff --git a/vrct_gui/main_window/widgets/create_textbox.py b/vrct_gui/main_window/widgets/create_textbox.py index 1bc4a05b..c2d6f813 100644 --- a/vrct_gui/main_window/widgets/create_textbox.py +++ b/vrct_gui/main_window/widgets/create_textbox.py @@ -1,6 +1,6 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkTextbox -from ...ui_utils import getLatestWidth, getLongestText, bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, setDefaultActiveTab, switchActiveTabAndPassiveTab, switchTabsColor +from ...ui_utils import getLatestWidth, getLongestText, bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, _setDefaultActiveTab, switchActiveTabAndPassiveTab, switchTabsColor def createTextbox(settings, main_window): @@ -167,7 +167,7 @@ def createTextbox(settings, main_window): # Set default active textbox tab main_window.current_active_textbox_tab = getattr(main_window, "textbox_tab_all") - setDefaultActiveTab( + _setDefaultActiveTab( active_tab_widget=main_window.current_active_textbox_tab, active_bg_color=settings.ctm.TEXTBOX_TAB_BG_ACTIVE_COLOR, active_text_color=settings.ctm.TEXTBOX_TAB_TEXT_ACTIVE_COLOR diff --git a/vrct_gui/ui_utils/__init__.py b/vrct_gui/ui_utils/__init__.py index bc2a1337..e501aebe 100644 --- a/vrct_gui/ui_utils/__init__.py +++ b/vrct_gui/ui_utils/__init__.py @@ -1 +1,2 @@ -from .ui_utils import * \ No newline at end of file +from .ui_utils import * +from .ui_utils import _setDefaultActiveTab \ No newline at end of file diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index eb45ea3d..d714fc3c 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -83,7 +83,7 @@ def unbindEventFromActiveTabWidget(active_tab_widget): active_tab_widget.unbind(event_name) active_tab_widget.children["!ctklabel"].unbind(event_name) -def setDefaultActiveTab(active_tab_widget, active_bg_color, active_text_color): +def _setDefaultActiveTab(active_tab_widget, active_bg_color, active_text_color): active_tab_widget.configure(fg_color=active_bg_color, cursor="") active_tab_widget.children["!ctklabel"].configure(fg_color=active_bg_color, text_color=active_text_color) unbindEventFromActiveTabWidget(active_tab_widget) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 29fb5bc2..dad8a8a6 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -10,6 +10,7 @@ from ._printToTextbox import _printToTextbox from .main_window import createMainWindowWidgets from .config_window import ConfigWindow +from .ui_utils import _setDefaultActiveTab from config import config @@ -104,5 +105,14 @@ class VRCT_GUI(CTk): tags=tags, ) + def setDefaultActiveLanguagePresetTab(self, tab_no:str): + vrct_gui.current_active_preset_tab = getattr(self, f"sqls__presets_button_{tab_no}") + _setDefaultActiveTab( + active_tab_widget=vrct_gui.current_active_preset_tab, + active_bg_color=vrct_gui.settings.main.ctm.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR, + active_text_color=vrct_gui.settings.main.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR + ) + + vrct_gui = VRCT_GUI() \ No newline at end of file From 36dde7f0b30afc0af1e3c4c756fe73e2baeecb13 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 30 Aug 2023 19:54:40 +0900 Subject: [PATCH 053/355] =?UTF-8?q?main.py=E3=81=8B=E3=82=89view.py?= =?UTF-8?q?=E3=81=AB=E3=81=84=E3=81=8F=E3=81=A4=E3=81=8B=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 82 ++++++++++++++-------------------------------- view.py | 100 ++++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 103 insertions(+), 79 deletions(-) diff --git a/main.py b/main.py index 1d84ce3e..c38a2161 100644 --- a/main.py +++ b/main.py @@ -3,8 +3,8 @@ import customtkinter from vrct_gui import vrct_gui from config import config from model import model - -from view import viewInitializer +from customtkinter import StringVar +from view import view # func transcription send message def sendMicMessage(message): @@ -35,11 +35,11 @@ def sendMicMessage(message): def startTranscriptionSendMessage(): model.startMicTranscript(sendMicMessage) - vrct_gui.changeMainWindowWidgetsStatus("normal", "All") + view.setMainWindowAllWidgetsStatusToNormal() def stopTranscriptionSendMessage(): model.stopMicTranscript() - vrct_gui.changeMainWindowWidgetsStatus("normal", "All") + view.setMainWindowAllWidgetsStatusToNormal() # func transcription receive message def receiveSpeakerMessage(message): @@ -61,11 +61,11 @@ def receiveSpeakerMessage(message): def startTranscriptionReceiveMessage(): model.startSpeakerTranscript(receiveSpeakerMessage) - vrct_gui.changeMainWindowWidgetsStatus("normal", "All") + view.setMainWindowAllWidgetsStatusToNormal() def stopTranscriptionReceiveMessage(): model.stopSpeakerTranscript() - vrct_gui.changeMainWindowWidgetsStatus("normal", "All") + view.setMainWindowAllWidgetsStatusToNormal() # func message box def sendChatMessage(message): @@ -99,8 +99,7 @@ def sendChatMessage(message): def messageBoxPressKeyEnter(e): model.oscStopSendTyping() - entry_message_box = getattr(vrct_gui, "entry_message_box") - message = entry_message_box.get() + message = view.getTextFromMessageBox() sendChatMessage(message) def messageBoxPressKeyAny(e): @@ -179,36 +178,6 @@ def callbackSelectedTabNo3(): config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) # func print textbox -def logTranslationStatusChange(): - textbox_all = getattr(vrct_gui, "textbox_all") - textbox_system = getattr(vrct_gui, "textbox_system") - if config.ENABLE_TRANSLATION is True: - vrct_gui.printToTextbox(textbox_all, "翻訳機能をONにしました", "", "INFO") - vrct_gui.printToTextbox(textbox_system, "翻訳機能をONにしました", "", "INFO") - else: - vrct_gui.printToTextbox(textbox_all, "翻訳機能をOFFにしました", "", "INFO") - vrct_gui.printToTextbox(textbox_system, "翻訳機能をOFFにしました", "", "INFO") - -def logTranscriptionSendStatusChange(): - textbox_all = getattr(vrct_gui, "textbox_all") - textbox_system = getattr(vrct_gui, "textbox_system") - if config.ENABLE_TRANSCRIPTION_SEND is True: - vrct_gui.printToTextbox(textbox_all, "Voice2chatbox機能をONにしました", "", "INFO") - vrct_gui.printToTextbox(textbox_system, "Voice2chatbox機能をONにしました", "", "INFO") - else: - vrct_gui.printToTextbox(textbox_all, "Voice2chatbox機能をOFFにしました", "", "INFO") - vrct_gui.printToTextbox(textbox_system, "Voice2chatbox機能をOFFにしました", "", "INFO") - -def logTranscriptionReceiveStatusChange(): - textbox_all = getattr(vrct_gui, "textbox_all") - textbox_system = getattr(vrct_gui, "textbox_system") - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - vrct_gui.printToTextbox(textbox_all, "Speaker2chatbox機能をONにしました", "", "INFO") - vrct_gui.printToTextbox(textbox_system, "Speaker2chatbox機能をONにしました", "", "INFO") - else: - vrct_gui.printToTextbox(textbox_all, "Speaker2chatbox機能をOFFにしました", "", "INFO") - vrct_gui.printToTextbox(textbox_system, "Speaker2chatbox機能をOFFにしました", "", "INFO") - def logSendMessage(message, translate): textbox_all = getattr(vrct_gui, "textbox_all") textbox_sent = getattr(vrct_gui, "textbox_sent") @@ -239,54 +208,51 @@ def logOSCError(): vrct_gui.printToTextbox(textbox_all, "OSC is not enabled, please enable OSC and rejoin", "", "INFO") vrct_gui.printToTextbox(textbox_system, "OSC is not enabled, please enable OSC and rejoin", "", "INFO") -def logForegroundStatusChange(): - textbox_all = getattr(vrct_gui, "textbox_all") - textbox_system = getattr(vrct_gui, "textbox_system") - if config.ENABLE_FOREGROUND is True: - vrct_gui.printToTextbox(textbox_all, "Start foreground", "", "INFO") - vrct_gui.printToTextbox(textbox_system, "Start foreground", "", "INFO") - else: - vrct_gui.printToTextbox(textbox_all, "Stop foreground", "", "INFO") - vrct_gui.printToTextbox(textbox_system, "Stop foreground", "", "INFO") # command func def callbackToggleTranslation(): - config.ENABLE_TRANSLATION = getattr(vrct_gui, "translation_switch_box").get() - logTranslationStatusChange() + config.ENABLE_TRANSLATION = view.getTranslationButtonStatus() + if config.ENABLE_TRANSLATION is True: + view.printToTextbox_enableTranslation() + else: + view.printToTextbox_disableTranslation() def callbackToggleTranscriptionSend(): - vrct_gui.changeMainWindowWidgetsStatus("disabled", "All") - config.ENABLE_TRANSCRIPTION_SEND = getattr(vrct_gui, "transcription_send_switch_box").get() + view.setMainWindowAllWidgetsStatusToDisabled() + config.ENABLE_TRANSCRIPTION_SEND = view.getTranscriptionSendButtonStatus() if config.ENABLE_TRANSCRIPTION_SEND is True: + view.printToTextbox_enableTranscriptionSend() th_startTranscriptionSendMessage = Thread(target=startTranscriptionSendMessage) th_startTranscriptionSendMessage.daemon = True th_startTranscriptionSendMessage.start() else: + view.printToTextbox_disableTranscriptionSend() th_stopTranscriptionSendMessage = Thread(target=stopTranscriptionSendMessage) th_stopTranscriptionSendMessage.daemon = True th_stopTranscriptionSendMessage.start() - logTranscriptionSendStatusChange() def callbackToggleTranscriptionReceive(): - vrct_gui.changeMainWindowWidgetsStatus("disabled", "All") - config.ENABLE_TRANSCRIPTION_RECEIVE = getattr(vrct_gui, "transcription_receive_switch_box").get() + view.setMainWindowAllWidgetsStatusToDisabled() + config.ENABLE_TRANSCRIPTION_RECEIVE = view.getTranscriptionReceiveButtonStatus() if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + view.printToTextbox_enableTranscriptionReceive() th_startTranscriptionReceiveMessage = Thread(target=startTranscriptionReceiveMessage) th_startTranscriptionReceiveMessage.daemon = True th_startTranscriptionReceiveMessage.start() else: + view.printToTextbox_disableTranscriptionReceive() th_stopTranscriptionReceiveMessage = Thread(target=stopTranscriptionReceiveMessage) th_stopTranscriptionReceiveMessage.daemon = True th_stopTranscriptionReceiveMessage.start() - logTranscriptionReceiveStatusChange() def callbackToggleForeground(): - config.ENABLE_FOREGROUND = getattr(vrct_gui, "foreground_switch_box").get() + config.ENABLE_FOREGROUND = view.getForegroundButtonStatus() if config.ENABLE_FOREGROUND is True: + view.printToTextbox_enableForeground() vrct_gui.attributes("-topmost", True) else: + view.printToTextbox_disableForeground() vrct_gui.attributes("-topmost", False) - logForegroundStatusChange() # create GUI vrct_gui.createGUI() @@ -306,7 +272,7 @@ model.checkOSCStarted() model.checkSoftwareUpdated() # set UI and callback -viewInitializer( +view.initializer( sidebar_features={ "callback_toggle_translation": callbackToggleTranslation, "callback_toggle_transcription_send": callbackToggleTranscriptionSend, diff --git a/view.py b/view.py index e902a09b..e472f0d5 100644 --- a/view.py +++ b/view.py @@ -3,41 +3,99 @@ from vrct_gui import vrct_gui from config import config -def viewInitializer(sidebar_features, language_presets, entry_message_box): - - vrct_gui.CALLBACK_TOGGLE_TRANSLATION = sidebar_features["callback_toggle_translation"], - vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features["callback_toggle_transcription_send"], - vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features["callback_toggle_transcription_receive"], - vrct_gui.CALLBACK_TOGGLE_FOREGROUND = sidebar_features["callback_toggle_foreground"], +class view(): + def __init__(self): + pass - vrct_gui.sqls__optionmenu_your_language.configure(values=language_presets["values"]) - vrct_gui.sqls__optionmenu_your_language.configure(variable=StringVar(value=config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO])) - vrct_gui.sqls__optionmenu_target_language.configure(values=language_presets["values"]) - vrct_gui.sqls__optionmenu_target_language.configure(variable=StringVar(value=config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO])) + def initializer(self, sidebar_features, language_presets, entry_message_box): - vrct_gui.CALLBACK_SELECTED_TAB_NO_1 = language_presets["callback_selected_tab_no_1"] - vrct_gui.CALLBACK_SELECTED_TAB_NO_2 = language_presets["callback_selected_tab_no_2"] - vrct_gui.CALLBACK_SELECTED_TAB_NO_3 = language_presets["callback_selected_tab_no_3"] - vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) + vrct_gui.CALLBACK_TOGGLE_TRANSLATION = sidebar_features["callback_toggle_translation"] + vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features["callback_toggle_transcription_send"] + vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features["callback_toggle_transcription_receive"] + vrct_gui.CALLBACK_TOGGLE_FOREGROUND = sidebar_features["callback_toggle_foreground"] - def foregroundOffForcefully(e): + vrct_gui.sqls__optionmenu_your_language.configure(values=language_presets["values"]) + vrct_gui.sqls__optionmenu_your_language.configure(command=language_presets["callback_your_language"]) + vrct_gui.sqls__optionmenu_your_language.configure(variable=StringVar(value=config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO])) + vrct_gui.sqls__optionmenu_target_language.configure(values=language_presets["values"]) + vrct_gui.sqls__optionmenu_target_language.configure(command=language_presets["callback_target_language"]) + vrct_gui.sqls__optionmenu_target_language.configure(variable=StringVar(value=config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO])) + + vrct_gui.CALLBACK_SELECTED_TAB_NO_1 = language_presets["callback_selected_tab_no_1"] + vrct_gui.CALLBACK_SELECTED_TAB_NO_2 = language_presets["callback_selected_tab_no_2"] + vrct_gui.CALLBACK_SELECTED_TAB_NO_3 = language_presets["callback_selected_tab_no_3"] + vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) + + + entry_message_box = getattr(vrct_gui, "entry_message_box") + entry_message_box.bind("", lambda: entry_message_box["bind_Return"]) + entry_message_box.bind("", lambda: entry_message_box["bind_Any_KeyPress"]) + entry_message_box.bind("", self._foregroundOffForcefully) + entry_message_box.bind("", self._foregroundOnForcefully) + + + + def setMainWindowAllWidgetsStatusToNormal(self): + vrct_gui.changeMainWindowWidgetsStatus("normal", "All") + + def setMainWindowAllWidgetsStatusToDisabled(self): + vrct_gui.changeMainWindowWidgetsStatus("disabled", "All") + + + + def _foregroundOffForcefully(self, _e): if config.ENABLE_FOREGROUND: vrct_gui.attributes("-topmost", False) - def foregroundOnForcefully(e): + def _foregroundOnForcefully(self, _e): if config.ENABLE_FOREGROUND: vrct_gui.attributes("-topmost", True) - entry_message_box = getattr(vrct_gui, "entry_message_box") - entry_message_box.bind("", lambda: entry_message_box["bind_Return"]) - entry_message_box.bind("", lambda: entry_message_box["bind_Any_KeyPress"]) - entry_message_box.bind("", foregroundOffForcefully) - entry_message_box.bind("", foregroundOnForcefully) + def getTranslationButtonStatus(self): + return vrct_gui.translation_switch_box.get() + def getTranscriptionSendButtonStatus(self): + return vrct_gui.transcription_send_switch_box.get() + def getTranscriptionReceiveButtonStatus(self): + return vrct_gui.transcription_receive_switch_box.get() + def getForegroundButtonStatus(self): + return vrct_gui.foreground_switch_box.get() + def printToTextbox_enableTranslation(self): + self._printToTextbox_Info("翻訳機能をONにしました") + def printToTextbox_disableTranslation(self): + self._printToTextbox_Info("翻訳機能をOFFにしました") + + def printToTextbox_enableTranscriptionSend(self): + self._printToTextbox_Info("Voice2chatbox機能をONにしました") + def printToTextbox_disableTranscriptionSend(self): + self._printToTextbox_Info("Voice2chatbox機能をOFFにしました") + + def printToTextbox_enableTranscriptionReceive(self): + self._printToTextbox_Info("Speaker2chatbox機能をONにしました") + def printToTextbox_disableTranscriptionReceive(self): + self._printToTextbox_Info("Speaker2chatbox機能をOFFにしました") + + def printToTextbox_enableForeground(self): + self._printToTextbox_Info("Start foreground") + def printToTextbox_disableForeground(self): + self._printToTextbox_Info("Stop foreground") + + + def _printToTextbox_Info(self, info_message): + vrct_gui.printToTextbox(vrct_gui.textbox_all, info_message, "", "INFO") + vrct_gui.printToTextbox(vrct_gui.textbox_system, info_message, "", "INFO") + pass + + + + def getTextFromMessageBox(self): + return vrct_gui.entry_message_box.get() + +view = view() \ No newline at end of file From fde7ea6a75631052a1870e50387b1ce3bc5a3089 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 31 Aug 2023 00:19:19 +0900 Subject: [PATCH 054/355] =?UTF-8?q?main.py=E3=81=8B=E3=82=89view.py?= =?UTF-8?q?=E3=81=AB=E3=81=84=E3=81=8F=E3=81=A4=E3=81=8B=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E7=A7=BB=E5=8B=95=20=E7=B6=9A=E3=81=8D(messageBoxPressKeyAny?= =?UTF-8?q?=E3=81=AF=E5=88=A5=E3=81=AE=E5=A0=B4=E6=89=80=E3=81=AB=E7=A7=BB?= =?UTF-8?q?=E5=8B=95=E3=81=AB=E3=81=AA=E3=82=8B=E3=81=AE=E3=81=A7=E5=88=A5?= =?UTF-8?q?=E3=81=AE=E3=82=B3=E3=83=9F=E3=83=83=E3=83=88=E3=81=AB=E3=80=82?= =?UTF-8?q?=E3=81=9D=E3=82=8C=E4=BB=A5=E5=A4=96=E3=81=AF=E5=AE=8C=E4=BA=86?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 85 +++++++++++++++++++-------------------------------------- view.py | 74 +++++++++++++++++++++++++++++++++++++++++-------- 2 files changed, 90 insertions(+), 69 deletions(-) diff --git a/main.py b/main.py index c38a2161..27d81c3a 100644 --- a/main.py +++ b/main.py @@ -11,12 +11,12 @@ def sendMicMessage(message): if len(message) > 0: translation = "" if model.checkKeywords(message): - logDetectWordFilter(message) + view.printToTextbox_DetectedByWordFilter(detected_message=message) return elif config.ENABLE_TRANSLATION is False: pass elif model.getTranslatorStatus() is False: - logAuthenticationError() + view.printToTextbox_AuthenticationError() else: translation = model.getInputTranslate(message) @@ -29,9 +29,9 @@ def sendMicMessage(message): osc_message = message model.oscSendMessage(osc_message) else: - logOSCError() + view.printToTextbox_OSCError() - logSendMessage(message, translation) + view.printToTextbox_SentMessage(message, translation) def startTranscriptionSendMessage(): model.startMicTranscript(sendMicMessage) @@ -48,7 +48,7 @@ def receiveSpeakerMessage(message): if config.ENABLE_TRANSLATION is False: pass elif model.getTranslatorStatus() is False: - logAuthenticationError() + view.printToTextbox_AuthenticationError() else: translation = model.getOutputTranslate(message) @@ -57,7 +57,7 @@ def receiveSpeakerMessage(message): xsoverlay_message = config.MESSAGE_FORMAT.replace("[message]", message) xsoverlay_message = xsoverlay_message.replace("[translation]", translation) model.notificationXSOverlay(xsoverlay_message) - logReceiveMessage(message, translation) + view.printToTextbox_ReceivedMessage(message, translation) def startTranscriptionReceiveMessage(): model.startSpeakerTranscript(receiveSpeakerMessage) @@ -74,7 +74,7 @@ def sendChatMessage(message): if config.ENABLE_TRANSLATION is False: pass elif model.getTranslatorStatus() is False: - logAuthenticationError() + view.printToTextbox_AuthenticationError() else: translation = model.getInputTranslate(message) @@ -87,15 +87,14 @@ def sendChatMessage(message): osc_message = message model.oscSendMessage(osc_message) else: - logOSCError() + view.printToTextbox_OSCError() # update textbox message log - logSendMessage(message, translation) + view.printToTextbox_SentMessage(message, translation) # delete message in entry message box if config.ENABLE_AUTO_CLEAR_CHATBOX is True: - entry_message_box = getattr(vrct_gui, "entry_message_box") - entry_message_box.delete(0, customtkinter.END) + view.clearMessageBox() def messageBoxPressKeyEnter(e): model.oscStopSendTyping() @@ -131,8 +130,7 @@ def setTargetLanguageAndCountry(select): def callbackSelectedTabNo1(): config.SELECTED_TAB_NO = "1" - vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] - vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] + view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) languages = config.SELECTED_TAB_YOUR_LANGUAGES select = languages[config.SELECTED_TAB_NO] language, country = model.getLanguageAndCountry(select) @@ -147,8 +145,7 @@ def callbackSelectedTabNo1(): def callbackSelectedTabNo2(): config.SELECTED_TAB_NO = "2" - vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] - vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] + view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) languages = config.SELECTED_TAB_YOUR_LANGUAGES select = languages[config.SELECTED_TAB_NO] language, country = model.getLanguageAndCountry(select) @@ -163,8 +160,7 @@ def callbackSelectedTabNo2(): def callbackSelectedTabNo3(): config.SELECTED_TAB_NO = "3" - vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] - vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] + view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) languages = config.SELECTED_TAB_YOUR_LANGUAGES select = languages[config.SELECTED_TAB_NO] language, country = model.getLanguageAndCountry(select) @@ -177,37 +173,6 @@ def callbackSelectedTabNo3(): config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) -# func print textbox -def logSendMessage(message, translate): - textbox_all = getattr(vrct_gui, "textbox_all") - textbox_sent = getattr(vrct_gui, "textbox_sent") - vrct_gui.printToTextbox(textbox_all, message, translate, "SEND") - vrct_gui.printToTextbox(textbox_sent, message, translate, "SEND") - -def logReceiveMessage(message, translate): - textbox_all = getattr(vrct_gui, "textbox_all") - textbox_sent = getattr(vrct_gui, "textbox_received") - vrct_gui.printToTextbox(textbox_all, message, translate, "RECEIVE") - vrct_gui.printToTextbox(textbox_sent, message, translate, "RECEIVE") - -def logDetectWordFilter(message): - textbox_all = getattr(vrct_gui, "textbox_all") - textbox_system = getattr(vrct_gui, "textbox_system") - vrct_gui.printToTextbox(textbox_all, f"Detect WordFilter :{message}", "", "INFO") - vrct_gui.printToTextbox(textbox_system, f"Detect WordFilter :{message}", "", "INFO") - -def logAuthenticationError(): - textbox_all = getattr(vrct_gui, "textbox_all") - textbox_system = getattr(vrct_gui, "textbox_system") - vrct_gui.printToTextbox(textbox_all, "Auth Key or language setting is incorrect", "", "INFO") - vrct_gui.printToTextbox(textbox_system, "Auth Key or language setting is incorrect", "", "INFO") - -def logOSCError(): - textbox_all = getattr(vrct_gui, "textbox_all") - textbox_system = getattr(vrct_gui, "textbox_system") - vrct_gui.printToTextbox(textbox_all, "OSC is not enabled, please enable OSC and rejoin", "", "INFO") - vrct_gui.printToTextbox(textbox_system, "OSC is not enabled, please enable OSC and rejoin", "", "INFO") - # command func def callbackToggleTranslation(): @@ -249,18 +214,18 @@ def callbackToggleForeground(): config.ENABLE_FOREGROUND = view.getForegroundButtonStatus() if config.ENABLE_FOREGROUND is True: view.printToTextbox_enableForeground() - vrct_gui.attributes("-topmost", True) + view.foregroundOn() else: view.printToTextbox_disableForeground() - vrct_gui.attributes("-topmost", False) + view.foregroundOff() # create GUI -vrct_gui.createGUI() +view.createGUI() # init config if model.authenticationTranslator() is False: # error update Auth key - logAuthenticationError() + view.printToTextbox_AuthenticationError() # set word filter model.addKeywords() @@ -290,11 +255,17 @@ view.initializer( "callback_selected_tab_no_3": callbackSelectedTabNo3, }, - entry_message_box={ - "bind_Return": messageBoxPressKeyEnter, - "bind_Any_KeyPress": messageBoxPressKeyAny, - }, + # 辞書型で関数を渡しても上手く行かず、仕方なくタプルで渡してる。 + # 本当はコメントアウト(以下とview.py内33,34行目)しているようにできたらいいけど、 + # _tkinter.TclError: unknown option "-bind_Any_KeyPress"みたいにエラーがでる。 + entry_message_box=None, + # entry_message_box={ + # "bind_Return": messageBoxPressKeyEnter, + # "bind_Any_KeyPress": messageBoxPressKeyAny, + # }, + entry_message_box_bind_Return=messageBoxPressKeyEnter, + entry_message_box_bind_Any_KeyPress=messageBoxPressKeyAny, ) if __name__ == "__main__": - vrct_gui.startMainLoop() \ No newline at end of file + view.startMainLoop() \ No newline at end of file diff --git a/view.py b/view.py index e472f0d5..0f7c2590 100644 --- a/view.py +++ b/view.py @@ -1,14 +1,14 @@ -from customtkinter import StringVar +from customtkinter import StringVar, END as CTK_END from vrct_gui import vrct_gui from config import config -class view(): +class View(): def __init__(self): pass - def initializer(self, sidebar_features, language_presets, entry_message_box): + def initializer(self, sidebar_features, language_presets, entry_message_box, entry_message_box_bind_Return, entry_message_box_bind_Any_KeyPress): vrct_gui.CALLBACK_TOGGLE_TRANSLATION = sidebar_features["callback_toggle_translation"] vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features["callback_toggle_transcription_send"] @@ -30,8 +30,11 @@ class view(): entry_message_box = getattr(vrct_gui, "entry_message_box") - entry_message_box.bind("", lambda: entry_message_box["bind_Return"]) - entry_message_box.bind("", lambda: entry_message_box["bind_Any_KeyPress"]) + # entry_message_box.bind("", lambda e: entry_message_box["bind_Return"](e)) + # entry_message_box.bind("", lambda e: entry_message_box["bind_Any_KeyPress"](e)) + entry_message_box.bind("", entry_message_box_bind_Return) + entry_message_box.bind("", entry_message_box_bind_Any_KeyPress) + entry_message_box.bind("", self._foregroundOffForcefully) entry_message_box.bind("", self._foregroundOnForcefully) @@ -45,15 +48,25 @@ class view(): - def _foregroundOffForcefully(self, _e): - if config.ENABLE_FOREGROUND: - vrct_gui.attributes("-topmost", False) - def _foregroundOnForcefully(self, _e): if config.ENABLE_FOREGROUND: - vrct_gui.attributes("-topmost", True) + self.foregroundOn() + + def _foregroundOffForcefully(self, _e): + if config.ENABLE_FOREGROUND: + self.foregroundOff() + def foregroundOn(self): + vrct_gui.attributes("-topmost", True) + + def foregroundOff(self): + vrct_gui.attributes("-topmost", False) + + + def updateGuiVariableByPresetTabNo(self, tab_no:str): + vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[tab_no] + vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[tab_no] @@ -88,14 +101,51 @@ class view(): self._printToTextbox_Info("Stop foreground") + def printToTextbox_AuthenticationError(self): + self._printToTextbox_Info("Auth Key or language setting is incorrect") + + def printToTextbox_OSCError(self): + self._printToTextbox_Info("OSC is not enabled, please enable OSC and rejoin") + + def printToTextbox_DetectedByWordFilter(self, detected_message): + self._printToTextbox_Info(f"Detect WordFilter :{detected_message}") + + def _printToTextbox_Info(self, info_message): vrct_gui.printToTextbox(vrct_gui.textbox_all, info_message, "", "INFO") vrct_gui.printToTextbox(vrct_gui.textbox_system, info_message, "", "INFO") - pass + def printToTextbox_SentMessage(self, original_message, translated_message): + self._printToTextbox_Sent(original_message, translated_message) + + def _printToTextbox_Sent(self, original_message, translated_message): + vrct_gui.printToTextbox(vrct_gui.textbox_all, original_message, translated_message, "SEND") + vrct_gui.printToTextbox(vrct_gui.textbox_sent, original_message, translated_message, "SEND") + + + def printToTextbox_ReceivedMessage(self, original_message, translated_message): + self._printToTextbox_Received(original_message, translated_message) + + def _printToTextbox_Received(self, original_message, translated_message): + vrct_gui.printToTextbox(vrct_gui.textbox_all, original_message, translated_message, "RECEIVE") + vrct_gui.printToTextbox(vrct_gui.textbox_received, original_message, translated_message, "RECEIVE") + + def getTextFromMessageBox(self): return vrct_gui.entry_message_box.get() -view = view() \ No newline at end of file + def clearMessageBox(self): + vrct_gui.entry_message_box.delete(0, CTK_END) + + + + + def createGUI(self): + vrct_gui.createGUI() + + def startMainLoop(self): + vrct_gui.startMainLoop() + +view = View() \ No newline at end of file From be5a0e386fbc5b761ae7ab49ae082497d3e18e57 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 31 Aug 2023 00:43:10 +0900 Subject: [PATCH 055/355] =?UTF-8?q?view.py=E3=81=B8=E3=81=AE=E5=88=86?= =?UTF-8?q?=E9=9B=A2=E5=AE=8C=E4=BA=86(messageBoxPressKeyAny=E3=82=92gui?= =?UTF-8?q?=E5=86=85=E9=83=A8=E3=81=AB=E7=A7=BB=E5=8B=95=E3=80=81=E4=BD=BF?= =?UTF-8?q?=E3=82=8F=E3=81=AA=E3=81=8F=E3=81=AA=E3=81=A3=E3=81=9Fconfig.py?= =?UTF-8?q?=E3=81=AEBREAK=5FKEYSYM=5FLIST=E3=81=AF=E4=B8=80=E6=97=A6?= =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 14 +++++++------- main.py | 8 -------- .../widgets/create_entry_message_box.py | 14 ++++++++++++++ 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/config.py b/config.py index 9d8d2ae0..e6199231 100644 --- a/config.py +++ b/config.py @@ -400,9 +400,9 @@ class Config: def GITHUB_URL(self): return self._GITHUB_URL - @property - def BREAK_KEYSYM_LIST(self): - return self._BREAK_KEYSYM_LIST + # @property + # def BREAK_KEYSYM_LIST(self): + # return self._BREAK_KEYSYM_LIST @property def MAX_MIC_ENERGY_THRESHOLD(self): @@ -487,10 +487,10 @@ class Config: self._ENABLE_OSC = False self._UPDATE_FLAG = False self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" - self._BREAK_KEYSYM_LIST = [ - "Delete", "Select", "Up", "Down", "Next", "End", "Print", - "Prior","Insert","Home", "Left", "Clear", "Right", "Linefeed" - ] + # self._BREAK_KEYSYM_LIST = [ + # "Delete", "Select", "Up", "Down", "Next", "End", "Print", + # "Prior","Insert","Home", "Left", "Clear", "Right", "Linefeed" + # ] self._MAX_MIC_ENERGY_THRESHOLD = 2000 self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 self._SELECTED_TAB_NO = "1" diff --git a/main.py b/main.py index 27d81c3a..d1a39236 100644 --- a/main.py +++ b/main.py @@ -1,9 +1,6 @@ from threading import Thread -import customtkinter -from vrct_gui import vrct_gui from config import config from model import model -from customtkinter import StringVar from view import view # func transcription send message @@ -103,11 +100,6 @@ def messageBoxPressKeyEnter(e): def messageBoxPressKeyAny(e): model.oscStartSendTyping() - entry_message_box = getattr(vrct_gui, "entry_message_box") - if e.keysym != "??": - if len(e.char) != 0 and e.keysym in config.BREAK_KEYSYM_LIST: - entry_message_box.insert("end", e.char) - return "break" # func select languages def setYourLanguageAndCountry(select): diff --git a/vrct_gui/main_window/widgets/create_entry_message_box.py b/vrct_gui/main_window/widgets/create_entry_message_box.py index 61dfe593..7bdfb491 100644 --- a/vrct_gui/main_window/widgets/create_entry_message_box.py +++ b/vrct_gui/main_window/widgets/create_entry_message_box.py @@ -19,3 +19,17 @@ def createEntryMessageBox(settings, main_window): ) main_window.entry_message_box.grid(row=0, column=0, padx=settings.uism.TEXTBOX_ENTRY_PADX, pady=settings.uism.TEXTBOX_ENTRY_PADY, sticky="nsew") main_window.entry_message_box._entry.grid(padx=settings.uism.TEXTBOX_ENTRY_IPADX, pady=settings.uism.TEXTBOX_ENTRY_IPADY) + + + def messageBoxAnyKeyPress(e): + BREAK_KEYSYM_LIST = [ + "Delete", "Select", "Up", "Down", "Next", "End", "Print", + "Prior","Insert","Home", "Left", "Clear", "Right", "Linefeed" + ] + if e.keysym != "??": + if len(e.char) != 0 and e.keysym in BREAK_KEYSYM_LIST: + main_window.entry_message_box.insert("end", e.char) + return "break" + + main_window.entry_message_box.bind("", messageBoxAnyKeyPress) + From cb434717e001857187ac02f06982f060f389754c Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 31 Aug 2023 15:59:07 +0900 Subject: [PATCH 056/355] =?UTF-8?q?add=20UI=20to=20config=5Fwindow=20compa?= =?UTF-8?q?ct=5Fmode=E5=88=87=E3=82=8A=E6=9B=BF=E3=81=88=E3=82=B9=E3=82=A4?= =?UTF-8?q?=E3=83=83=E3=83=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/config_window/ConfigWindow.py | 7 +-- vrct_gui/config_window/widgets/__init__.py | 2 +- .../createSettingBoxTopBar/__init__.py | 1 + .../_createSettingBoxCompactModeButton.py | 56 +++++++++++++++++++ .../_createSettingBoxTitle.py} | 8 +-- .../createSettingBoxTopBar.py | 15 +++++ 6 files changed, 80 insertions(+), 9 deletions(-) create mode 100644 vrct_gui/config_window/widgets/createSettingBoxTopBar/__init__.py create mode 100644 vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py rename vrct_gui/config_window/widgets/{createSettingBoxTitle.py => createSettingBoxTopBar/_createSettingBoxTitle.py} (78%) create mode 100644 vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 3e95aa62..40f4c3bc 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -1,9 +1,8 @@ -from .widgets import createConfigWindowTitle, createSettingBoxTitle, createSideMenuAndSettingsBoxContainers +from .widgets import createConfigWindowTitle, createSideMenuAndSettingsBoxContainers, createSettingBoxTopBar + from customtkinter import CTkToplevel -from config import config - class ConfigWindow(CTkToplevel): def __init__(self, vrct_gui, settings): super().__init__() @@ -23,7 +22,7 @@ class ConfigWindow(CTkToplevel): createConfigWindowTitle(config_window=self, settings=settings) - createSettingBoxTitle(config_window=self, settings=settings) + createSettingBoxTopBar(config_window=self, settings=settings) createSideMenuAndSettingsBoxContainers(config_window=self, settings=settings) diff --git a/vrct_gui/config_window/widgets/__init__.py b/vrct_gui/config_window/widgets/__init__.py index 08f2304b..e3651955 100644 --- a/vrct_gui/config_window/widgets/__init__.py +++ b/vrct_gui/config_window/widgets/__init__.py @@ -1,4 +1,4 @@ from .createConfigWindowTitle import createConfigWindowTitle -from .createSettingBoxTitle import createSettingBoxTitle +from .createSettingBoxTopBar import createSettingBoxTopBar from .createSideMenuAndSettingsBoxContainers import createSideMenuAndSettingsBoxContainers \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/__init__.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/__init__.py new file mode 100644 index 00000000..b5be1139 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/__init__.py @@ -0,0 +1 @@ +from .createSettingBoxTopBar import createSettingBoxTopBar \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py new file mode 100644 index 00000000..ce98730a --- /dev/null +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py @@ -0,0 +1,56 @@ +from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkSwitch + +def _createSettingBoxCompactModeButton(parent_widget, config_window, settings): + + config_window.setting_box_compact_mode_button_container = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) + config_window.setting_box_compact_mode_button_container.grid(row=0, column=1, padx=(0, 20), sticky="nsw") + + + + config_window.setting_box_compact_mode_button_container.grid_rowconfigure((0,2), weight=1) + config_window.setting_box_compact_mode_button_container = CTkFrame(config_window.setting_box_compact_mode_button_container, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) + config_window.setting_box_compact_mode_button_container.grid(row=1, column=0, sticky="nsew") + + + config_window.setting_box_compact_mode_button_container.grid_rowconfigure(0, weight=1) + config_window.setting_box_compact_mode_label = CTkLabel( + config_window.setting_box_compact_mode_button_container, + height=0, + text="Compact Mode", + anchor="w", + font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal"), + text_color=settings.ctm.LABELS_TEXT_COLOR + ) + config_window.setting_box_compact_mode_label.grid(row=0, column=0, padx=(0,10)) + + + + + + + + + config_window.setting_box_compact_mode_switch_frame = CTkFrame(config_window.setting_box_compact_mode_button_container, corner_radius=0, width=0, height=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR) + config_window.setting_box_compact_mode_switch_frame.grid(row=0, column=1, padx=0, sticky="e") + + config_window.setting_box_compact_mode_switch_box = CTkSwitch( + config_window.setting_box_compact_mode_switch_frame, + text=None, + height=0, + width=0, + # corner_radius=0, + border_width=0, + switch_width=40, + switch_height=16, + onvalue=True, + offvalue=False, + # command=command, + # fg_color="", + # bg_color="red", + progress_color=settings.ctm.SB__SWITCH_BOX_ACTIVE_BG_COLOR, # SB__SWITCH_BOX_ACTIVE_BG_COLOR is for SB. change it later. + ) + + config_window.setting_box_compact_mode_switch_box.select() if settings.IS_CONFIG_WINDOW_COMPACT_MODE else config_window.setting_box_compact_mode_switch_box.deselect() + + config_window.setting_box_compact_mode_switch_box.grid(row=0, column=0) + diff --git a/vrct_gui/config_window/widgets/createSettingBoxTitle.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxTitle.py similarity index 78% rename from vrct_gui/config_window/widgets/createSettingBoxTitle.py rename to vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxTitle.py index 57f5c573..7e7479b7 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTitle.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxTitle.py @@ -1,10 +1,10 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel -def createSettingBoxTitle(config_window, settings): +def _createSettingBoxTitle(parent_widget, config_window, settings): - config_window.grid_columnconfigure(1, weight=1) - config_window.main_current_active_config_title_container = CTkFrame(config_window, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) - config_window.main_current_active_config_title_container.grid(row=0, column=1, sticky="nsew") + parent_widget.grid_columnconfigure(0, weight=1) + config_window.main_current_active_config_title_container = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) + config_window.main_current_active_config_title_container.grid(row=0, column=0, sticky="nsew") config_window.main_current_active_config_title_container.grid_rowconfigure(0, weight=1) diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py new file mode 100644 index 00000000..a42d43f9 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py @@ -0,0 +1,15 @@ +from customtkinter import CTkFont, CTkFrame, CTkLabel + +from ._createSettingBoxTitle import _createSettingBoxTitle +from ._createSettingBoxCompactModeButton import _createSettingBoxCompactModeButton + +def createSettingBoxTopBar(config_window, settings): + + config_window.grid_columnconfigure(1, weight=1) + config_window.setting_box_top_bar = CTkFrame(config_window, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) + config_window.setting_box_top_bar.grid(row=0, column=1, sticky="nsew") + + + _createSettingBoxTitle(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings) + + _createSettingBoxCompactModeButton(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings) \ No newline at end of file From 08d3f0473897c0f3997d55d508769b750c4724a3 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 31 Aug 2023 19:13:14 +0900 Subject: [PATCH 057/355] add UI and Feature: Config Window Compact Mode. --- config.py | 14 ++++++++++++++ main.py | 15 +++++++++++++++ view.py | 16 +++++++++++++++- vrct_gui/config_window/ConfigWindow.py | 8 ++++++++ .../_createSettingBoxCompactModeButton.py | 13 ++++++++++++- vrct_gui/vrct_gui.py | 8 ++++---- 6 files changed, 68 insertions(+), 6 deletions(-) diff --git a/config.py b/config.py index e6199231..a66921a4 100644 --- a/config.py +++ b/config.py @@ -442,6 +442,17 @@ class Config: self._SELECTED_TAB_TARGET_LANGUAGES = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + # Config Window + @property + def IS_CONFIG_WINDOW_COMPACT_MODE(self): + return self._IS_CONFIG_WINDOW_COMPACT_MODE + + @IS_CONFIG_WINDOW_COMPACT_MODE.setter + def IS_CONFIG_WINDOW_COMPACT_MODE(self, value): + if type(value) is bool: + self._IS_CONFIG_WINDOW_COMPACT_MODE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + def init_config(self): self._VERSION = "1.3.2" self._PATH_CONFIG = "./config.json" @@ -505,6 +516,9 @@ class Config: "3":"English\n(United States)", } + # Config Window + self._IS_CONFIG_WINDOW_COMPACT_MODE = False + def load_config(self): if os_path.isfile(self.PATH_CONFIG) is not False: with open(self.PATH_CONFIG, 'r') as fp: diff --git a/main.py b/main.py index d1a39236..b6bf26b2 100644 --- a/main.py +++ b/main.py @@ -211,6 +211,16 @@ def callbackToggleForeground(): view.printToTextbox_disableForeground() view.foregroundOff() + +# Config Window +def callbackEnableConfigWindowCompactMode(): + config.IS_CONFIG_WINDOW_COMPACT_MODE = True + view.reloadConfigWindowSettingBoxContainer() + +def callbackDisableConfigWindowCompactMode(): + config.IS_CONFIG_WINDOW_COMPACT_MODE = False + view.reloadConfigWindowSettingBoxContainer() + # create GUI view.createGUI() @@ -257,6 +267,11 @@ view.initializer( # }, entry_message_box_bind_Return=messageBoxPressKeyEnter, entry_message_box_bind_Any_KeyPress=messageBoxPressKeyAny, + + config_window={ + "callback_disable_config_window_compact_mode": callbackEnableConfigWindowCompactMode, + "callback_enable_config_window_compact_mode": callbackDisableConfigWindowCompactMode, + }, ) if __name__ == "__main__": diff --git a/view.py b/view.py index 0f7c2590..905c936f 100644 --- a/view.py +++ b/view.py @@ -8,7 +8,7 @@ class View(): pass - def initializer(self, sidebar_features, language_presets, entry_message_box, entry_message_box_bind_Return, entry_message_box_bind_Any_KeyPress): + def initializer(self, sidebar_features, language_presets, entry_message_box, entry_message_box_bind_Return, entry_message_box_bind_Any_KeyPress, config_window): vrct_gui.CALLBACK_TOGGLE_TRANSLATION = sidebar_features["callback_toggle_translation"] vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features["callback_toggle_transcription_send"] @@ -39,6 +39,14 @@ class View(): entry_message_box.bind("", self._foregroundOnForcefully) + vrct_gui.config_window.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window["callback_disable_config_window_compact_mode"] + vrct_gui.config_window.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window["callback_enable_config_window_compact_mode"] + + + # Config Window + vrct_gui.config_window.settings.IS_CONFIG_WINDOW_COMPACT_MODE = config.IS_CONFIG_WINDOW_COMPACT_MODE + + def setMainWindowAllWidgetsStatusToNormal(self): vrct_gui.changeMainWindowWidgetsStatus("normal", "All") @@ -148,4 +156,10 @@ class View(): def startMainLoop(self): vrct_gui.startMainLoop() + + # Config Window + def reloadConfigWindowSettingBoxContainer(self): + vrct_gui.config_window.settings.IS_CONFIG_WINDOW_COMPACT_MODE = config.IS_CONFIG_WINDOW_COMPACT_MODE + vrct_gui.config_window.reloadConfigWindowSettingBoxContainer() + view = View() \ No newline at end of file diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 40f4c3bc..3143fc19 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -17,6 +17,7 @@ class ConfigWindow(CTkToplevel): self.configure(fg_color="#ff7f50") self.protocol("WM_DELETE_WINDOW", vrct_gui.closeConfigWindow) + self.settings = settings @@ -26,3 +27,10 @@ class ConfigWindow(CTkToplevel): createSideMenuAndSettingsBoxContainers(config_window=self, settings=settings) + + + + + def reloadConfigWindowSettingBoxContainer(self): + self.main_bg_container.destroy() + createSideMenuAndSettingsBoxContainers(config_window=self, settings=self.settings) \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py index ce98730a..0d9e21ed 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py @@ -2,6 +2,17 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkSwitch def _createSettingBoxCompactModeButton(parent_widget, config_window, settings): + def switchConfigWindowCompactMode(): + print(config_window.setting_box_compact_mode_switch_box.get()) + if config_window.setting_box_compact_mode_switch_box.get() is True: + if callable(config_window.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE) is True: + config_window.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE() + else: + if callable(config_window.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE) is True: + config_window.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE() + + + config_window.setting_box_compact_mode_button_container = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) config_window.setting_box_compact_mode_button_container.grid(row=0, column=1, padx=(0, 20), sticky="nsw") @@ -44,7 +55,7 @@ def _createSettingBoxCompactModeButton(parent_widget, config_window, settings): switch_height=16, onvalue=True, offvalue=False, - # command=command, + command=switchConfigWindowCompactMode, # fg_color="", # bg_color="red", progress_color=settings.ctm.SB__SWITCH_BOX_ACTIVE_BG_COLOR, # SB__SWITCH_BOX_ACTIVE_BG_COLOR is for SB. change it later. diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index dad8a8a6..4f6dbe32 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -106,11 +106,11 @@ class VRCT_GUI(CTk): ) def setDefaultActiveLanguagePresetTab(self, tab_no:str): - vrct_gui.current_active_preset_tab = getattr(self, f"sqls__presets_button_{tab_no}") + self.current_active_preset_tab = getattr(self, f"sqls__presets_button_{tab_no}") _setDefaultActiveTab( - active_tab_widget=vrct_gui.current_active_preset_tab, - active_bg_color=vrct_gui.settings.main.ctm.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR, - active_text_color=vrct_gui.settings.main.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR + active_tab_widget=self.current_active_preset_tab, + active_bg_color=self.settings.main.ctm.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR, + active_text_color=self.settings.main.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR ) From c3459c1ffe2828e5ab6dc6292e9348d65668ec34 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 31 Aug 2023 21:00:30 +0900 Subject: [PATCH 058/355] =?UTF-8?q?[Bugfix]=20Config=20Window=E3=82=92Comp?= =?UTF-8?q?act=20mode=E3=81=AB=E3=81=99=E3=82=8B=E3=81=8B=E3=81=A9?= =?UTF-8?q?=E3=81=86=E3=81=8B=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=82=92config.js?= =?UTF-8?q?on=E3=81=AB=E4=BF=9D=E5=AD=98=E3=81=97=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=82=8B=E3=81=AB=E3=82=82=E9=96=A2=E3=82=8F=E3=82=89=E3=81=9A?= =?UTF-8?q?=E3=80=81GUI=E8=B5=B7=E5=8B=95=E6=99=82=E3=81=AB=E5=8F=8D?= =?UTF-8?q?=E6=98=A0=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E3=81=AE=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 12 +++++++++--- vrct_gui/vrct_gui.py | 31 +++++++++++++++---------------- 2 files changed, 24 insertions(+), 19 deletions(-) diff --git a/view.py b/view.py index 905c936f..4fd3d85e 100644 --- a/view.py +++ b/view.py @@ -1,3 +1,5 @@ +from types import SimpleNamespace + from customtkinter import StringVar, END as CTK_END from vrct_gui import vrct_gui @@ -5,6 +7,11 @@ from config import config class View(): def __init__(self): + self.settings = SimpleNamespace() + self.settings.config_window = SimpleNamespace() + self.settings.config_window = SimpleNamespace( + is_config_window_compact_mode=config.IS_CONFIG_WINDOW_COMPACT_MODE + ) pass @@ -43,8 +50,7 @@ class View(): vrct_gui.config_window.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window["callback_enable_config_window_compact_mode"] - # Config Window - vrct_gui.config_window.settings.IS_CONFIG_WINDOW_COMPACT_MODE = config.IS_CONFIG_WINDOW_COMPACT_MODE + @@ -151,7 +157,7 @@ class View(): def createGUI(self): - vrct_gui.createGUI() + vrct_gui.createGUI(settings=self.settings) def startMainLoop(self): vrct_gui.startMainLoop() diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 4f6dbe32..87e0eb9a 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -18,6 +18,19 @@ from config import config class VRCT_GUI(CTk): def __init__(self): super().__init__() + self.YOUR_LANGUAGE = "Japanese\n(Japan)" + self.TARGET_LANGUAGE = "English\n(United States)" + + self.CALLBACK_TOGGLE_TRANSLATION = None + self.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = None + self.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = None + self.CALLBACK_TOGGLE_FOREGROUND = None + self.CALLBACK_SELECTED_TAB_NO_1 = None + self.CALLBACK_SELECTED_TAB_NO_2 = None + self.CALLBACK_SELECTED_TAB_NO_3 = None + + + def createGUI(self, settings): self.settings = SimpleNamespace() theme = get_appearance_mode() if config.APPEARANCE_THEME == "System" else config.APPEARANCE_THEME all_ctm = ColorThemeManager(theme) @@ -40,28 +53,14 @@ class VRCT_GUI(CTk): self.settings.config_window = SimpleNamespace( ctm=all_ctm.config_window, uism=all_uism.config_window, - IS_CONFIG_WINDOW_COMPACT_MODE=False, + IS_CONFIG_WINDOW_COMPACT_MODE=settings.config_window.is_config_window_compact_mode, **common_args ) - - self.YOUR_LANGUAGE = "Japanese\n(Japan)" - self.TARGET_LANGUAGE = "English\n(United States)" - - self.CALLBACK_TOGGLE_TRANSLATION = None - self.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = None - self.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = None - self.CALLBACK_TOGGLE_FOREGROUND = None - self.CALLBACK_SELECTED_TAB_NO_1 = None - self.CALLBACK_SELECTED_TAB_NO_2 = None - self.CALLBACK_SELECTED_TAB_NO_3 = None - + createMainWindowWidgets(vrct_gui=self, settings=self.settings.main) self.config_window = ConfigWindow(vrct_gui=self, settings=self.settings.config_window) # self.information_window = ToplevelWindowInformation(self) - def createGUI(self): - createMainWindowWidgets(vrct_gui=self, settings=self.settings.main) - def startMainLoop(self): self.mainloop() From fac28edba6e1f92f7a51f4c157d0d8cc9533dc66 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 31 Aug 2023 21:44:53 +0900 Subject: [PATCH 059/355] =?UTF-8?q?GUI=E3=81=8C=E7=94=9F=E6=88=90=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=82=8B=E5=89=8D=E3=81=AB=E3=81=97=E3=81=9F=E3=81=84?= =?UTF-8?q?=E5=87=A6=E7=90=86(=E8=89=B2=E3=82=84=E3=82=B9=E3=82=B1?= =?UTF-8?q?=E3=83=BC=E3=83=AB=E3=81=AA=E3=81=A9)=E3=82=92vrct=5Fgui.py?= =?UTF-8?q?=E3=81=8B=E3=82=89view.py=E3=81=AB=E7=A7=BB=E5=8B=95=E3=80=82?= =?UTF-8?q?=E4=BB=8A=E5=BE=8C=E3=82=B3=E3=83=B3=E3=83=88=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=A9=E5=81=B4=E3=81=A7=E5=80=A4=E3=82=92=E5=A4=89=E6=9B=B4?= =?UTF-8?q?=E3=81=97=E3=81=A6GUI=E5=86=8D=E7=94=9F=E6=88=90=E3=81=AA?= =?UTF-8?q?=E3=81=A9=E3=81=97=E3=82=84=E3=81=99=E3=81=8F=E3=81=AA=E3=82=8B?= =?UTF-8?q?=E3=81=AF=E3=81=9A=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 2 +- view.py | 32 ++++++++++++++++++++++++++------ vrct_gui/vrct_gui.py | 29 +++-------------------------- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/main.py b/main.py index b6bf26b2..807cf48f 100644 --- a/main.py +++ b/main.py @@ -239,7 +239,7 @@ model.checkOSCStarted() model.checkSoftwareUpdated() # set UI and callback -view.initializer( +view.register( sidebar_features={ "callback_toggle_translation": callbackToggleTranslation, "callback_toggle_transcription_send": callbackToggleTranscriptionSend, diff --git a/view.py b/view.py index 4fd3d85e..696d597c 100644 --- a/view.py +++ b/view.py @@ -1,6 +1,7 @@ from types import SimpleNamespace -from customtkinter import StringVar, END as CTK_END +from customtkinter import StringVar, END as CTK_END, get_appearance_mode +from vrct_gui.ui_managers import ColorThemeManager, ImageFilenameManager, UiScalingManager from vrct_gui import vrct_gui from config import config @@ -8,14 +9,33 @@ from config import config class View(): def __init__(self): self.settings = SimpleNamespace() - self.settings.config_window = SimpleNamespace() - self.settings.config_window = SimpleNamespace( - is_config_window_compact_mode=config.IS_CONFIG_WINDOW_COMPACT_MODE + theme = get_appearance_mode() if config.APPEARANCE_THEME == "System" else config.APPEARANCE_THEME + all_ctm = ColorThemeManager(theme) + all_uism = UiScalingManager(config.UI_SCALING) + image_filename = ImageFilenameManager(theme) + + common_args = { + "image_filename": image_filename, + "FONT_FAMILY": config.FONT_FAMILY, + } + + self.settings.main = SimpleNamespace( + ctm=all_ctm.main, + uism=all_uism.main, + IS_SIDEBAR_COMPACT_MODE=False, + COMPACT_MODE_ICON_SIZE=0, + **common_args + ) + + self.settings.config_window = SimpleNamespace( + ctm=all_ctm.config_window, + uism=all_uism.config_window, + IS_CONFIG_WINDOW_COMPACT_MODE=config.IS_CONFIG_WINDOW_COMPACT_MODE, + **common_args ) - pass - def initializer(self, sidebar_features, language_presets, entry_message_box, entry_message_box_bind_Return, entry_message_box_bind_Any_KeyPress, config_window): + def register(self, sidebar_features, language_presets, entry_message_box, entry_message_box_bind_Return, entry_message_box_bind_Any_KeyPress, config_window): vrct_gui.CALLBACK_TOGGLE_TRANSLATION = sidebar_features["callback_toggle_translation"] vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features["callback_toggle_transcription_send"] diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 87e0eb9a..8a4701a0 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -4,7 +4,7 @@ from customtkinter import CTk, get_appearance_mode # from window_help_and_info import ToplevelWindowInformation -from .ui_managers import ColorThemeManager, ImageFilenameManager, UiScalingManager +# from .ui_managers import ColorThemeManager, ImageFilenameManager, UiScalingManager from ._changeMainWindowWidgetsStatus import _changeMainWindowWidgetsStatus from ._printToTextbox import _printToTextbox @@ -18,6 +18,7 @@ from config import config class VRCT_GUI(CTk): def __init__(self): super().__init__() + self.settings = SimpleNamespace() self.YOUR_LANGUAGE = "Japanese\n(Japan)" self.TARGET_LANGUAGE = "English\n(United States)" @@ -31,31 +32,7 @@ class VRCT_GUI(CTk): def createGUI(self, settings): - self.settings = SimpleNamespace() - theme = get_appearance_mode() if config.APPEARANCE_THEME == "System" else config.APPEARANCE_THEME - all_ctm = ColorThemeManager(theme) - all_uism = UiScalingManager(config.UI_SCALING) - image_filename = ImageFilenameManager(theme) - - common_args = { - "image_filename": image_filename, - "FONT_FAMILY": config.FONT_FAMILY, - } - - self.settings.main = SimpleNamespace( - ctm=all_ctm.main, - uism=all_uism.main, - IS_SIDEBAR_COMPACT_MODE=False, - COMPACT_MODE_ICON_SIZE=0, - **common_args - ) - - self.settings.config_window = SimpleNamespace( - ctm=all_ctm.config_window, - uism=all_uism.config_window, - IS_CONFIG_WINDOW_COMPACT_MODE=settings.config_window.is_config_window_compact_mode, - **common_args - ) + self.settings = settings createMainWindowWidgets(vrct_gui=self, settings=self.settings.main) self.config_window = ConfigWindow(vrct_gui=self, settings=self.settings.config_window) From 4db1acc455d2cdceeb5b77d69e2a151dea88b656 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 31 Aug 2023 22:09:43 +0900 Subject: [PATCH 060/355] =?UTF-8?q?[Chore]=20printToTextBox=E5=86=85?= =?UTF-8?q?=E3=81=A7config.py=E3=82=92=E4=BD=BF=E3=81=A3=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=81=8C=E3=80=81=E3=81=9D=E3=82=82=E3=81=9D=E3=82=82?= =?UTF-8?q?config.py=E3=81=AFGUI=E4=BB=A5=E4=B8=8B=E3=81=A7=E3=81=AF?= =?UTF-8?q?=E4=BD=BF=E3=82=8F=E3=81=AA=E3=81=84=E3=81=A8=E3=81=AE=E3=80=81?= =?UTF-8?q?=E7=89=B9=E3=81=AB=E3=82=B3=E3=83=BC=E3=83=89=E3=81=AE=E7=A7=BB?= =?UTF-8?q?=E5=8B=95=E3=82=82=E5=BF=85=E8=A6=81=E3=81=AA=E3=81=8F=E6=9B=B8?= =?UTF-8?q?=E3=81=8D=E6=8F=9B=E3=81=88=E3=81=A0=E3=81=91=E3=81=A7=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_printToTextbox.py | 10 ++++------ vrct_gui/vrct_gui.py | 4 +--- 2 files changed, 5 insertions(+), 9 deletions(-) diff --git a/vrct_gui/_printToTextbox.py b/vrct_gui/_printToTextbox.py index c0a696c7..88bcc87b 100644 --- a/vrct_gui/_printToTextbox.py +++ b/vrct_gui/_printToTextbox.py @@ -1,7 +1,5 @@ from datetime import datetime from customtkinter import CTkFont -from config import config - def _printToTextbox(settings, target_textbox, original_message, translated_message, tags=None): now_raw_data = datetime.now() @@ -23,10 +21,10 @@ def _printToTextbox(settings, target_textbox, original_message, translated_messa target_textbox._textbox.tag_configure("START", spacing1=10) - target_textbox._textbox.tag_configure("LABEL", font=CTkFont(family=config.FONT_FAMILY, size=12, weight="normal")) - target_textbox._textbox.tag_configure("TIMESTAMP", font=CTkFont(family=config.FONT_FAMILY, size=12, weight="normal")) - target_textbox._textbox.tag_configure("ORIGINAL_MESSAGE", font=CTkFont(family=config.FONT_FAMILY, size=12, weight="normal")) - target_textbox._textbox.tag_configure("TRANSLATED_MESSAGE", font=CTkFont(family=config.FONT_FAMILY, size=16, weight="normal")) + target_textbox._textbox.tag_configure("LABEL", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) + target_textbox._textbox.tag_configure("TIMESTAMP", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) + target_textbox._textbox.tag_configure("ORIGINAL_MESSAGE", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) + target_textbox._textbox.tag_configure("TRANSLATED_MESSAGE", font=CTkFont(family=settings.FONT_FAMILY, size=16, weight="normal")) target_textbox.configure(state='normal') target_textbox.insert("end", f"[{tags}] ", ("START", "LABEL", tags, f"{tags}_COLOR")) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 8a4701a0..7468b3b9 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -1,6 +1,6 @@ from types import SimpleNamespace -from customtkinter import CTk, get_appearance_mode +from customtkinter import CTk # from window_help_and_info import ToplevelWindowInformation @@ -12,8 +12,6 @@ from .main_window import createMainWindowWidgets from .config_window import ConfigWindow from .ui_utils import _setDefaultActiveTab -from config import config - class VRCT_GUI(CTk): def __init__(self): From 250be654bdbf9ba15a02650c02beede5d9a91098 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 31 Aug 2023 23:25:31 +0900 Subject: [PATCH 061/355] =?UTF-8?q?[bugfix]=20=E5=A4=89=E6=95=B0=E5=90=8D?= =?UTF-8?q?=E3=81=AE=E8=A2=AB=E3=82=8A=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 14 +++++++------- view.py | 8 +++----- 2 files changed, 10 insertions(+), 12 deletions(-) diff --git a/main.py b/main.py index b6bf26b2..66b57119 100644 --- a/main.py +++ b/main.py @@ -260,13 +260,13 @@ view.initializer( # 辞書型で関数を渡しても上手く行かず、仕方なくタプルで渡してる。 # 本当はコメントアウト(以下とview.py内33,34行目)しているようにできたらいいけど、 # _tkinter.TclError: unknown option "-bind_Any_KeyPress"みたいにエラーがでる。 - entry_message_box=None, - # entry_message_box={ - # "bind_Return": messageBoxPressKeyEnter, - # "bind_Any_KeyPress": messageBoxPressKeyAny, - # }, - entry_message_box_bind_Return=messageBoxPressKeyEnter, - entry_message_box_bind_Any_KeyPress=messageBoxPressKeyAny, + # entry_message_box=None, + entry_message_box_commands={ + "bind_Return": messageBoxPressKeyEnter, + "bind_Any_KeyPress": messageBoxPressKeyAny, + }, + # entry_message_box_bind_Return=messageBoxPressKeyEnter, + # entry_message_box_bind_Any_KeyPress=messageBoxPressKeyAny, config_window={ "callback_disable_config_window_compact_mode": callbackEnableConfigWindowCompactMode, diff --git a/view.py b/view.py index 4fd3d85e..62c065c7 100644 --- a/view.py +++ b/view.py @@ -15,7 +15,7 @@ class View(): pass - def initializer(self, sidebar_features, language_presets, entry_message_box, entry_message_box_bind_Return, entry_message_box_bind_Any_KeyPress, config_window): + def initializer(self, sidebar_features, language_presets, entry_message_box_commands, config_window): vrct_gui.CALLBACK_TOGGLE_TRANSLATION = sidebar_features["callback_toggle_translation"] vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features["callback_toggle_transcription_send"] @@ -37,10 +37,8 @@ class View(): entry_message_box = getattr(vrct_gui, "entry_message_box") - # entry_message_box.bind("", lambda e: entry_message_box["bind_Return"](e)) - # entry_message_box.bind("", lambda e: entry_message_box["bind_Any_KeyPress"](e)) - entry_message_box.bind("", entry_message_box_bind_Return) - entry_message_box.bind("", entry_message_box_bind_Any_KeyPress) + entry_message_box.bind("", entry_message_box_commands["bind_Return"]) + entry_message_box.bind("", entry_message_box_commands["bind_Any_KeyPress"]) entry_message_box.bind("", self._foregroundOffForcefully) entry_message_box.bind("", self._foregroundOnForcefully) From ed88a5b886ba454fc80c6de3f98a298f6281c543 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 31 Aug 2023 23:38:07 +0900 Subject: [PATCH 062/355] =?UTF-8?q?[Chore]=20messagebox=E3=81=AE=E3=83=90?= =?UTF-8?q?=E3=82=B0=E3=82=92=E7=9B=B4=E3=81=97=E3=81=A6=E3=82=82=E3=82=89?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E3=81=AE=E3=81=A7=E8=A6=81=E3=82=89=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/main.py b/main.py index 30673619..d6f7b199 100644 --- a/main.py +++ b/main.py @@ -257,16 +257,10 @@ view.register( "callback_selected_tab_no_3": callbackSelectedTabNo3, }, - # 辞書型で関数を渡しても上手く行かず、仕方なくタプルで渡してる。 - # 本当はコメントアウト(以下とview.py内33,34行目)しているようにできたらいいけど、 - # _tkinter.TclError: unknown option "-bind_Any_KeyPress"みたいにエラーがでる。 - # entry_message_box=None, entry_message_box_commands={ "bind_Return": messageBoxPressKeyEnter, "bind_Any_KeyPress": messageBoxPressKeyAny, }, - # entry_message_box_bind_Return=messageBoxPressKeyEnter, - # entry_message_box_bind_Any_KeyPress=messageBoxPressKeyAny, config_window={ "callback_disable_config_window_compact_mode": callbackEnableConfigWindowCompactMode, From 799a1a27bb378b6022a779c954cc75f31fb2e5ae Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 1 Sep 2023 02:25:00 +0900 Subject: [PATCH 063/355] =?UTF-8?q?Config=20Window=E3=81=AE=E5=90=84?= =?UTF-8?q?=E9=A0=85=E7=9B=AECallback=E9=96=A2=E6=95=B0=E3=82=92main.py?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=88=E3=83=AD=E3=83=BC=E3=83=A9=E3=81=A7?= =?UTF-8?q?=E5=AE=9F=E8=A1=8C=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 168 +++++++++++++++++- utils.py | 5 +- view.py | 39 ++++ vrct_gui/config_window/ConfigWindow.py | 38 ++++ .../createSettingBox_AdvancedSettings.py | 5 +- .../createSettingBox_Appearance.py | 21 +-- .../createSettingBox_Others.py | 10 +- .../createSettingBox_Mic.py | 30 +--- .../createSettingBox_Speaker.py | 17 +- .../createSettingBox_Translation.py | 10 +- 10 files changed, 282 insertions(+), 61 deletions(-) diff --git a/main.py b/main.py index d6f7b199..b455bf17 100644 --- a/main.py +++ b/main.py @@ -213,6 +213,7 @@ def callbackToggleForeground(): # Config Window +# Compact Mode Switch def callbackEnableConfigWindowCompactMode(): config.IS_CONFIG_WINDOW_COMPACT_MODE = True view.reloadConfigWindowSettingBoxContainer() @@ -221,6 +222,133 @@ def callbackDisableConfigWindowCompactMode(): config.IS_CONFIG_WINDOW_COMPACT_MODE = False view.reloadConfigWindowSettingBoxContainer() +# Appearance Tab +def callbackSetTransparency(value): + print(int(value)) + # self.parent.wm_attributes("-alpha", int(value/100)) + config.TRANSPARENCY = int(value) + +def callbackSetAppearance(value): + print(value) + config.APPEARANCE_THEME = value + +def callbackSetUiScaling(value): + print(value) + config.UI_SCALING = value + new_scaling_float = int(value.replace("%", "")) / 100 + +def callbackSetFontFamily(value): + print(value) + config.FONT_FAMILY = value + +def callbackSetUiLanguage(value): + print(value) + config.UI_LANGUAGE = value + +# Translation Tab +def callbackSetDeeplAuthkey(value): + print(str(value)) + # config.AUTH_KEYS["DeepL(auth)"] = str(value) + # if len(value) > 0: + # if model.authenticationTranslator(choice_translator="DeepL(auth)", auth_key=value) is True: + # print_textbox(self.parent.textbox_message_log, "Auth key update completed", "INFO") + # print_textbox(self.parent.textbox_message_system_log, "Auth key update completed", "INFO") + # else: + # pass + +# Transcription Tab (Mic) +def callbackSetMicHost(value): + print(value) + config.CHOICE_MIC_HOST = value + +def callbackSetMicDevice(value): + print(value) + config.CHOICE_MIC_DEVICE = value + +def callbackSetMicEnergyThreshold(value): + print(int(value)) + config.INPUT_MIC_ENERGY_THRESHOLD = int(value) + +def callbackSetMicDynamicEnergyThreshold(value): + print(value) + config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value + +def callbackSetMicRecordTimeout(value): + print(int(value)) + config.INPUT_MIC_RECORD_TIMEOUT = int(value) + +def callbackSetMicPhraseTimeout(value): + print(int(value)) + config.INPUT_MIC_PHRASE_TIMEOUT = int(value) + +def callbackSetMicMaxPhrases(value): + print(int(value)) + config.INPUT_MIC_MAX_PHRASES = int(value) + +def callbackSetMicWordFilter(value): + word_filter = str(value) + word_filter = [w.strip() for w in word_filter.split(",") if len(w.strip()) > 0] + word_filter = ",".join(word_filter) + print(word_filter) + if len(word_filter) > 0: + config.INPUT_MIC_WORD_FILTER = word_filter.split(",") + else: + config.INPUT_MIC_WORD_FILTER = [] + # model.resetKeywordProcessor() + # model.addKeywords() + +# Transcription Tab (Speaker) +def callbackSetSpeakerDevice(value): + print(value) + config.CHOICE_SPEAKER_DEVICE = value + +def callbackSetSpeakerEnergyThreshold(value): + print(int(value)) + config.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) + +def callbackSetSpeakerDynamicEnergyThreshold(value): + print(value) + config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value + +def callbackSetSpeakerRecordTimeout(value): + print(int(value)) + config.INPUT_SPEAKER_RECORD_TIMEOUT = int(value) + +def callbackSetSpeakerPhraseTimeout(value): + print(int(value)) + config.INPUT_SPEAKER_PHRASE_TIMEOUT = int(value) + +def callbackSetSpeakerMaxPhrases(value): + print(int(value)) + config.INPUT_SPEAKER_MAX_PHRASES = int(value) + + +# Others Tab +def callbackSetEnableAutoClearChatbox(value): + print(value) + config.ENABLE_AUTO_CLEAR_CHATBOX = value + +def callbackSetEnableNoticeXsoverlay(value): + print(value) + config.ENABLE_NOTICE_XSOVERLAY = value + +def callbackSetMessageFormat(value): + print(value) + if len(value) > 0: + config.MESSAGE_FORMAT = value + + +# Advanced Settings Tab +def callbackSetOscIpAddress(value): + print(value) + config.OSC_IP_ADDRESS = value + +def callbackSetOscPort(value): + print(int(value)) + config.OSC_PORT = int(value) + + + # create GUI view.createGUI() @@ -233,7 +361,7 @@ if model.authenticationTranslator() is False: model.addKeywords() # check OSC started -model.checkOSCStarted() +# model.checkOSCStarted() # check Software Updated model.checkSoftwareUpdated() @@ -263,8 +391,46 @@ view.register( }, config_window={ + # Compact Mode Switch "callback_disable_config_window_compact_mode": callbackEnableConfigWindowCompactMode, "callback_enable_config_window_compact_mode": callbackDisableConfigWindowCompactMode, + + # Appearance Tab + "callback_set_transparency": callbackSetTransparency, + "callback_set_appearance": callbackSetAppearance, + "callback_set_ui_scaling": callbackSetUiScaling, + "callback_set_font_family": callbackSetFontFamily, + "callback_set_ui_language": callbackSetUiLanguage, + + # Translation Tab + "callback_set_deepl_authkey": callbackSetDeeplAuthkey, + + # Transcription Tab (Mic) + "callback_set_mic_host": callbackSetMicHost, + "callback_set_mic_device": callbackSetMicDevice, + "callback_set_mic_energy_threshold": callbackSetMicEnergyThreshold, + "callback_set_mic_dynamic_energy_threshold": callbackSetMicDynamicEnergyThreshold, + "callback_set_mic_record_timeout": callbackSetMicRecordTimeout, + "callback_set_mic_phrase_timeout": callbackSetMicPhraseTimeout, + "callback_set_mic_max_phrases": callbackSetMicMaxPhrases, + "callback_set_mic_word_filter": callbackSetMicWordFilter, + + # Transcription Tab (Speaker) + "callback_set_speaker_device": callbackSetSpeakerDevice, + "callback_set_speaker_energy_threshold": callbackSetSpeakerEnergyThreshold, + "callback_set_speaker_dynamic_energy_threshold": callbackSetSpeakerDynamicEnergyThreshold, + "callback_set_speaker_record_timeout": callbackSetSpeakerRecordTimeout, + "callback_set_speaker_phrase_timeout": callbackSetSpeakerPhraseTimeout, + "callback_set_speaker_max_phrases": callbackSetSpeakerMaxPhrases, + + # Others Tab + "callback_set_enable_auto_clear_chatbox": callbackSetEnableAutoClearChatbox, + "callback_set_enable_notice_xsoverlay": callbackSetEnableNoticeXsoverlay, + "callback_set_message_format": callbackSetMessageFormat, + + # Advanced Settings Tab + "callback_set_osc_ip_address": callbackSetOscIpAddress, + "callback_set_osc_port": callbackSetOscPort, }, ) diff --git a/utils.py b/utils.py index a68474fb..d15b169c 100644 --- a/utils.py +++ b/utils.py @@ -9,4 +9,7 @@ def get_key_by_value(dictionary, value): for key, val in dictionary.items(): if val == value: return key - return None \ No newline at end of file + return None + +def callFunctionIfCallable(function, *args): + if callable(function) is True: function(*args) \ No newline at end of file diff --git a/view.py b/view.py index 3868fde7..f23adffa 100644 --- a/view.py +++ b/view.py @@ -64,10 +64,49 @@ class View(): entry_message_box.bind("", self._foregroundOnForcefully) + # Config Window + # Compact Mode Switch vrct_gui.config_window.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window["callback_disable_config_window_compact_mode"] vrct_gui.config_window.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window["callback_enable_config_window_compact_mode"] + # Appearance Tab + vrct_gui.config_window.CALLBACK_SET_TRANSPARENCY = config_window["callback_set_transparency"] + vrct_gui.config_window.CALLBACK_SET_APPEARANCE = config_window["callback_set_appearance"] + vrct_gui.config_window.CALLBACK_SET_UI_SCALING = config_window["callback_set_ui_scaling"] + vrct_gui.config_window.CALLBACK_SET_FONT_FAMILY = config_window["callback_set_font_family"] + vrct_gui.config_window.CALLBACK_SET_UI_LANGUAGE = config_window["callback_set_ui_language"] + + + # Translation Tab + vrct_gui.config_window.CALLBACK_SET_DEEPL_AUTHKEY = config_window["callback_set_deepl_authkey"] + + # Transcription Tab (Mic) + vrct_gui.config_window.CALLBACK_SET_MIC_HOST = config_window["callback_set_mic_host"] + vrct_gui.config_window.CALLBACK_SET_MIC_DEVICE = config_window["callback_set_mic_device"] + vrct_gui.config_window.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window["callback_set_mic_energy_threshold"] + vrct_gui.config_window.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_mic_dynamic_energy_threshold"] + vrct_gui.config_window.CALLBACK_SET_MIC_RECORD_TIMEOUT = config_window["callback_set_mic_record_timeout"] + vrct_gui.config_window.CALLBACK_SET_MIC_PHRASE_TIMEOUT = config_window["callback_set_mic_phrase_timeout"] + vrct_gui.config_window.CALLBACK_SET_MIC_MAX_PHRASES = config_window["callback_set_mic_max_phrases"] + vrct_gui.config_window.CALLBACK_SET_MIC_WORD_FILTER = config_window["callback_set_mic_word_filter"] + + # Transcription Tab (Speaker) + vrct_gui.config_window.CALLBACK_SET_SPEAKER_DEVICE = config_window["callback_set_speaker_device"] + vrct_gui.config_window.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window["callback_set_speaker_energy_threshold"] + vrct_gui.config_window.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_speaker_dynamic_energy_threshold"] + vrct_gui.config_window.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = config_window["callback_set_speaker_record_timeout"] + vrct_gui.config_window.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = config_window["callback_set_speaker_phrase_timeout"] + vrct_gui.config_window.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window["callback_set_speaker_max_phrases"] + + # Others Tab + vrct_gui.config_window.CALLBACK_SET_ENABLE_AUTO_CLEAR_CHATBOX = config_window["callback_set_enable_auto_clear_chatbox"] + vrct_gui.config_window.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window["callback_set_enable_notice_xsoverlay"] + vrct_gui.config_window.CALLBACK_SET_MESSAGE_FORMAT = config_window["callback_set_message_format"] + + # Advanced Settings Tab + vrct_gui.config_window.CALLBACK_SET_OSC_IP_ADDRESS = config_window["callback_set_osc_ip_address"] + vrct_gui.config_window.CALLBACK_SET_OSC_PORT = config_window["callback_set_osc_port"] diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 3143fc19..64dda3cf 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -20,6 +20,44 @@ class ConfigWindow(CTkToplevel): self.settings = settings + # Appearance Tab + self.CALLBACK_SET_TRANSPARENCY = None + self.CALLBACK_SET_APPEARANCE = None + self.CALLBACK_SET_UI_SCALING = None + self.CALLBACK_SET_FONT_FAMILY = None + self.CALLBACK_SET_UI_LANGUAGE = None + + # Translation Tab + self.CALLBACK_SET_DEEPL_AUTHKEY = None + + # Transcription Tab (Mic) + self.CALLBACK_SET_MIC_HOST = None + self.CALLBACK_SET_MIC_DEVICE = None + self.CALLBACK_SET_MIC_ENERGY_THRESHOLD = None + self.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = None + self.CALLBACK_SET_MIC_RECORD_TIMEOUT = None + self.CALLBACK_SET_MIC_PHRASE_TIMEOUT = None + self.CALLBACK_SET_MIC_MAX_PHRASES = None + self.CALLBACK_SET_MIC_WORD_FILTER = None + + # Transcription Tab (Speaker) + self.CALLBACK_SET_SPEAKER_DEVICE = None + self.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = None + self.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = None + self.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = None + self.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = None + self.CALLBACK_SET_SPEAKER_MAX_PHRASES = None + + # Others Tab + self.CALLBACK_SET_ENABLE_AUTO_CLEAR_CHATBOX = None + self.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = None + self.CALLBACK_SET_MESSAGE_FORMAT = None + + # Advanced Settings Tab + self.CALLBACK_SET_OSC_IP_ADDRESS = None + self.CALLBACK_SET_OSC_PORT = None + + createConfigWindowTitle(config_window=self, settings=settings) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py index b28b75a4..32a01e06 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py @@ -2,6 +2,7 @@ from time import sleep from customtkinter import StringVar, IntVar +from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator @@ -13,10 +14,10 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin def entry_ip_address_callback(value): - config.OSC_IP_ADDRESS = str(value) + callFunctionIfCallable(config_window.CALLBACK_SET_OSC_IP_ADDRESS, value) def entry_port_callback(value): - config.OSC_PORT = int(value) + callFunctionIfCallable(config_window.CALLBACK_SET_OSC_PORT, value) row=0 config_window.sb__ip_address = createSettingBoxEntry( diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index 77bdb10c..e8dda661 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -3,7 +3,7 @@ from time import sleep from customtkinter import StringVar, IntVar from tkinter import font as tk_font from languages import selectable_languages -from utils import get_key_by_value +from utils import get_key_by_value, callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator @@ -15,28 +15,25 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxSlider = sbg.createSettingBoxSlider - # 関数名は変えるかもしれない。 - # テーマ変更、フォント変更時、 Widget再生成か再起動かは検討中 - - + # テーマ変更、フォント変更時、 Widget再生成か再起動かは検討中\ def slider_transparency_callback(value): - # self.parent.wm_attributes("-alpha", int(value/100)) - config.TRANSPARENCY = int(value) + callFunctionIfCallable(config_window.CALLBACK_SET_TRANSPARENCY, value) def optionmenu_appearance_theme_callback(value): - config.APPEARANCE_THEME = value + callFunctionIfCallable(config_window.CALLBACK_SET_APPEARANCE, value) def optionmenu_ui_scaling_callback(value): + callFunctionIfCallable(config_window.CALLBACK_SET_UI_SCALING, value) # self.optionmenu_ui_scaling.set(choice) - # new_scaling_float = int(choice.replace("%", "")) / 100 - config.UI_SCALING = value def optionmenu_font_family_callback(value): - config.FONT_FAMILY = value + callFunctionIfCallable(config_window.CALLBACK_SET_FONT_FAMILY, value) def optionmenu_ui_language_callback(value): - config.UI_LANGUAGE = get_key_by_value(selectable_languages, value) + value = get_key_by_value(selectable_languages, value) + callFunctionIfCallable(config_window.CALLBACK_SET_UI_LANGUAGE, value) + row=0 config_window.sb__transparency = createSettingBoxSlider( diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index f65858c6..a92249c7 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -2,6 +2,7 @@ from time import sleep from customtkinter import StringVar, IntVar +from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator @@ -15,16 +16,13 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings): # 関数名 chatbox から messagebox に変える予定 config.ENABLE_AUTO_CLEAR_CHATBOX も MESSAGEBOXに変えるかな。 def checkbox_auto_clear_chatbox_callback(checkbox_box_widget): - print(checkbox_box_widget.get()) - config.ENABLE_AUTO_CLEAR_CHATBOX = checkbox_box_widget.get() + callFunctionIfCallable(config_window.CALLBACK_SET_DEEPL_AUTHKEY, checkbox_box_widget.get()) def checkbox_notice_xsoverlay_callback(checkbox_box_widget): - print(checkbox_box_widget.get()) - config.ENABLE_NOTICE_XSOVERLAY = checkbox_box_widget.get() + callFunctionIfCallable(config_window.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, checkbox_box_widget.get()) def entry_message_format_callback(value): - if len(value) > 0: - config.MESSAGE_FORMAT = value + callFunctionIfCallable(config_window.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, value) row=0 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 8cc5f1d1..68b6a39d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -2,6 +2,7 @@ from time import sleep from customtkinter import StringVar, IntVar +from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator @@ -39,42 +40,29 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): passive_button_wrapper_widget.grid() def optionmenu_mic_host_callback(value): - config.CHOICE_MIC_HOST = value + callFunctionIfCallable(config_window.CALLBACK_SET_MIC_HOST, value) def optionmenu_input_mic_device_callback(value): - config.CHOICE_MIC_DEVICE = value + callFunctionIfCallable(config_window.CALLBACK_SET_MIC_DEVICE, value) def slider_input_mic_energy_threshold_callback(value): - config.INPUT_MIC_ENERGY_THRESHOLD = int(value) + callFunctionIfCallable(config_window.CALLBACK_SET_MIC_ENERGY_THRESHOLD, value) def checkbox_input_mic_dynamic_energy_threshold_callback(checkbox_box_widget): - print(checkbox_box_widget.get()) - config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = checkbox_box_widget.get() + callFunctionIfCallable(config_window.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD, checkbox_box_widget.get()) def entry_input_mic_record_timeout_callback(value): - print(int(value)) - config.INPUT_MIC_RECORD_TIMEOUT = int(value) + callFunctionIfCallable(config_window.CALLBACK_SET_MIC_RECORD_TIMEOUT, value) def entry_input_mic_phrase_timeout_callback(value): - print(int(value)) - config.INPUT_MIC_PHRASE_TIMEOUT = int(value) + callFunctionIfCallable(config_window.CALLBACK_SET_MIC_PHRASE_TIMEOUT, value) def entry_input_mic_max_phrases_callback(value): - print(int(value)) - config.INPUT_MIC_MAX_PHRASES = int(value) + callFunctionIfCallable(config_window.CALLBACK_SET_MIC_MAX_PHRASES, value) def entry_input_mic_word_filters_callback(value): - word_filter = str(value) - word_filter = [w.strip() for w in word_filter.split(",") if len(w.strip()) > 0] - word_filter = ",".join(word_filter) - print(word_filter) - if len(word_filter) > 0: - config.INPUT_MIC_WORD_FILTER = word_filter.split(",") - else: - config.INPUT_MIC_WORD_FILTER = [] - # model.resetKeywordProcessor() - # model.addKeywords() + callFunctionIfCallable(config_window.CALLBACK_SET_MIC_WORD_FILTER, value) row=0 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 9069abeb..70cf726d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -2,6 +2,7 @@ from time import sleep from customtkinter import StringVar, IntVar +from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator @@ -39,27 +40,23 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): passive_button_wrapper_widget.grid() def optionmenu_input_speaker_device_callback(value): - config.CHOICE_SPEAKER_DEVICE = value + callFunctionIfCallable(config_window.CALLBACK_SET_SPEAKER_DEVICE, value) def slider_input_speaker_energy_threshold_callback(value): - config.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) + callFunctionIfCallable(config_window.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD, value) def checkbox_input_speaker_dynamic_energy_threshold_callback(checkbox_box_widget): - print(checkbox_box_widget.get()) - config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = checkbox_box_widget.get() + callFunctionIfCallable(config_window.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, checkbox_box_widget.get()) def entry_input_speaker_record_timeout_callback(value): - print(int(value)) - config.INPUT_SPEAKER_RECORD_TIMEOUT = int(value) + callFunctionIfCallable(config_window.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT, value) def entry_input_speaker_phrase_timeout_callback(value): - print(int(value)) - config.INPUT_SPEAKER_PHRASE_TIMEOUT = int(value) + callFunctionIfCallable(config_window.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT, value) def entry_input_speaker_max_phrases_callback(value): - print(int(value)) - config.INPUT_SPEAKER_MAX_PHRASES = int(value) + callFunctionIfCallable(config_window.CALLBACK_SET_SPEAKER_MAX_PHRASES, value) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index 1bc4ba0d..974e6f33 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -2,6 +2,7 @@ from time import sleep from customtkinter import StringVar, IntVar +from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator @@ -13,14 +14,7 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings): def deepl_authkey_callback(value): - print(str(value)) - # config.AUTH_KEYS["DeepL(auth)"] = str(value) - # if len(value) > 0: - # if model.authenticationTranslator(choice_translator="DeepL(auth)", auth_key=value) is True: - # print_textbox(self.parent.textbox_message_log, "Auth key update completed", "INFO") - # print_textbox(self.parent.textbox_message_system_log, "Auth key update completed", "INFO") - # else: - # pass + callFunctionIfCallable(config_window.CALLBACK_SET_DEEPL_AUTHKEY, value) row=0 From f3f549494ef8f0e126f4625621efffe8cb9dce54 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 1 Sep 2023 03:10:43 +0900 Subject: [PATCH 064/355] =?UTF-8?q?Config=20Window:=20Transcription=20Tab?= =?UTF-8?q?=20Threshold=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF(Mic,=20Speaker?= =?UTF-8?q?)=E3=81=AECallback=E9=96=A2=E6=95=B0=E3=82=92main.py=E3=81=8B?= =?UTF-8?q?=E3=82=89=E5=91=BC=E3=81=B9=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=80=82=20=E5=90=84=E8=A8=AD=E5=AE=9A=E9=A0=85=E7=9B=AE?= =?UTF-8?q?=E3=81=8C=E3=81=A1=E3=82=83=E3=82=93=E3=81=A8main.py=E3=81=A7?= =?UTF-8?q?=E5=91=BC=E3=81=B0=E3=82=8C=E3=81=A6=E3=81=84=E3=82=8B=E3=81=8B?= =?UTF-8?q?=E7=A2=BA=E8=AA=8D=E3=81=99=E3=82=8B=E3=81=9F=E3=82=81=E3=81=AB?= =?UTF-8?q?print=E6=96=87=E8=BF=BD=E5=8A=A0=E3=80=82=E3=81=8A=E3=81=8B?= =?UTF-8?q?=E3=81=92=E3=81=A7=E6=8C=87=E5=AE=9A=E3=81=97=E5=BF=98=E3=82=8C?= =?UTF-8?q?=E3=81=AA=E3=81=A9=E3=81=8C=E5=88=86=E3=81=8B=E3=81=A3=E3=81=9F?= =?UTF-8?q?=E3=81=AE=E3=81=A7=E3=81=9D=E3=81=AE=E4=BF=AE=E6=AD=A3=E3=80=82?= =?UTF-8?q?=20=E8=B5=B7=E5=8B=95=E6=99=82OSC=E3=83=81=E3=82=A7=E3=83=83?= =?UTF-8?q?=E3=82=AF=E5=87=A6=E7=90=86=E3=81=AE=E3=82=B3=E3=83=A1=E3=83=B3?= =?UTF-8?q?=E3=83=88=E3=82=A2=E3=82=A6=E3=83=88=E6=88=BB=E3=81=99=E3=81=AE?= =?UTF-8?q?=E3=82=92=E5=BF=98=E3=82=8C=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3(=E9=96=8B=E7=99=BA=E4=B8=8A?= =?UTF-8?q?=E3=81=97=E3=81=84=E3=81=AA=E3=81=8C=E3=82=88=E3=81=8F=E3=82=84?= =?UTF-8?q?=E3=82=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 76 ++++++++++++------- view.py | 2 + vrct_gui/config_window/ConfigWindow.py | 2 + .../createSettingBox_Others.py | 2 +- .../createSettingBox_Mic.py | 8 +- .../createSettingBox_Speaker.py | 2 +- 6 files changed, 59 insertions(+), 33 deletions(-) diff --git a/main.py b/main.py index b455bf17..17eb02b8 100644 --- a/main.py +++ b/main.py @@ -224,30 +224,31 @@ def callbackDisableConfigWindowCompactMode(): # Appearance Tab def callbackSetTransparency(value): - print(int(value)) - # self.parent.wm_attributes("-alpha", int(value/100)) + print("callbackSetTransparency", int(value)) config.TRANSPARENCY = int(value) + # self.parent.wm_attributes("-alpha", int(value/100)) def callbackSetAppearance(value): - print(value) + print("callbackSetAppearance", value) config.APPEARANCE_THEME = value def callbackSetUiScaling(value): - print(value) + print("callbackSetUiScaling", value) config.UI_SCALING = value new_scaling_float = int(value.replace("%", "")) / 100 + print("callbackSetUiScaling_new_scaling_float", new_scaling_float) def callbackSetFontFamily(value): - print(value) + print("callbackSetFontFamily", value) config.FONT_FAMILY = value def callbackSetUiLanguage(value): - print(value) + print("callbackSetUiLanguage", value) config.UI_LANGUAGE = value # Translation Tab def callbackSetDeeplAuthkey(value): - print(str(value)) + print("callbackSetDeeplAuthkey", str(value)) # config.AUTH_KEYS["DeepL(auth)"] = str(value) # if len(value) > 0: # if model.authenticationTranslator(choice_translator="DeepL(auth)", auth_key=value) is True: @@ -258,38 +259,48 @@ def callbackSetDeeplAuthkey(value): # Transcription Tab (Mic) def callbackSetMicHost(value): - print(value) + print("callbackSetMicHost", value) config.CHOICE_MIC_HOST = value def callbackSetMicDevice(value): - print(value) + print("callbackSetMicDevice", value) config.CHOICE_MIC_DEVICE = value def callbackSetMicEnergyThreshold(value): - print(int(value)) + print("callbackSetMicEnergyThreshold", int(value)) config.INPUT_MIC_ENERGY_THRESHOLD = int(value) def callbackSetMicDynamicEnergyThreshold(value): - print(value) + print("callbackSetMicDynamicEnergyThreshold", value) config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value +def callbackCheckMicThreshold(is_turned_on): + print("callbackCheckMicThreshold", is_turned_on) + if is_turned_on is True: + # UIの処理あり + pass + else: + # UIの処理あり + pass + def callbackSetMicRecordTimeout(value): - print(int(value)) + print("callbackSetMicRecordTimeout", int(value)) config.INPUT_MIC_RECORD_TIMEOUT = int(value) def callbackSetMicPhraseTimeout(value): - print(int(value)) + print("callbackSetMicPhraseTimeout", int(value)) config.INPUT_MIC_PHRASE_TIMEOUT = int(value) def callbackSetMicMaxPhrases(value): - print(int(value)) + print("callbackSetMicMaxPhrases", int(value)) config.INPUT_MIC_MAX_PHRASES = int(value) def callbackSetMicWordFilter(value): + print("callbackSetMicWordFilter", value) word_filter = str(value) word_filter = [w.strip() for w in word_filter.split(",") if len(w.strip()) > 0] word_filter = ",".join(word_filter) - print(word_filter) + print("callbackSetMicWordFilter_afterSplitting", word_filter) if len(word_filter) > 0: config.INPUT_MIC_WORD_FILTER = word_filter.split(",") else: @@ -299,52 +310,61 @@ def callbackSetMicWordFilter(value): # Transcription Tab (Speaker) def callbackSetSpeakerDevice(value): - print(value) + print("callbackSetSpeakerDevice", value) config.CHOICE_SPEAKER_DEVICE = value def callbackSetSpeakerEnergyThreshold(value): - print(int(value)) + print("callbackSetSpeakerEnergyThreshold", int(value)) config.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) def callbackSetSpeakerDynamicEnergyThreshold(value): - print(value) + print("callbackSetSpeakerDynamicEnergyThreshold", value) config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value +def callbackCheckSpeakerThreshold(is_turned_on): + print("callbackCheckSpeakerThreshold", is_turned_on) + if is_turned_on is True: + # UIの処理あり + pass + else: + # UIの処理あり + pass + def callbackSetSpeakerRecordTimeout(value): - print(int(value)) + print("callbackSetSpeakerRecordTimeout", int(value)) config.INPUT_SPEAKER_RECORD_TIMEOUT = int(value) def callbackSetSpeakerPhraseTimeout(value): - print(int(value)) + print("callbackSetSpeakerPhraseTimeout", int(value)) config.INPUT_SPEAKER_PHRASE_TIMEOUT = int(value) def callbackSetSpeakerMaxPhrases(value): - print(int(value)) + print("callbackSetSpeakerMaxPhrases", int(value)) config.INPUT_SPEAKER_MAX_PHRASES = int(value) # Others Tab def callbackSetEnableAutoClearChatbox(value): - print(value) + print("callbackSetEnableAutoClearChatbox", value) config.ENABLE_AUTO_CLEAR_CHATBOX = value def callbackSetEnableNoticeXsoverlay(value): - print(value) + print("callbackSetEnableNoticeXsoverlay", value) config.ENABLE_NOTICE_XSOVERLAY = value def callbackSetMessageFormat(value): - print(value) + print("callbackSetMessageFormat", value) if len(value) > 0: config.MESSAGE_FORMAT = value # Advanced Settings Tab def callbackSetOscIpAddress(value): - print(value) + print("callbackSetOscIpAddress", value) config.OSC_IP_ADDRESS = value def callbackSetOscPort(value): - print(int(value)) + print("callbackSetOscPort", int(value)) config.OSC_PORT = int(value) @@ -361,7 +381,7 @@ if model.authenticationTranslator() is False: model.addKeywords() # check OSC started -# model.checkOSCStarted() +model.checkOSCStarted() # check Software Updated model.checkSoftwareUpdated() @@ -410,6 +430,7 @@ view.register( "callback_set_mic_device": callbackSetMicDevice, "callback_set_mic_energy_threshold": callbackSetMicEnergyThreshold, "callback_set_mic_dynamic_energy_threshold": callbackSetMicDynamicEnergyThreshold, + "callback_check_mic_threshold": callbackCheckMicThreshold, "callback_set_mic_record_timeout": callbackSetMicRecordTimeout, "callback_set_mic_phrase_timeout": callbackSetMicPhraseTimeout, "callback_set_mic_max_phrases": callbackSetMicMaxPhrases, @@ -419,6 +440,7 @@ view.register( "callback_set_speaker_device": callbackSetSpeakerDevice, "callback_set_speaker_energy_threshold": callbackSetSpeakerEnergyThreshold, "callback_set_speaker_dynamic_energy_threshold": callbackSetSpeakerDynamicEnergyThreshold, + "callback_check_speaker_threshold": callbackCheckSpeakerThreshold, "callback_set_speaker_record_timeout": callbackSetSpeakerRecordTimeout, "callback_set_speaker_phrase_timeout": callbackSetSpeakerPhraseTimeout, "callback_set_speaker_max_phrases": callbackSetSpeakerMaxPhrases, diff --git a/view.py b/view.py index f23adffa..56132574 100644 --- a/view.py +++ b/view.py @@ -86,6 +86,7 @@ class View(): vrct_gui.config_window.CALLBACK_SET_MIC_DEVICE = config_window["callback_set_mic_device"] vrct_gui.config_window.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window["callback_set_mic_energy_threshold"] vrct_gui.config_window.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_mic_dynamic_energy_threshold"] + vrct_gui.config_window.CALLBACK_CHECK_MIC_THRESHOLD = config_window["callback_check_mic_threshold"] vrct_gui.config_window.CALLBACK_SET_MIC_RECORD_TIMEOUT = config_window["callback_set_mic_record_timeout"] vrct_gui.config_window.CALLBACK_SET_MIC_PHRASE_TIMEOUT = config_window["callback_set_mic_phrase_timeout"] vrct_gui.config_window.CALLBACK_SET_MIC_MAX_PHRASES = config_window["callback_set_mic_max_phrases"] @@ -95,6 +96,7 @@ class View(): vrct_gui.config_window.CALLBACK_SET_SPEAKER_DEVICE = config_window["callback_set_speaker_device"] vrct_gui.config_window.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window["callback_set_speaker_energy_threshold"] vrct_gui.config_window.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_speaker_dynamic_energy_threshold"] + vrct_gui.config_window.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window["callback_check_speaker_threshold"] vrct_gui.config_window.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = config_window["callback_set_speaker_record_timeout"] vrct_gui.config_window.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = config_window["callback_set_speaker_phrase_timeout"] vrct_gui.config_window.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window["callback_set_speaker_max_phrases"] diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 64dda3cf..f561a370 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -35,6 +35,7 @@ class ConfigWindow(CTkToplevel): self.CALLBACK_SET_MIC_DEVICE = None self.CALLBACK_SET_MIC_ENERGY_THRESHOLD = None self.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = None + self.CALLBACK_CHECK_MIC_THRESHOLD = None self.CALLBACK_SET_MIC_RECORD_TIMEOUT = None self.CALLBACK_SET_MIC_PHRASE_TIMEOUT = None self.CALLBACK_SET_MIC_MAX_PHRASES = None @@ -44,6 +45,7 @@ class ConfigWindow(CTkToplevel): self.CALLBACK_SET_SPEAKER_DEVICE = None self.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = None self.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = None + self.CALLBACK_CHECK_SPEAKER_THRESHOLD = None self.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = None self.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = None self.CALLBACK_SET_SPEAKER_MAX_PHRASES = None diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index a92249c7..c5cb4fb2 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -16,7 +16,7 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings): # 関数名 chatbox から messagebox に変える予定 config.ENABLE_AUTO_CLEAR_CHATBOX も MESSAGEBOXに変えるかな。 def checkbox_auto_clear_chatbox_callback(checkbox_box_widget): - callFunctionIfCallable(config_window.CALLBACK_SET_DEEPL_AUTHKEY, checkbox_box_widget.get()) + callFunctionIfCallable(config_window.CALLBACK_SET_ENABLE_AUTO_CLEAR_CHATBOX, checkbox_box_widget.get()) def checkbox_notice_xsoverlay_callback(checkbox_box_widget): callFunctionIfCallable(config_window.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, checkbox_box_widget.get()) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 68b6a39d..e4e550c2 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -16,8 +16,8 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): createSettingBoxEntry = sbg.createSettingBoxEntry - def checkbox_input_speaker_threshold_check_callback(e, passive_button_wrapper_widget, active_button_wrapper_widget, is_turned_on): - print("is_turned_on", is_turned_on) + def checkbox_input_mic_threshold_check_callback(e, passive_button_wrapper_widget, active_button_wrapper_widget, is_turned_on): + callFunctionIfCallable(config_window.CALLBACK_CHECK_MIC_THRESHOLD, is_turned_on) if is_turned_on is True: passive_button_widget = passive_button_wrapper_widget.children["!ctklabel"] @@ -112,14 +112,14 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): progressbar_attr_name="sb__progressbar_x_slider__progressbar_mic_energy_threshold", passive_button_attr_name="sb__progressbar_x_slider__passive_button_mic_energy_threshold", - passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + passive_button_command=lambda e: checkbox_input_mic_threshold_check_callback( e, config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold, config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold, is_turned_on=True, ), active_button_attr_name="sb__progressbar_x_slider__active_button_mic_energy_threshold", - active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( + active_button_command=lambda e: checkbox_input_mic_threshold_check_callback( e, config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold, config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 70cf726d..a4237b82 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -17,7 +17,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): def checkbox_input_speaker_threshold_check_callback(e, passive_button_wrapper_widget, active_button_wrapper_widget, is_turned_on): - print("is_turned_on", is_turned_on) + callFunctionIfCallable(config_window.CALLBACK_CHECK_SPEAKER_THRESHOLD, is_turned_on) if is_turned_on is True: passive_button_widget = passive_button_wrapper_widget.children["!ctklabel"] From 19b2cbb010efff8e6627f3577f2b3a036e550aab Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 1 Sep 2023 03:29:33 +0900 Subject: [PATCH 065/355] =?UTF-8?q?=E5=A4=89=E6=95=B0=E5=90=8D=20=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E5=90=8D=E5=A4=89=E6=9B=B4:=20chatbox=20->=20message?= =?UTF-8?q?=20box.=20=E3=83=A1=E3=82=A4=E3=83=B3=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=81=AE=E3=83=86=E3=82=AD=E3=82=B9=E3=83=88=E5=85=A5=E5=8A=9B?= =?UTF-8?q?=E6=AC=84=E3=81=AE=E4=BA=8B=E3=81=AFmessage=20box.=20chatbox?= =?UTF-8?q?=E3=81=AFvrc=E5=86=85=E3=81=AEchatbox=E3=82=82=E3=81=82?= =?UTF-8?q?=E3=82=8B=E3=81=AE=E3=81=A7=E5=91=BC=E3=81=B3=E6=96=B9=E3=82=92?= =?UTF-8?q?=E5=88=86=E3=81=91=E3=82=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 12 ++++++------ main.py | 10 +++++----- view.py | 2 +- vrct_gui/config_window/ConfigWindow.py | 2 +- .../setting_box_others/createSettingBox_Others.py | 14 +++++++------- 5 files changed, 20 insertions(+), 20 deletions(-) diff --git a/config.py b/config.py index a66921a4..1e86fa4a 100644 --- a/config.py +++ b/config.py @@ -359,13 +359,13 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property - def ENABLE_AUTO_CLEAR_CHATBOX(self): - return self._ENABLE_AUTO_CLEAR_CHATBOX + def ENABLE_AUTO_CLEAR_MESSAGE_BOX(self): + return self._ENABLE_AUTO_CLEAR_MESSAGE_BOX - @ENABLE_AUTO_CLEAR_CHATBOX.setter - def ENABLE_AUTO_CLEAR_CHATBOX(self, value): + @ENABLE_AUTO_CLEAR_MESSAGE_BOX.setter + def ENABLE_AUTO_CLEAR_MESSAGE_BOX(self, value): if type(value) is bool: - self._ENABLE_AUTO_CLEAR_CHATBOX = value + self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property @@ -493,7 +493,7 @@ class Config: "Google(web)": None, } self._MESSAGE_FORMAT = "[message]([translation])" - self._ENABLE_AUTO_CLEAR_CHATBOX = False + self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = False self._ENABLE_NOTICE_XSOVERLAY = False self._ENABLE_OSC = False self._UPDATE_FLAG = False diff --git a/main.py b/main.py index 17eb02b8..c939d26b 100644 --- a/main.py +++ b/main.py @@ -90,7 +90,7 @@ def sendChatMessage(message): view.printToTextbox_SentMessage(message, translation) # delete message in entry message box - if config.ENABLE_AUTO_CLEAR_CHATBOX is True: + if config.ENABLE_AUTO_CLEAR_MESSAGE_BOX is True: view.clearMessageBox() def messageBoxPressKeyEnter(e): @@ -344,9 +344,9 @@ def callbackSetSpeakerMaxPhrases(value): # Others Tab -def callbackSetEnableAutoClearChatbox(value): - print("callbackSetEnableAutoClearChatbox", value) - config.ENABLE_AUTO_CLEAR_CHATBOX = value +def callbackSetEnableAutoClearMessageBox(value): + print("callbackSetEnableAutoClearMessageBox", value) + config.ENABLE_AUTO_CLEAR_MESSAGE_BOX = value def callbackSetEnableNoticeXsoverlay(value): print("callbackSetEnableNoticeXsoverlay", value) @@ -446,7 +446,7 @@ view.register( "callback_set_speaker_max_phrases": callbackSetSpeakerMaxPhrases, # Others Tab - "callback_set_enable_auto_clear_chatbox": callbackSetEnableAutoClearChatbox, + "callback_set_enable_auto_clear_chatbox": callbackSetEnableAutoClearMessageBox, "callback_set_enable_notice_xsoverlay": callbackSetEnableNoticeXsoverlay, "callback_set_message_format": callbackSetMessageFormat, diff --git a/view.py b/view.py index 56132574..975921f8 100644 --- a/view.py +++ b/view.py @@ -102,7 +102,7 @@ class View(): vrct_gui.config_window.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window["callback_set_speaker_max_phrases"] # Others Tab - vrct_gui.config_window.CALLBACK_SET_ENABLE_AUTO_CLEAR_CHATBOX = config_window["callback_set_enable_auto_clear_chatbox"] + vrct_gui.config_window.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window["callback_set_enable_auto_clear_chatbox"] vrct_gui.config_window.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window["callback_set_enable_notice_xsoverlay"] vrct_gui.config_window.CALLBACK_SET_MESSAGE_FORMAT = config_window["callback_set_message_format"] diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index f561a370..25e03688 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -51,7 +51,7 @@ class ConfigWindow(CTkToplevel): self.CALLBACK_SET_SPEAKER_MAX_PHRASES = None # Others Tab - self.CALLBACK_SET_ENABLE_AUTO_CLEAR_CHATBOX = None + self.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = None self.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = None self.CALLBACK_SET_MESSAGE_FORMAT = None diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index c5cb4fb2..b0d77325 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -14,9 +14,9 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings): createSettingBoxEntry = sbg.createSettingBoxEntry - # 関数名 chatbox から messagebox に変える予定 config.ENABLE_AUTO_CLEAR_CHATBOX も MESSAGEBOXに変えるかな。 - def checkbox_auto_clear_chatbox_callback(checkbox_box_widget): - callFunctionIfCallable(config_window.CALLBACK_SET_ENABLE_AUTO_CLEAR_CHATBOX, checkbox_box_widget.get()) + # 元 checkbox_auto_clear_chatbox_callback + def checkbox_auto_clear_message_box_callback(checkbox_box_widget): + callFunctionIfCallable(config_window.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX, checkbox_box_widget.get()) def checkbox_notice_xsoverlay_callback(checkbox_box_widget): callFunctionIfCallable(config_window.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, checkbox_box_widget.get()) @@ -26,15 +26,15 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings): row=0 - config_window.sb__auto_clear_chatbox = createSettingBoxCheckbox( + config_window.sb__auto_clear_message_box = createSettingBoxCheckbox( parent_widget=setting_box_wrapper, label_text="Auto Clear The Message Box", desc_text="Clear the message box after sending your message.", - checkbox_attr_name="sb__checkbox_auto_clear_chatbox", - command=lambda: checkbox_auto_clear_chatbox_callback(config_window.sb__checkbox_auto_clear_chatbox), + checkbox_attr_name="sb__checkbox_auto_clear_message_box", + command=lambda: checkbox_auto_clear_message_box_callback(config_window.sb__checkbox_auto_clear_message_box), is_checked=False ) - config_window.sb__auto_clear_chatbox.grid(row=row) + config_window.sb__auto_clear_message_box.grid(row=row) row+=1 From 55ee9e6325703eec43ff0c462f5a70f212f35d0f Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 1 Sep 2023 03:36:33 +0900 Subject: [PATCH 066/355] [Chore] remove the code that is no longer in use. --- .../_createSettingBoxContainer.py | 1 - .../createSettingBox_AdvancedSettings.py | 2 -- .../setting_box_appearance/createSettingBox_Appearance.py | 6 +----- .../setting_box_others/createSettingBox_Others.py | 2 -- .../setting_box_translation/createSettingBox_Translation.py | 2 -- 5 files changed, 1 insertion(+), 12 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py index a8219006..dd01d49c 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py @@ -19,7 +19,6 @@ def _createSettingBoxContainer(config_window, settings, setting_box_container_se return setting_box_wrapper_section_title_frame - # Common setting # Setting box container setting_box_container_widget = CTkFrame(config_window.main_setting_box_bg_wrapper, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py index 32a01e06..10fdb6f8 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py @@ -1,5 +1,3 @@ -from time import sleep - from customtkinter import StringVar, IntVar from utils import callFunctionIfCallable diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index e8dda661..fc9567ba 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -1,11 +1,8 @@ -from time import sleep - from customtkinter import StringVar, IntVar from tkinter import font as tk_font from languages import selectable_languages from utils import get_key_by_value, callFunctionIfCallable - from .._SettingBoxGenerator import _SettingBoxGenerator from config import config @@ -16,7 +13,7 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): createSettingBoxSlider = sbg.createSettingBoxSlider # 関数名は変えるかもしれない。 - # テーマ変更、フォント変更時、 Widget再生成か再起動かは検討中\ + # テーマ変更、フォント変更時、 Widget再生成か再起動かは検討中 def slider_transparency_callback(value): callFunctionIfCallable(config_window.CALLBACK_SET_TRANSPARENCY, value) @@ -25,7 +22,6 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): def optionmenu_ui_scaling_callback(value): callFunctionIfCallable(config_window.CALLBACK_SET_UI_SCALING, value) - # self.optionmenu_ui_scaling.set(choice) def optionmenu_font_family_callback(value): callFunctionIfCallable(config_window.CALLBACK_SET_FONT_FAMILY, value) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index b0d77325..7b5811c9 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -1,5 +1,3 @@ -from time import sleep - from customtkinter import StringVar, IntVar from utils import callFunctionIfCallable diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index 974e6f33..5c635edd 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -1,5 +1,3 @@ -from time import sleep - from customtkinter import StringVar, IntVar from utils import callFunctionIfCallable From 2ee8eca63bf0a3b3d2369070d2595b675192467b Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 2 Sep 2023 02:19:23 +0900 Subject: [PATCH 067/355] =?UTF-8?q?Config=20Window=20=E5=90=84=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E9=A0=85=E7=9B=AE=E3=82=92=E5=A4=89=E6=95=B0=E5=8C=96?= =?UTF-8?q?=E3=81=97=E3=80=81ctk=20variable=E3=81=AA=E3=81=A9=E5=85=A8?= =?UTF-8?q?=E3=81=A6view.py=E3=81=AB=E7=A7=BB=E5=8B=95=E3=80=82=20vrct=5Fg?= =?UTF-8?q?ui=E4=BB=A5=E4=B8=8B=E3=81=A7=E3=81=AFconfig.py=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E3=82=8F=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E5=88=87=E3=82=8A=E9=9B=A2=E3=81=97=E3=81=9F=E3=80=82=20CTkScr?= =?UTF-8?q?ollableDropdown=E3=82=92=E4=BD=BF=E3=81=86=E3=81=93=E3=81=A8?= =?UTF-8?q?=E3=82=92=E3=82=84=E3=82=81=E3=81=9F=E3=80=82(grab=5Fset?= =?UTF-8?q?=E3=81=A8=E5=B9=B2=E6=B8=89=E3=81=99=E3=82=8B=E3=81=9F=E3=82=81?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 4 + view.py | 186 +++++++++++++++++- vrct_gui/config_window/ConfigWindow.py | 44 +---- .../_SettingBoxGenerator.py | 96 ++++----- .../createSettingBox_AdvancedSettings.py | 14 +- .../createSettingBox_Appearance.py | 53 +++-- .../createSettingBox_Others.py | 18 +- .../createSettingBox_Mic.py | 68 +++---- .../createSettingBox_Speaker.py | 42 ++-- .../createSettingBox_Translation.py | 6 +- .../main_window/createMainWindowWidgets.py | 2 +- vrct_gui/vrct_gui.py | 9 +- 12 files changed, 337 insertions(+), 205 deletions(-) diff --git a/main.py b/main.py index c939d26b..03774d60 100644 --- a/main.py +++ b/main.py @@ -2,6 +2,8 @@ from threading import Thread from config import config from model import model from view import view +from utils import get_key_by_value +from languages import selectable_languages # func transcription send message def sendMicMessage(message): @@ -244,6 +246,8 @@ def callbackSetFontFamily(value): def callbackSetUiLanguage(value): print("callbackSetUiLanguage", value) + value = get_key_by_value(selectable_languages, value) + print("callbackSetUiLanguage__after_get_key_by_value", value) config.UI_LANGUAGE = value # Translation Tab diff --git a/view.py b/view.py index 975921f8..5f37da6a 100644 --- a/view.py +++ b/view.py @@ -1,6 +1,8 @@ from types import SimpleNamespace +from tkinter import font as tk_font +from languages import selectable_languages -from customtkinter import StringVar, END as CTK_END, get_appearance_mode +from customtkinter import StringVar, IntVar, BooleanVar, END as CTK_END, get_appearance_mode from vrct_gui.ui_managers import ColorThemeManager, ImageFilenameManager, UiScalingManager from vrct_gui import vrct_gui @@ -34,6 +36,170 @@ class View(): **common_args ) + self.view_variable = SimpleNamespace( + VAR_LABEL_TRANSPARENCY=StringVar(value="Transparency"), + VAR_DESC_TRANSPARENCY=StringVar(value="Change the window's transparency. 50% to 100%. (Default: 100%)"), + SLIDER_RANGE_TRANSPARENCY=(50, 100), + CALLBACK_SET_TRANSPARENCY=None, + VAR_TRANSPARENCY=IntVar(value=config.TRANSPARENCY), + + VAR_LABEL_APPEARANCE_THEME=StringVar(value="Theme"), + VAR_DESC_APPEARANCE_THEME=StringVar(value="Change the color theme from \"Light\" and \"Dark\". If you select \"System\", It will adjust based on your Windows theme. (Default: System)"), + LIST_APPEARANCE_THEME=["Light", "Dark", "System"], + CALLBACK_SET_APPEARANCE_THEME=None, + VAR_APPEARANCE_THEME=StringVar(value=config.APPEARANCE_THEME), + + VAR_LABEL_UI_SCALING=StringVar(value="UI Size"), + VAR_DESC_UI_SCALING=StringVar(value="(Default: 100%)"), + LIST_UI_SCALING=["80%", "90%", "100%", "110%", "120%"], + CALLBACK_SET_UI_SCALING=None, + VAR_UI_SCALING=StringVar(value=config.UI_SCALING), + + VAR_LABEL_FONT_FAMILY=StringVar(value="Font Family"), + VAR_DESC_FONT_FAMILY=StringVar(value="(Default: Yu Gothic UI)"), + LIST_FONT_FAMILY=list(tk_font.families()), + CALLBACK_SET_FONT_FAMILY=None, + VAR_FONT_FAMILY=StringVar(value=config.FONT_FAMILY), + + VAR_LABEL_UI_LANGUAGE=StringVar(value="UI Language"), + VAR_DESC_UI_LANGUAGE=StringVar(value="(Default: English)"), + LIST_UI_LANGUAGE=list(selectable_languages.values()), + CALLBACK_SET_UI_LANGUAGE=None, + VAR_UI_LANGUAGE=StringVar(value=selectable_languages[config.UI_LANGUAGE]), + + + + VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value="DeepL Auth Key"), + VAR_DESC_DEEPL_AUTH_KEY=None, + # VAR_DESC_DEEPL_AUTH_KEY=StringVar(value=""), + CALLBACK_SET_DEEPL_AUTH_KEY=None, + VAR_DEEPL_AUTH_KEY=StringVar(value=config.AUTH_KEYS["DeepL(auth)"]), + + + + VAR_LABEL_MIC_HOST=StringVar(value="Mic Host"), + VAR_DESC_MIC_HOST=StringVar(value="Select the mic host. (Default: ?)"), + LIST_MIC_HOST=[], # model.getListInputHost(), + CALLBACK_SET_MIC_HOST=None, + VAR_MIC_HOST=StringVar(value=config.CHOICE_MIC_HOST), + + VAR_LABEL_MIC_DEVICE=StringVar(value="Mic Device"), + VAR_DESC_MIC_DEVICE=StringVar(value="Select the mic devise. (Default: ?)"), + LIST_MIC_DEVICE=[], # model.getListInputDevice(), + CALLBACK_SET_MIC_DEVICE=None, + VAR_MIC_DEVICE=StringVar(value=config.CHOICE_MIC_DEVICE), + + VAR_LABEL_MIC_ENERGY_THRESHOLD=StringVar(value="Mic Energy Threshold"), + VAR_DESC_MIC_ENERGY_THRESHOLD=StringVar(value="Slider to modify the threshold for activating voice input.\nPress the microphone button to start input and speak something, so you can adjust it while monitoring the actual volume. 0 to 2000 (Default: 300)"), + SLIDER_RANGE_MIC_ENERGY_THRESHOLD=(0, config.MAX_MIC_ENERGY_THRESHOLD), + CALLBACK_CHECK_MIC_THRESHOLD=None, + VAR_MIC_ENERGY_THRESHOLD=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), + + VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value="Mic Dynamic Energy Threshold"), + VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value="When this feature is selected, it will automatically adjust in a way that works well, based on the set Mic Energy Threshold."), + CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD=None, + VAR_MIC_DYNAMIC_ENERGY_THRESHOLD=BooleanVar(value=config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD), + + VAR_LABEL_MIC_RECORD_TIMEOUT=StringVar(value="Mic Record Timeout"), + VAR_DESC_MIC_RECORD_TIMEOUT=StringVar(value="(Default: 3)"), + CALLBACK_SET_MIC_RECORD_TIMEOUT=None, + VAR_MIC_RECORD_TIMEOUT=IntVar(value=config.INPUT_MIC_RECORD_TIMEOUT), + + VAR_LABEL_MIC_PHRASE_TIMEOUT=StringVar(value="Mic Phrase Timeout"), + VAR_DESC_MIC_PHRASE_TIMEOUT=StringVar(value="(Default: 3)"), + CALLBACK_SET_MIC_PHRASE_TIMEOUT=None, + VAR_MIC_PHRASE_TIMEOUT=IntVar(value=config.INPUT_MIC_PHRASE_TIMEOUT), + + VAR_LABEL_MIC_MAX_PHRASES=StringVar(value="Mic Max Phrases"), + VAR_DESC_MIC_MAX_PHRASES=StringVar(value="It will stop recording and send the recordings when the set count of phrase(s) is reached. (Default: 10)"), + CALLBACK_SET_MIC_MAX_PHRASES=None, + VAR_MIC_MAX_PHRASES=IntVar(value=config.INPUT_MIC_MAX_PHRASES), + + + VAR_LABEL_MIC_WORD_FILTER=StringVar(value="Mic Word Filter"), + VAR_DESC_MIC_WORD_FILTER=StringVar(value="It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC"), + CALLBACK_SET_MIC_WORD_FILTER=None, + VAR_MIC_WORD_FILTER=StringVar(value=",".join(config.INPUT_MIC_WORD_FILTER) if len(config.INPUT_MIC_WORD_FILTER) > 0 else ""), + + + + + + + + + + + + VAR_LABEL_SPEAKER_DEVICE=StringVar(value="Speaker Device"), + VAR_DESC_SPEAKER_DEVICE=StringVar(value="Select the speaker devise. (Default: ?)"), + LIST_SPEAKER_DEVICE=[], # model.getListOutputDevice(), + CALLBACK_SET_SPEAKER_DEVICE=None, + VAR_SPEAKER_DEVICE=StringVar(value=config.CHOICE_SPEAKER_DEVICE), + + VAR_LABEL_SPEAKER_ENERGY_THRESHOLD=StringVar(value="Mic Energy Threshold"), + VAR_DESC_SPEAKER_ENERGY_THRESHOLD=StringVar(value="Slider to modify the threshold for activating voice input.\nPress the headphones mark button to start input and speak something, so you can adjust it while monitoring the actual volume. 0 to 4000 (Default: 300)"), + SLIDER_RANGE_SPEAKER_ENERGY_THRESHOLD=(0, config.MAX_SPEAKER_ENERGY_THRESHOLD), + CALLBACK_CHECK_SPEAKER_THRESHOLD=None, + VAR_SPEAKER_ENERGY_THRESHOLD=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), + + VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value="Speaker Dynamic Energy Threshold"), + VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value="When this feature is selected, it will automatically adjust in a way that works well, based on the set Speaker Energy Threshold."), + CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=None, + VAR_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=BooleanVar(value=config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD), + + VAR_LABEL_SPEAKER_RECORD_TIMEOUT=StringVar(value="Speaker Record Timeout"), + VAR_DESC_SPEAKER_RECORD_TIMEOUT=StringVar(value="(Default: 3)"), + CALLBACK_SET_SPEAKER_RECORD_TIMEOUT=None, + VAR_SPEAKER_RECORD_TIMEOUT=IntVar(value=config.INPUT_SPEAKER_RECORD_TIMEOUT), + + VAR_LABEL_SPEAKER_PHRASE_TIMEOUT=StringVar(value="Speaker Phrase Timeout"), + VAR_DESC_SPEAKER_PHRASE_TIMEOUT=StringVar(value="It will stop recording and receive the recordings when the set second(s) is reached. (Default: 3)"), + CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT=None, + VAR_SPEAKER_PHRASE_TIMEOUT=IntVar(value=config.INPUT_SPEAKER_PHRASE_TIMEOUT), + + VAR_LABEL_SPEAKER_MAX_PHRASES=StringVar(value="Speaker Max Phrases"), + VAR_DESC_SPEAKER_MAX_PHRASES=StringVar(value="It will stop recording and receive the recordings when the set count of phrase(s) is reached. (Default: 10)"), + CALLBACK_SET_SPEAKER_MAX_PHRASES=None, + VAR_SPEAKER_MAX_PHRASES=IntVar(value=config.INPUT_SPEAKER_MAX_PHRASES), + + + + + + + VAR_LABEL_ENABLE_AUTO_CLEAR_MESSAGE_BOX=StringVar(value="Auto Clear The Message Box"), + VAR_DESC_ENABLE_AUTO_CLEAR_MESSAGE_BOX=StringVar(value="Clear the message box after sending your message."), + CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX=None, + VAR_ENABLE_AUTO_CLEAR_MESSAGE_BOX=BooleanVar(value=config.ENABLE_AUTO_CLEAR_MESSAGE_BOX), + + VAR_LABEL_ENABLE_NOTICE_XSOVERLAY=StringVar(value="Notification XSOverlay (VR Only)"), + VAR_DESC_ENABLE_NOTICE_XSOVERLAY=StringVar(value="Notify received messages by using XSOverlay's notification feature."), + CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY=None, + VAR_ENABLE_NOTICE_XSOVERLAY=BooleanVar(value=config.ENABLE_NOTICE_XSOVERLAY), + + VAR_LABEL_MESSAGE_FORMAT=StringVar(value="Message Format"), + VAR_DESC_MESSAGE_FORMAT=StringVar(value="You can change the decoration of the message you want to send. (Default: \"[message]([translation])\" )"), + CALLBACK_SET_MESSAGE_FORMAT=None, + VAR_MESSAGE_FORMAT=StringVar(value=config.MESSAGE_FORMAT), + + + + + + VAR_LABEL_OSC_IP_ADDRESS=StringVar(value="OSC IP Address"), + VAR_DESC_OSC_IP_ADDRESS=StringVar(value="(Default: 127.0.0.1)"), + CALLBACK_SET_OSC_IP_ADDRESS=None, + VAR_OSC_IP_ADDRESS=IntVar(value=config.OSC_IP_ADDRESS), + + VAR_LABEL_OSC_PORT=StringVar(value="OSC Port"), + VAR_DESC_OSC_PORT=StringVar(value="(Default: 9000)"), + CALLBACK_SET_OSC_PORT=None, + VAR_OSC_PORT=IntVar(value=config.OSC_PORT), + + ) + + def register(self, sidebar_features, language_presets, entry_message_box_commands, config_window): @@ -72,9 +238,23 @@ class View(): # Appearance Tab vrct_gui.config_window.CALLBACK_SET_TRANSPARENCY = config_window["callback_set_transparency"] + # vrct_gui.config_window.sb__transparency_slider.configure(variable=IntVar(value=config.TRANSPARENCY)) + vrct_gui.config_window.CALLBACK_SET_APPEARANCE = config_window["callback_set_appearance"] vrct_gui.config_window.CALLBACK_SET_UI_SCALING = config_window["callback_set_ui_scaling"] - vrct_gui.config_window.CALLBACK_SET_FONT_FAMILY = config_window["callback_set_font_family"] + + self.view_variable.CALLBACK_SET_FONT_FAMILY = config_window["callback_set_font_family"] + + # vrct_gui.config_window.sb__optionmenu_font_family.configure(values=self.view_variable.LIST_FONT_FAMILY) + + # self.view_variable.VAR_FONT_FAMILY = StringVar(value=config.FONT_FAMILY) + # vrct_gui.config_window.sb__optionmenu_font_family.configure(variable=self.view_variable.VAR_FONT_FAMILY) + + + + # vrct_gui.config_window.sb__optionmenu_font_family.configure(variable=StringVar(value=config.FONT_FAMILY)) + # vrct_gui.config_window.sb__optionmenu_font_family.configure(values=["test", "from", "view.py"]) + vrct_gui.config_window.CALLBACK_SET_UI_LANGUAGE = config_window["callback_set_ui_language"] @@ -216,7 +396,7 @@ class View(): def createGUI(self): - vrct_gui.createGUI(settings=self.settings) + vrct_gui.createGUI(settings=self.settings, view_variable=self.view_variable) def startMainLoop(self): vrct_gui.startMainLoop() diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 25e03688..c2c87c1c 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -4,7 +4,7 @@ from .widgets import createConfigWindowTitle, createSideMenuAndSettingsBoxContai from customtkinter import CTkToplevel class ConfigWindow(CTkToplevel): - def __init__(self, vrct_gui, settings): + def __init__(self, vrct_gui, settings, view_variable): super().__init__() self.withdraw() @@ -18,47 +18,7 @@ class ConfigWindow(CTkToplevel): self.protocol("WM_DELETE_WINDOW", vrct_gui.closeConfigWindow) self.settings = settings - - - # Appearance Tab - self.CALLBACK_SET_TRANSPARENCY = None - self.CALLBACK_SET_APPEARANCE = None - self.CALLBACK_SET_UI_SCALING = None - self.CALLBACK_SET_FONT_FAMILY = None - self.CALLBACK_SET_UI_LANGUAGE = None - - # Translation Tab - self.CALLBACK_SET_DEEPL_AUTHKEY = None - - # Transcription Tab (Mic) - self.CALLBACK_SET_MIC_HOST = None - self.CALLBACK_SET_MIC_DEVICE = None - self.CALLBACK_SET_MIC_ENERGY_THRESHOLD = None - self.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = None - self.CALLBACK_CHECK_MIC_THRESHOLD = None - self.CALLBACK_SET_MIC_RECORD_TIMEOUT = None - self.CALLBACK_SET_MIC_PHRASE_TIMEOUT = None - self.CALLBACK_SET_MIC_MAX_PHRASES = None - self.CALLBACK_SET_MIC_WORD_FILTER = None - - # Transcription Tab (Speaker) - self.CALLBACK_SET_SPEAKER_DEVICE = None - self.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = None - self.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = None - self.CALLBACK_CHECK_SPEAKER_THRESHOLD = None - self.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = None - self.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = None - self.CALLBACK_SET_SPEAKER_MAX_PHRASES = None - - # Others Tab - self.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = None - self.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = None - self.CALLBACK_SET_MESSAGE_FORMAT = None - - # Advanced Settings Tab - self.CALLBACK_SET_OSC_IP_ADDRESS = None - self.CALLBACK_SET_OSC_PORT = None - + self.view_variable = view_variable createConfigWindowTitle(config_window=self, settings=settings) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index ebeca5ac..d6044900 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -1,4 +1,4 @@ -from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar, END as CTK_END +from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar, END as CTK_END, StringVar, IntVar from ctk_scrollable_dropdown import CTkScrollableDropdown from vrct_gui.ui_utils import createButtonWithImage @@ -23,22 +23,22 @@ class _SettingBoxGenerator(): setting_box_frame_wrapper.grid_columnconfigure((0,1), weight=1, minsize=int(self.uism.SB__MAIN_WIDTH / 2)) return setting_box_frame_wrapper - def _createSettingBoxFrame(self, parent_widget, label_text, desc_text): + def _createSettingBoxFrame(self, parent_widget, for_var_label_text, for_var_desc_text): setting_box_frame = CTkFrame(parent_widget, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=0, height=0) setting_box_frame_wrapper = self._createSettingBoxFrameWrapper(setting_box_frame) - self._setSettingBoxLabels(setting_box_frame_wrapper, label_text, desc_text) + self._setSettingBoxLabels(setting_box_frame_wrapper, for_var_label_text, for_var_desc_text) # "pady=(0,1)" is for bottom padding. It can be removed(override) when you do like "self.attr_name.grid(row=row, pady=0)" setting_box_frame.grid(column=0, padx=0, pady=(0,1), sticky="ew") return (setting_box_frame, setting_box_frame_wrapper) - def _setSettingBoxLabels(self, setting_box_frame, label_text, desc_text=False): + def _setSettingBoxLabels(self, setting_box_frame, for_var_label_text, for_var_desc_text=None): setting_box_labels_frame = CTkFrame(setting_box_frame, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=0, height=0) setting_box_label = CTkLabel( setting_box_labels_frame, - text=label_text, + textvariable=for_var_label_text, anchor="w", # height=0, font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__LABEL_FONT_SIZE, weight="normal"), @@ -46,12 +46,12 @@ class _SettingBoxGenerator(): ) setting_box_label.grid(row=0, column=0, padx=0, pady=0, sticky="ew") - if desc_text == False or self.IS_CONFIG_WINDOW_COMPACT_MODE is True: + if for_var_desc_text == None or self.IS_CONFIG_WINDOW_COMPACT_MODE is True: pass else: self.setting_box_desc = CTkLabel( setting_box_labels_frame, - text=desc_text, + textvariable=for_var_desc_text, anchor="w", justify="left", # height=0, @@ -65,8 +65,8 @@ class _SettingBoxGenerator(): - def createSettingBoxDropdownMenu(self, parent_widget, label_text, desc_text, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values, command, variable): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, label_text, desc_text) + def createSettingBoxDropdownMenu(self, parent_widget, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, variable=None, dropdown_menu_attr_name=None, dropdown_menu_values=None): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, for_var_label_text, for_var_desc_text) setting_box_dropdown_menu_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) setting_box_dropdown_menu_frame.grid(row=0, column=1, padx=0, sticky="e") @@ -74,7 +74,6 @@ class _SettingBoxGenerator(): self.createOption_DropdownMenu( setting_box_dropdown_menu_frame=setting_box_dropdown_menu_frame, optionmenu_attr_name=optionmenu_attr_name, - dropdown_menu_attr_name=dropdown_menu_attr_name, dropdown_menu_values=dropdown_menu_values, command=command, variable=variable, @@ -85,8 +84,8 @@ class _SettingBoxGenerator(): - def createSettingBoxSwitch(self, parent_widget, label_text, desc_text, switch_attr_name, is_checked, command): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, label_text, desc_text) + def createSettingBoxSwitch(self, parent_widget, for_var_label_text, for_var_desc_text, switch_attr_name, is_checked, command): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, for_var_label_text, for_var_desc_text) setting_box_switch_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) setting_box_switch_frame.grid(row=0, column=1, padx=0, sticky="e") @@ -117,8 +116,8 @@ class _SettingBoxGenerator(): - def createSettingBoxCheckbox(self, parent_widget, label_text, desc_text, checkbox_attr_name, is_checked, command): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, label_text, desc_text) + def createSettingBoxCheckbox(self, parent_widget, for_var_label_text, for_var_desc_text, checkbox_attr_name, variable, command): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, for_var_label_text, for_var_desc_text) setting_box_checkbox_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) setting_box_checkbox_frame.grid(row=0, column=1, padx=0, sticky="e") @@ -131,6 +130,7 @@ class _SettingBoxGenerator(): checkbox_height=self.uism.SB__CHECKBOX_SIZE, onvalue=True, offvalue=False, + variable=variable, command=command, corner_radius=self.uism.SB__CHECKBOX_CORNER_RADIUS, border_width=self.uism.SB__CHECKBOX_BORDER_WIDTH, @@ -144,7 +144,7 @@ class _SettingBoxGenerator(): ) setattr(self.config_window, checkbox_attr_name, checkbox_widget) - checkbox_widget.select() if is_checked else checkbox_widget.deselect() + # checkbox_widget.select() if is_checked else checkbox_widget.deselect() checkbox_widget.grid(row=0, column=0) @@ -155,8 +155,8 @@ class _SettingBoxGenerator(): - def createSettingBoxSlider(self, parent_widget, label_text, desc_text, slider_attr_name, slider_range, command, variable, slider_number_of_steps: Union[int, None] = None): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, label_text, desc_text) + def createSettingBoxSlider(self, parent_widget, for_var_label_text, for_var_desc_text, slider_attr_name, slider_range, command, variable, slider_number_of_steps: Union[int, None] = None): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, for_var_label_text, for_var_desc_text) setting_box_slider_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) setting_box_slider_frame.grid(row=0, column=1, padx=0, sticky="e") @@ -181,18 +181,20 @@ class _SettingBoxGenerator(): def createSettingBoxProgressbarXSlider(self, - parent_widget, label_text, desc_text, command, + parent_widget, for_var_label_text, for_var_desc_text, command, entry_attr_name, - slider_attr_name, slider_range, slider_number_of_steps, + slider_attr_name, slider_range, progressbar_attr_name, passive_button_attr_name, passive_button_command, active_button_attr_name, active_button_command, button_image_filename, variable, + + slider_number_of_steps: Union[int, None] = None, ): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, label_text, desc_text) + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, for_var_label_text, for_var_desc_text) setting_box_progressbar_x_slider_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) setting_box_progressbar_x_slider_frame.grid(row=0, column=1, padx=0, sticky="e") @@ -288,8 +290,8 @@ class _SettingBoxGenerator(): - def createSettingBoxEntry(self, parent_widget, label_text, desc_text, entry_attr_name, entry_width, entry_bind__Any_KeyRelease, entry_textvariable): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, label_text, desc_text) + def createSettingBoxEntry(self, parent_widget, for_var_label_text, for_var_desc_text, entry_attr_name, entry_width, entry_bind__Any_KeyRelease, entry_textvariable): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, for_var_label_text, for_var_desc_text) setting_box_entry_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) setting_box_entry_frame.grid(row=0, column=1, padx=0, sticky="e") @@ -414,50 +416,54 @@ class _SettingBoxGenerator(): - def createOption_DropdownMenu(self, setting_box_dropdown_menu_frame, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values, command, variable): + def createOption_DropdownMenu(self, setting_box_dropdown_menu_frame, optionmenu_attr_name, command, variable, dropdown_menu_values): + + # set the value to the option menu's variable automatically + # def adjustedCommand(selected_value): + # option_menu_widget.set(selected_value) + # command(selected_value) + option_menu_widget = CTkOptionMenu( setting_box_dropdown_menu_frame, height=self.uism.SB__OPTIONMENU_HEIGHT, width=self.uism.SB__OPTIONMENU_WIDTH, + values=dropdown_menu_values, button_color=self.ctm.SB__OPTIONMENU_BG_COLOR, button_hover_color=self.ctm.SB__OPTIONMENU_HOVERED_BG_COLOR, fg_color=self.ctm.SB__OPTIONMENU_BG_COLOR, font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), variable=variable, + command=command, anchor="w", ) option_menu_widget.grid(row=0, column=0, sticky="e") setattr(self.config_window, optionmenu_attr_name, option_menu_widget) - # set the value to the option menu's variable automatically - def adjustedCommand(selected_value): - option_menu_widget.set(selected_value) - command(selected_value) + # option_menu_widget.configure(command=adjustedCommand) - dropdown_menu_widget = CTkScrollableDropdown( - option_menu_widget, - values=dropdown_menu_values, - justify="left", - width=self.uism.SB__DROPDOWN_MENU_WIDTH, - min_show_button_num=6, - button_pady=0, - frame_corner_radius=self.uism.SB__DROPDOWN_MENU_FRAME_CORNER_RADIUS, - max_button_height=self.uism.SB__DROPDOWN_MENU_MAX_BUTTON_HEIGHT, - max_height=self.uism.SB__DROPDOWN_MENU_FRAME_MAX_HEIGHT, - font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), - command=adjustedCommand, - ) + # dropdown_menu_widget = CTkScrollableDropdown( + # option_menu_widget, + # justify="left", + # width=self.uism.SB__DROPDOWN_MENU_WIDTH, + # min_show_button_num=6, + # button_pady=0, + # frame_corner_radius=self.uism.SB__DROPDOWN_MENU_FRAME_CORNER_RADIUS, + # max_button_height=self.uism.SB__DROPDOWN_MENU_MAX_BUTTON_HEIGHT, + # max_height=self.uism.SB__DROPDOWN_MENU_FRAME_MAX_HEIGHT, + # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), + # command=adjustedCommand, + # ) # dropdown_menu_widget.bind( # "", # lambda e: dropdown_menu_widget._withdraw() if not str(e.widget).startswith(str(dropdown_menu_widget.frame._parent_frame)) else None, # ) - dropdown_menu_widget.bind( - "", - lambda e: print(e), - ) + # dropdown_menu_widget.bind( + # "", + # lambda e: print(e), + # ) - setattr(self.config_window, dropdown_menu_attr_name, dropdown_menu_widget) + # setattr(self.config_window, dropdown_menu_attr_name, dropdown_menu_widget) return option_menu_widget diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py index 10fdb6f8..9a5397be 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py @@ -20,12 +20,12 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin row=0 config_window.sb__ip_address = createSettingBoxEntry( parent_widget=setting_box_wrapper, - label_text="OSC IP Address", - desc_text="(Default: 127.0.0.1)", + for_var_label_text=config_window.view_variable.VAR_LABEL_OSC_IP_ADDRESS, + for_var_desc_text=config_window.view_variable.VAR_DESC_OSC_IP_ADDRESS, entry_attr_name="sb__entry_ip_address", entry_width=settings.uism.SB__ENTRY_WIDTH_150, entry_bind__Any_KeyRelease=lambda value: entry_ip_address_callback(value), - entry_textvariable=StringVar(value=config.OSC_IP_ADDRESS), + entry_textvariable=config_window.view_variable.VAR_OSC_IP_ADDRESS, ) config_window.sb__ip_address.grid(row=row) row+=1 @@ -33,12 +33,12 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin config_window.sb__port = createSettingBoxEntry( parent_widget=setting_box_wrapper, - label_text="OSC Port", - desc_text="(Default: 9000)", + for_var_label_text=config_window.view_variable.VAR_LABEL_OSC_PORT, + for_var_desc_text=config_window.view_variable.VAR_DESC_OSC_PORT, entry_attr_name="sb__entry_port", entry_width=settings.uism.SB__ENTRY_WIDTH_150, entry_bind__Any_KeyRelease=lambda value: entry_port_callback(value), - entry_textvariable=IntVar(value=config.OSC_PORT), + entry_textvariable=config_window.view_variable.VAR_OSC_PORT, ) config_window.sb__port.grid(row=row) - row+=1 \ No newline at end of file + row+=1 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index fc9567ba..f44f18ce 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -1,7 +1,4 @@ -from customtkinter import StringVar, IntVar -from tkinter import font as tk_font -from languages import selectable_languages -from utils import get_key_by_value, callFunctionIfCallable +from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator @@ -24,22 +21,21 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): callFunctionIfCallable(config_window.CALLBACK_SET_UI_SCALING, value) def optionmenu_font_family_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_FONT_FAMILY, value) + callFunctionIfCallable(config_window.view_variable.CALLBACK_SET_FONT_FAMILY, value) def optionmenu_ui_language_callback(value): - value = get_key_by_value(selectable_languages, value) callFunctionIfCallable(config_window.CALLBACK_SET_UI_LANGUAGE, value) row=0 config_window.sb__transparency = createSettingBoxSlider( parent_widget=setting_box_wrapper, - label_text="Transparency", - desc_text="Change the window's transparency. 50% to 100%. (Default: 100%)", + for_var_label_text=config_window.view_variable.VAR_LABEL_TRANSPARENCY, + for_var_desc_text=config_window.view_variable.VAR_DESC_TRANSPARENCY, slider_attr_name="sb__transparency_slider", - slider_range=(50, 100), + slider_range=config_window.view_variable.SLIDER_RANGE_TRANSPARENCY, command=lambda value: slider_transparency_callback(value), - variable=IntVar(value=config.TRANSPARENCY), + variable=config_window.view_variable.VAR_TRANSPARENCY, ) config_window.sb__transparency.grid(row=row) row+=1 @@ -47,58 +43,55 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): config_window.sb__appearance_theme = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - label_text="Theme", - desc_text="Change the color theme from \"Light\" and \"Dark\". If you select \"System\", It will adjust based on your Windows theme. (Default: System)", + for_var_label_text=config_window.view_variable.VAR_LABEL_APPEARANCE_THEME, + for_var_desc_text=config_window.view_variable.VAR_DESC_APPEARANCE_THEME, optionmenu_attr_name="sb__optionmenu_appearance_theme", dropdown_menu_attr_name="sb__dropdown_appearance_theme", - dropdown_menu_values=["Light", "Dark", "System"], + dropdown_menu_values=config_window.view_variable.LIST_APPEARANCE_THEME, command=lambda value: optionmenu_appearance_theme_callback(value), - variable=StringVar(value=config.APPEARANCE_THEME) + variable=config_window.view_variable.VAR_APPEARANCE_THEME, ) config_window.sb__appearance_theme.grid(row=row) row+=1 + config_window.sb__ui_scaling = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - label_text="UI Size", - desc_text="(Default: 100%)", + for_var_label_text=config_window.view_variable.VAR_LABEL_UI_SCALING, + for_var_desc_text=config_window.view_variable.VAR_DESC_UI_SCALING, optionmenu_attr_name="sb__optionmenu_ui_scaling", dropdown_menu_attr_name="sb__dropdown_ui_scaling", - dropdown_menu_values=["80%", "90%", "100%", "110%", "120%"], + dropdown_menu_values=config_window.view_variable.LIST_UI_SCALING, command=lambda value: optionmenu_ui_scaling_callback(value), - variable=StringVar(value=config.UI_SCALING) + variable=config_window.view_variable.VAR_UI_SCALING, ) config_window.sb__ui_scaling.grid(row=row) row+=1 - # font_families = list(tk_font.families()) config_window.sb__font_family = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - label_text="Font Family", - desc_text="(Default: Yu Gothic UI)", + for_var_label_text=config_window.view_variable.VAR_LABEL_FONT_FAMILY, + for_var_desc_text=config_window.view_variable.VAR_DESC_FONT_FAMILY, optionmenu_attr_name="sb__optionmenu_font_family", - dropdown_menu_attr_name="sb__dropdown_font_family", - dropdown_menu_values=["Font A", "Font B"], - # dropdown_menu_values=font_families, + dropdown_menu_values=config_window.view_variable.LIST_FONT_FAMILY, command=lambda value: optionmenu_font_family_callback(value), - variable=StringVar(value=config.FONT_FAMILY) + variable=config_window.view_variable.VAR_FONT_FAMILY, ) config_window.sb__font_family.grid(row=row) row+=1 - selectable_languages_values = list(selectable_languages.values()) config_window.sb__ui_language = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - label_text="UI Language", - desc_text="(Default: English)", + for_var_label_text=config_window.view_variable.VAR_LABEL_UI_LANGUAGE, + for_var_desc_text=config_window.view_variable.VAR_DESC_UI_LANGUAGE, optionmenu_attr_name="sb__optionmenu_ui_language", dropdown_menu_attr_name="sb__dropdown_ui_language", - dropdown_menu_values=selectable_languages_values, + dropdown_menu_values=config_window.view_variable.LIST_UI_LANGUAGE, command=lambda value: optionmenu_ui_language_callback(value), - variable=StringVar(value=selectable_languages[config.UI_LANGUAGE]), + variable=config_window.view_variable.VAR_UI_LANGUAGE, ) config_window.sb__ui_language.grid(row=row) row+=1 \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index 7b5811c9..42f7d295 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -26,11 +26,11 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings): row=0 config_window.sb__auto_clear_message_box = createSettingBoxCheckbox( parent_widget=setting_box_wrapper, - label_text="Auto Clear The Message Box", - desc_text="Clear the message box after sending your message.", + for_var_label_text=config_window.view_variable.VAR_LABEL_ENABLE_AUTO_CLEAR_MESSAGE_BOX, + for_var_desc_text=config_window.view_variable.VAR_DESC_ENABLE_AUTO_CLEAR_MESSAGE_BOX, checkbox_attr_name="sb__checkbox_auto_clear_message_box", command=lambda: checkbox_auto_clear_message_box_callback(config_window.sb__checkbox_auto_clear_message_box), - is_checked=False + variable=config_window.view_variable.VAR_ENABLE_AUTO_CLEAR_MESSAGE_BOX, ) config_window.sb__auto_clear_message_box.grid(row=row) row+=1 @@ -38,11 +38,11 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings): config_window.sb__notice_xsoverlay = createSettingBoxCheckbox( parent_widget=setting_box_wrapper, - label_text="Notification XSOverlay (VR Only)", - desc_text="Notify received messages by using XSOverlay's notification feature.", + for_var_label_text=config_window.view_variable.VAR_LABEL_ENABLE_NOTICE_XSOVERLAY, + for_var_desc_text=config_window.view_variable.VAR_DESC_ENABLE_NOTICE_XSOVERLAY, checkbox_attr_name="sb__checkbox_notice_xsoverlay", command=lambda: checkbox_notice_xsoverlay_callback(config_window.sb__checkbox_notice_xsoverlay), - is_checked=False + variable=config_window.view_variable.VAR_ENABLE_NOTICE_XSOVERLAY, ) config_window.sb__notice_xsoverlay.grid(row=row) row+=1 @@ -50,12 +50,12 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings): config_window.sb__message_format = createSettingBoxEntry( parent_widget=setting_box_wrapper, - label_text="Message Format", - desc_text="You can change the decoration of the message you want to send. (Default: \"[message]([translation])\" )", + for_var_label_text=config_window.view_variable.VAR_LABEL_MESSAGE_FORMAT, + for_var_desc_text=config_window.view_variable.VAR_DESC_MESSAGE_FORMAT, entry_attr_name="sb__entry_message_format", entry_width=settings.uism.SB__ENTRY_WIDTH_250, entry_bind__Any_KeyRelease=lambda value: entry_message_format_callback(value), - entry_textvariable=StringVar(value=config.MESSAGE_FORMAT), + entry_textvariable=config_window.view_variable.VAR_MESSAGE_FORMAT, ) config_window.sb__message_format.grid(row=row) row+=1 \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index e4e550c2..6ff16118 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -6,7 +6,6 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator -from config import config def createSettingBox_Mic(setting_box_wrapper, config_window, settings): sbg = _SettingBoxGenerator(config_window, settings) @@ -69,28 +68,25 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): # Mic Host と Mic Device は一つの項目として引っ付ける予定 config_window.sb__mic_host = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - label_text="Mic Host", - desc_text="Select the mic host. (Default: ?)", + for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_HOST, + for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_HOST, optionmenu_attr_name="sb__optionmenu_mic_host", - dropdown_menu_attr_name="sb__dropdown_mic_host", - # dropdown_menu_values=model.getListInputHost(), - dropdown_menu_values=["host1", "host2", "host3"], + dropdown_menu_values=config_window.view_variable.LIST_MIC_HOST, command=lambda value: optionmenu_mic_host_callback(value), - variable=StringVar(value=config.CHOICE_MIC_HOST) + variable=config_window.view_variable.VAR_MIC_HOST, ) config_window.sb__mic_host.grid(row=row) row+=1 config_window.sb__mic_device = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - label_text="Mic Device", - desc_text="Select the mic devise. (Default: ?)", + for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_DEVICE, + for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_DEVICE, optionmenu_attr_name="sb__optionmenu_mic_device", dropdown_menu_attr_name="sb__dropdown_mic_device", - # dropdown_menu_values=model.getListInputDevice(), - dropdown_menu_values=["device1", "device2", "device3"], + dropdown_menu_values=config_window.view_variable.LIST_MIC_DEVICE, command=lambda value: optionmenu_input_mic_device_callback(value), - variable=StringVar(value=config.CHOICE_MIC_DEVICE) + variable=config_window.view_variable.VAR_MIC_DEVICE, ) config_window.sb__mic_device.grid(row=row) row+=1 @@ -98,16 +94,15 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): config_window.sb__mic_energy_threshold = createSettingBoxProgressbarXSlider( parent_widget=setting_box_wrapper, - label_text="Mic Energy Threshold", - desc_text="Slider to modify the threshold for activating voice input.\nPress the microphone button to start input and speak something, so you can adjust it while monitoring the actual volume. 0 to 2000 (Default: 300)", + for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_ENERGY_THRESHOLD, + for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_ENERGY_THRESHOLD, command=slider_input_mic_energy_threshold_callback, - variable=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), + variable=config_window.view_variable.VAR_MIC_ENERGY_THRESHOLD, entry_attr_name="sb__progressbar_x_slider__entry_mic_energy_threshold", slider_attr_name="progressbar_x_slider__slider_mic_energy_threshold", - slider_range=(0, config.MAX_MIC_ENERGY_THRESHOLD), - slider_number_of_steps=config.MAX_MIC_ENERGY_THRESHOLD, + slider_range=config_window.view_variable.SLIDER_RANGE_MIC_ENERGY_THRESHOLD, progressbar_attr_name="sb__progressbar_x_slider__progressbar_mic_energy_threshold", @@ -133,11 +128,11 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): # Mic Dynamic Energy Thresholdも上に引っ付ける予定 config_window.sb__mic_dynamic_energy_threshold = createSettingBoxCheckbox( parent_widget=setting_box_wrapper, - label_text="Mic Dynamic Energy Threshold", - desc_text="When this feature is selected, it will automatically adjust in a way that works well, based on the set Mic Energy Threshold.", + for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD, + for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD, checkbox_attr_name="sb__checkbox_mic_dynamic_energy_threshold", command=lambda: checkbox_input_mic_dynamic_energy_threshold_callback(config_window.sb__checkbox_mic_dynamic_energy_threshold), - is_checked=False + variable=config_window.view_variable.VAR_MIC_DYNAMIC_ENERGY_THRESHOLD ) config_window.sb__mic_dynamic_energy_threshold.grid(row=row) row+=1 @@ -146,55 +141,50 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): # 以下3つも一つの項目にまとめるかもしれない config_window.sb__mic_record_timeout = createSettingBoxEntry( parent_widget=setting_box_wrapper, - label_text="Mic Record Timeout", - desc_text="(Default: 3)", + for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_RECORD_TIMEOUT, + for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_RECORD_TIMEOUT, entry_attr_name="sb__entry_mic_record_timeout", entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_record_timeout_callback(value), - entry_textvariable=IntVar(value=config.INPUT_MIC_RECORD_TIMEOUT), + entry_textvariable=config_window.view_variable.VAR_MIC_RECORD_TIMEOUT, ) config_window.sb__mic_record_timeout.grid(row=row) row+=1 config_window.sb__mic_phrase_timeout = createSettingBoxEntry( parent_widget=setting_box_wrapper, - label_text="Mic Phrase Timeout", - desc_text="It will stop recording and send the recordings when the set second(s) is reached. (Default: 3)", + for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_PHRASE_TIMEOUT, + for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_PHRASE_TIMEOUT, entry_attr_name="sb__entry_mic_phrase_timeout", entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_phrase_timeout_callback(value), - entry_textvariable=IntVar(value=config.INPUT_MIC_PHRASE_TIMEOUT), + entry_textvariable=config_window.view_variable.VAR_MIC_PHRASE_TIMEOUT, ) config_window.sb__mic_phrase_timeout.grid(row=row) row+=1 config_window.sb__mic_max_phrases = createSettingBoxEntry( parent_widget=setting_box_wrapper, - label_text="Mic Max Phrases", - desc_text="It will stop recording and send the recordings when the set count of phrase(s) is reached. (Default: 10)", + for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_MAX_PHRASES, + for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_MAX_PHRASES, entry_attr_name="sb__entry_mic_max_phrases", entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_max_phrases_callback(value), - entry_textvariable=IntVar(value=config.INPUT_MIC_MAX_PHRASES), + entry_textvariable=config_window.view_variable.VAR_MIC_MAX_PHRASES, ) config_window.sb__mic_max_phrases.grid(row=row) row+=1 - # __________ + # # __________ - - if len(config.INPUT_MIC_WORD_FILTER) > 0: - entry_textvariable=StringVar(value=",".join(config.INPUT_MIC_WORD_FILTER)) - else: - entry_textvariable=None config_window.sb__mic_word_filter = createSettingBoxEntry( parent_widget=setting_box_wrapper, - label_text="Mic Word Filter", - desc_text="It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC", + for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_WORD_FILTER, + for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_WORD_FILTER, entry_attr_name="sb__entry_mic_word_filter", - entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_width=settings.uism.SB__ENTRY_WIDTH_300, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_word_filters_callback(value), - entry_textvariable=entry_textvariable, + entry_textvariable=config_window.view_variable.VAR_MIC_WORD_FILTER, ) config_window.sb__mic_word_filter.grid(row=row) row+=1 \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index a4237b82..f0da5ab4 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -63,14 +63,13 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): row=0 config_window.sb__speaker_device = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - label_text="Speaker Device", - desc_text="Select the speaker devise. (Default: ?)", + for_var_label_text=config_window.view_variable.VAR_LABEL_SPEAKER_DEVICE, + for_var_desc_text=config_window.view_variable.VAR_DESC_SPEAKER_DEVICE, optionmenu_attr_name="sb__optionmenu_speaker_device", dropdown_menu_attr_name="sb__dropdown_speaker_device", - # dropdown_menu_values=model.getListOutputDevice(), - dropdown_menu_values=["device1", "device2", "device3"], + dropdown_menu_values=config_window.view_variable.LIST_SPEAKER_DEVICE, command=lambda value: optionmenu_input_speaker_device_callback(value), - variable=StringVar(value=config.CHOICE_SPEAKER_DEVICE) + variable=config_window.view_variable.VAR_SPEAKER_DEVICE, ) config_window.sb__speaker_device.grid(row=row) row+=1 @@ -78,16 +77,15 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): config_window.sb__speaker_energy_threshold = createSettingBoxProgressbarXSlider( parent_widget=setting_box_wrapper, - label_text="Speaker Energy Threshold", - desc_text="Slider to modify the threshold for activating voice input.\nPress the headphones mark button to start input and speak something, so you can adjust it while monitoring the actual volume. 0 to 4000 (Default: 300)", + for_var_label_text=config_window.view_variable.VAR_LABEL_SPEAKER_ENERGY_THRESHOLD, + for_var_desc_text=config_window.view_variable.VAR_DESC_SPEAKER_ENERGY_THRESHOLD, command=slider_input_speaker_energy_threshold_callback, - variable=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), + variable=config_window.view_variable.VAR_SPEAKER_ENERGY_THRESHOLD, entry_attr_name="sb__progressbar_x_slider__entry_speaker_energy_threshold", slider_attr_name="progressbar_x_slider__slider_speaker_energy_threshold", - slider_range=(0, config.MAX_SPEAKER_ENERGY_THRESHOLD), - slider_number_of_steps=config.MAX_SPEAKER_ENERGY_THRESHOLD, + slider_range=config_window.view_variable.SLIDER_RANGE_SPEAKER_ENERGY_THRESHOLD, progressbar_attr_name="sb__progressbar_x_slider__progressbar_speaker_energy_threshold", @@ -113,11 +111,11 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): # Speaker Dynamic Energy Thresholdも上に引っ付ける予定 config_window.sb__speaker_dynamic_energy_threshold = createSettingBoxCheckbox( parent_widget=setting_box_wrapper, - label_text="Speaker Dynamic Energy Threshold", - desc_text="When this feature is selected, it will automatically adjust in a way that works well, based on the set Speaker Energy Threshold.", + for_var_label_text=config_window.view_variable.VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, + for_var_desc_text=config_window.view_variable.VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, checkbox_attr_name="sb__checkbox_speaker_dynamic_energy_threshold", command=lambda: checkbox_input_speaker_dynamic_energy_threshold_callback(config_window.sb__checkbox_speaker_dynamic_energy_threshold), - is_checked=False + variable=config_window.view_variable.VAR_MIC_DYNAMIC_ENERGY_THRESHOLD, ) config_window.sb__speaker_dynamic_energy_threshold.grid(row=row) row+=1 @@ -126,36 +124,36 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): # 以下3つも一つの項目にまとめるかもしれない config_window.sb__speaker_record_timeout = createSettingBoxEntry( parent_widget=setting_box_wrapper, - label_text="Speaker Record Timeout", - desc_text="(Default: 3)", + for_var_label_text=config_window.view_variable.VAR_LABEL_SPEAKER_RECORD_TIMEOUT, + for_var_desc_text=config_window.view_variable.VAR_DESC_SPEAKER_RECORD_TIMEOUT, entry_attr_name="sb__entry_speaker_record_timeout", entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_record_timeout_callback(value), - entry_textvariable=IntVar(value=config.INPUT_SPEAKER_RECORD_TIMEOUT), + entry_textvariable=config_window.view_variable.VAR_SPEAKER_RECORD_TIMEOUT, ) config_window.sb__speaker_record_timeout.grid(row=row) row+=1 config_window.sb__speaker_phrase_timeout = createSettingBoxEntry( parent_widget=setting_box_wrapper, - label_text="Speaker Phrase Timeout", - desc_text="It will stop recording and receive the recordings when the set second(s) is reached. (Default: 3)", + for_var_label_text=config_window.view_variable.VAR_LABEL_SPEAKER_PHRASE_TIMEOUT, + for_var_desc_text=config_window.view_variable.VAR_DESC_SPEAKER_PHRASE_TIMEOUT, entry_attr_name="sb__entry_speaker_phrase_timeout", entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_phrase_timeout_callback(value), - entry_textvariable=IntVar(value=config.INPUT_SPEAKER_PHRASE_TIMEOUT), + entry_textvariable=config_window.view_variable.VAR_SPEAKER_PHRASE_TIMEOUT, ) config_window.sb__speaker_phrase_timeout.grid(row=row) row+=1 config_window.sb__speaker_max_phrases = createSettingBoxEntry( parent_widget=setting_box_wrapper, - label_text="Speaker Max Phrases", - desc_text="It will stop recording and receive the recordings when the set count of phrase(s) is reached. (Default: 10)", + for_var_label_text=config_window.view_variable.VAR_LABEL_SPEAKER_MAX_PHRASES, + for_var_desc_text=config_window.view_variable.VAR_DESC_SPEAKER_MAX_PHRASES, entry_attr_name="sb__entry_speaker_max_phrases", entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_max_phrases_callback(value), - entry_textvariable=IntVar(value=config.INPUT_SPEAKER_MAX_PHRASES), + entry_textvariable=config_window.view_variable.VAR_SPEAKER_MAX_PHRASES, ) config_window.sb__speaker_max_phrases.grid(row=row) row+=1 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index 5c635edd..45d341d4 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -18,12 +18,12 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings): row=0 config_window.sb__deepl_authkey = createSettingBoxEntry( parent_widget=setting_box_wrapper, - label_text="DeepL Auth Key", - desc_text="", + for_var_label_text=config_window.view_variable.VAR_LABEL_DEEPL_AUTH_KEY, + for_var_desc_text=config_window.view_variable.VAR_DESC_DEEPL_AUTH_KEY, entry_attr_name="sb__deepl_authkey", entry_width=settings.uism.SB__ENTRY_WIDTH_300, entry_bind__Any_KeyRelease=lambda value: deepl_authkey_callback(value), - entry_textvariable=StringVar(value=config.AUTH_KEYS["DeepL(auth)"]), + entry_textvariable=config_window.view_variable.VAR_DEEPL_AUTH_KEY, ) config_window.sb__deepl_authkey.grid(row=row) row+=1 \ No newline at end of file diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 980a15be..2949a7e7 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -5,7 +5,7 @@ from customtkinter import CTkFrame from ..ui_utils import createButtonWithImage, getImagePath -def createMainWindowWidgets(vrct_gui, settings): +def createMainWindowWidgets(vrct_gui, settings, view_variable): vrct_gui.protocol("WM_DELETE_WINDOW", vrct_gui.quitVRCT) # self.IS_DEVELOPER_MODE = False diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 7468b3b9..836ca7c5 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -16,7 +16,7 @@ from .ui_utils import _setDefaultActiveTab class VRCT_GUI(CTk): def __init__(self): super().__init__() - self.settings = SimpleNamespace() + # self.settings = SimpleNamespace() self.YOUR_LANGUAGE = "Japanese\n(Japan)" self.TARGET_LANGUAGE = "English\n(United States)" @@ -29,11 +29,12 @@ class VRCT_GUI(CTk): self.CALLBACK_SELECTED_TAB_NO_3 = None - def createGUI(self, settings): + def createGUI(self, settings, view_variable): self.settings = settings + self.view_variable = view_variable - createMainWindowWidgets(vrct_gui=self, settings=self.settings.main) - self.config_window = ConfigWindow(vrct_gui=self, settings=self.settings.config_window) + createMainWindowWidgets(vrct_gui=self, settings=self.settings.main, view_variable=self.view_variable) + self.config_window = ConfigWindow(vrct_gui=self, settings=self.settings.config_window, view_variable=self.view_variable) # self.information_window = ToplevelWindowInformation(self) def startMainLoop(self): From a4507eea182ec2f86b3ba97ec9b19a23421fce53 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 2 Sep 2023 02:36:17 +0900 Subject: [PATCH 068/355] =?UTF-8?q?[Chore]=20Config=20Window:=20config.py,?= =?UTF-8?q?=20CTkScrollableDropdown=E3=81=AEimport=E6=96=87=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting_box_containers/_SettingBoxGenerator.py | 4 +--- .../createSettingBox_AdvancedSettings.py | 4 ---- .../setting_box_appearance/createSettingBox_Appearance.py | 2 -- .../setting_box_others/createSettingBox_Others.py | 4 ---- .../setting_box_transcription/createSettingBox_Mic.py | 3 --- .../setting_box_transcription/createSettingBox_Speaker.py | 4 ---- .../setting_box_translation/createSettingBox_Translation.py | 4 ---- 7 files changed, 1 insertion(+), 24 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index d6044900..3f91fa48 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -1,11 +1,9 @@ -from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar, END as CTK_END, StringVar, IntVar -from ctk_scrollable_dropdown import CTkScrollableDropdown +from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar, END as CTK_END from vrct_gui.ui_utils import createButtonWithImage from typing import Union - class _SettingBoxGenerator(): def __init__(self, config_window, settings): diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py index 9a5397be..41c57907 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py @@ -1,11 +1,7 @@ -from customtkinter import StringVar, IntVar - from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator -from config import config - def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settings): sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxEntry = sbg.createSettingBoxEntry diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index f44f18ce..800288a2 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -2,8 +2,6 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator -from config import config - def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index 42f7d295..b3c2b81e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -1,11 +1,7 @@ -from customtkinter import StringVar, IntVar - from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator -from config import config - def createSettingBox_Others(setting_box_wrapper, config_window, settings): sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxCheckbox = sbg.createSettingBoxCheckbox diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 6ff16118..583d192f 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -1,12 +1,9 @@ from time import sleep -from customtkinter import StringVar, IntVar - from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator - def createSettingBox_Mic(setting_box_wrapper, config_window, settings): sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index f0da5ab4..236e4c58 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -1,13 +1,9 @@ from time import sleep -from customtkinter import StringVar, IntVar - from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator -from config import config - def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index 45d341d4..1bdaa8b0 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -1,11 +1,7 @@ -from customtkinter import StringVar, IntVar - from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator -from config import config - def createSettingBox_Translation(setting_box_wrapper, config_window, settings): sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxEntry = sbg.createSettingBoxEntry From 66413a60dd172b9fd6c8a14afdeaefb5681546e6 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 2 Sep 2023 02:46:27 +0900 Subject: [PATCH 069/355] =?UTF-8?q?[Chore]=20view.py=E3=81=AE=E5=A4=89?= =?UTF-8?q?=E6=95=B0=E7=BE=A4=E3=81=AB=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0=E3=81=99=E3=82=8B=E3=81=AA=E3=81=A9?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/view.py b/view.py index 5f37da6a..248a8f3b 100644 --- a/view.py +++ b/view.py @@ -37,6 +37,8 @@ class View(): ) self.view_variable = SimpleNamespace( + # Config Window + # Appearance Tab VAR_LABEL_TRANSPARENCY=StringVar(value="Transparency"), VAR_DESC_TRANSPARENCY=StringVar(value="Change the window's transparency. 50% to 100%. (Default: 100%)"), SLIDER_RANGE_TRANSPARENCY=(50, 100), @@ -68,7 +70,7 @@ class View(): VAR_UI_LANGUAGE=StringVar(value=selectable_languages[config.UI_LANGUAGE]), - + # Translation Tab VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value="DeepL Auth Key"), VAR_DESC_DEEPL_AUTH_KEY=None, # VAR_DESC_DEEPL_AUTH_KEY=StringVar(value=""), @@ -76,7 +78,7 @@ class View(): VAR_DEEPL_AUTH_KEY=StringVar(value=config.AUTH_KEYS["DeepL(auth)"]), - + # Transcription Tab (Mic) VAR_LABEL_MIC_HOST=StringVar(value="Mic Host"), VAR_DESC_MIC_HOST=StringVar(value="Select the mic host. (Default: ?)"), LIST_MIC_HOST=[], # model.getListInputHost(), @@ -115,22 +117,13 @@ class View(): CALLBACK_SET_MIC_MAX_PHRASES=None, VAR_MIC_MAX_PHRASES=IntVar(value=config.INPUT_MIC_MAX_PHRASES), - VAR_LABEL_MIC_WORD_FILTER=StringVar(value="Mic Word Filter"), VAR_DESC_MIC_WORD_FILTER=StringVar(value="It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC"), CALLBACK_SET_MIC_WORD_FILTER=None, VAR_MIC_WORD_FILTER=StringVar(value=",".join(config.INPUT_MIC_WORD_FILTER) if len(config.INPUT_MIC_WORD_FILTER) > 0 else ""), - - - - - - - - - + # Transcription Tab (Speaker) VAR_LABEL_SPEAKER_DEVICE=StringVar(value="Speaker Device"), VAR_DESC_SPEAKER_DEVICE=StringVar(value="Select the speaker devise. (Default: ?)"), LIST_SPEAKER_DEVICE=[], # model.getListOutputDevice(), @@ -164,10 +157,7 @@ class View(): VAR_SPEAKER_MAX_PHRASES=IntVar(value=config.INPUT_SPEAKER_MAX_PHRASES), - - - - + # Others Tab VAR_LABEL_ENABLE_AUTO_CLEAR_MESSAGE_BOX=StringVar(value="Auto Clear The Message Box"), VAR_DESC_ENABLE_AUTO_CLEAR_MESSAGE_BOX=StringVar(value="Clear the message box after sending your message."), CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX=None, @@ -184,9 +174,7 @@ class View(): VAR_MESSAGE_FORMAT=StringVar(value=config.MESSAGE_FORMAT), - - - + # Advanced Settings Tab VAR_LABEL_OSC_IP_ADDRESS=StringVar(value="OSC IP Address"), VAR_DESC_OSC_IP_ADDRESS=StringVar(value="(Default: 127.0.0.1)"), CALLBACK_SET_OSC_IP_ADDRESS=None, @@ -196,7 +184,6 @@ class View(): VAR_DESC_OSC_PORT=StringVar(value="(Default: 9000)"), CALLBACK_SET_OSC_PORT=None, VAR_OSC_PORT=IntVar(value=config.OSC_PORT), - ) From cc94added0b22fa1f58093f61740c89cf447d9bd Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 2 Sep 2023 03:42:06 +0900 Subject: [PATCH 070/355] =?UTF-8?q?Main=20Window:=20Sidebar=20features?= =?UTF-8?q?=E7=B3=BB=E3=81=AECALLBACK=E5=A4=89=E6=95=B0=E3=82=92view.py?= =?UTF-8?q?=E3=81=AB=E7=A7=BB=E5=8B=95=E3=80=82=E5=87=A6=E7=90=86=E3=82=82?= =?UTF-8?q?=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF=E3=82=BF=E3=83=AA=E3=83=B3?= =?UTF-8?q?=E3=82=B0=E3=80=82=20=E3=81=9D=E3=81=AECALLBACK=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E3=81=AE=E5=BC=95=E6=95=B0=E3=81=A7True/False?= =?UTF-8?q?=E3=81=AE=E5=80=A4=E3=82=92=E5=8F=97=E3=81=91=E5=8F=96=E3=82=8C?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=9F?= =?UTF-8?q?=E3=81=AE=E3=81=A7=E3=80=81view.py=E3=81=8C=E6=8F=90=E4=BE=9B?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=81=84=E3=81=9FgetButtonStatus=E7=B3=BB?= =?UTF-8?q?=E9=96=A2=E6=95=B0=E3=82=82=E5=89=8A=E9=99=A4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 16 ++++----- view.py | 21 +++++++----- .../main_window/createMainWindowWidgets.py | 2 +- .../main_window/widgets/create_sidebar.py | 33 +++++-------------- vrct_gui/vrct_gui.py | 10 +++--- 5 files changed, 35 insertions(+), 47 deletions(-) diff --git a/main.py b/main.py index 03774d60..58e3fd49 100644 --- a/main.py +++ b/main.py @@ -169,16 +169,16 @@ def callbackSelectedTabNo3(): # command func -def callbackToggleTranslation(): - config.ENABLE_TRANSLATION = view.getTranslationButtonStatus() +def callbackToggleTranslation(is_turned_on): + config.ENABLE_TRANSLATION = is_turned_on if config.ENABLE_TRANSLATION is True: view.printToTextbox_enableTranslation() else: view.printToTextbox_disableTranslation() -def callbackToggleTranscriptionSend(): +def callbackToggleTranscriptionSend(is_turned_on): view.setMainWindowAllWidgetsStatusToDisabled() - config.ENABLE_TRANSCRIPTION_SEND = view.getTranscriptionSendButtonStatus() + config.ENABLE_TRANSCRIPTION_SEND = is_turned_on if config.ENABLE_TRANSCRIPTION_SEND is True: view.printToTextbox_enableTranscriptionSend() th_startTranscriptionSendMessage = Thread(target=startTranscriptionSendMessage) @@ -190,9 +190,9 @@ def callbackToggleTranscriptionSend(): th_stopTranscriptionSendMessage.daemon = True th_stopTranscriptionSendMessage.start() -def callbackToggleTranscriptionReceive(): +def callbackToggleTranscriptionReceive(is_turned_on): view.setMainWindowAllWidgetsStatusToDisabled() - config.ENABLE_TRANSCRIPTION_RECEIVE = view.getTranscriptionReceiveButtonStatus() + config.ENABLE_TRANSCRIPTION_RECEIVE = is_turned_on if config.ENABLE_TRANSCRIPTION_RECEIVE is True: view.printToTextbox_enableTranscriptionReceive() th_startTranscriptionReceiveMessage = Thread(target=startTranscriptionReceiveMessage) @@ -204,8 +204,8 @@ def callbackToggleTranscriptionReceive(): th_stopTranscriptionReceiveMessage.daemon = True th_stopTranscriptionReceiveMessage.start() -def callbackToggleForeground(): - config.ENABLE_FOREGROUND = view.getForegroundButtonStatus() +def callbackToggleForeground(is_turned_on): + config.ENABLE_FOREGROUND = is_turned_on if config.ENABLE_FOREGROUND is True: view.printToTextbox_enableForeground() view.foregroundOn() diff --git a/view.py b/view.py index 248a8f3b..1c0c7abc 100644 --- a/view.py +++ b/view.py @@ -37,6 +37,18 @@ class View(): ) self.view_variable = SimpleNamespace( + + # Main Window + CALLBACK_TOGGLE_TRANSLATION=None, + CALLBACK_TOGGLE_TRANSCRIPTION_SEND=None, + CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE=None, + CALLBACK_TOGGLE_FOREGROUND=None, + # CALLBACK_SELECTED_TAB_NO_1=None, + # CALLBACK_SELECTED_TAB_NO_2=None, + # CALLBACK_SELECTED_TAB_NO_3=None, + + + # Config Window # Appearance Tab VAR_LABEL_TRANSPARENCY=StringVar(value="Transparency"), @@ -310,15 +322,6 @@ class View(): - def getTranslationButtonStatus(self): - return vrct_gui.translation_switch_box.get() - def getTranscriptionSendButtonStatus(self): - return vrct_gui.transcription_send_switch_box.get() - def getTranscriptionReceiveButtonStatus(self): - return vrct_gui.transcription_receive_switch_box.get() - def getForegroundButtonStatus(self): - return vrct_gui.foreground_switch_box.get() - def printToTextbox_enableTranslation(self): self._printToTextbox_Info("翻訳機能をONにしました") diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 2949a7e7..980a15be 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -5,7 +5,7 @@ from customtkinter import CTkFrame from ..ui_utils import createButtonWithImage, getImagePath -def createMainWindowWidgets(vrct_gui, settings, view_variable): +def createMainWindowWidgets(vrct_gui, settings): vrct_gui.protocol("WM_DELETE_WINDOW", vrct_gui.quitVRCT) # self.IS_DEVELOPER_MODE = False diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 86e6b75f..9396d336 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -2,48 +2,33 @@ from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkSwitch, from ...ui_utils import getImageFileFromUiUtils, openImageKeepAspectRatio, retag, getLatestHeight, bindEnterAndLeaveColor, bindButtonPressColor, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressAndReleaseFunction, bindButtonFunctionAndColor, switchActiveTabAndPassiveTab, switchTabsColor -from time import sleep +from utils import callFunctionIfCallable def createSidebar(settings, main_window): - from vrct_gui import vrct_gui - changeMainWindowWidgetsStatus = vrct_gui.changeMainWindowWidgetsStatus - - - - def toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, mark): mark.place(relx=0.85) if is_turned_on else mark.place(relx=-1) def toggleTranslationFeature(): - if callable(main_window.CALLBACK_TOGGLE_TRANSLATION) is True: - main_window.CALLBACK_TOGGLE_TRANSLATION() - is_turned_on = getattr(main_window, "translation_switch_box").get() - print(is_turned_on) + is_turned_on = main_window.translation_switch_box.get() + callFunctionIfCallable(main_window.CALLBACK_TOGGLE_TRANSLATION, is_turned_on) toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.translation_selected_mark) def toggleTranscriptionSendFeature(): - if callable(main_window.CALLBACK_TOGGLE_TRANSCRIPTION_SEND) is True: - main_window.CALLBACK_TOGGLE_TRANSCRIPTION_SEND() - is_turned_on = getattr(main_window, "transcription_send_switch_box").get() - print(is_turned_on) + is_turned_on = main_window.transcription_send_switch_box.get() + callFunctionIfCallable(main_window.CALLBACK_TOGGLE_TRANSCRIPTION_SEND, is_turned_on) toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_send_selected_mark) def toggleTranscriptionReceiveFeature(): - if callable(main_window.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE) is True: - main_window.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE() - is_turned_on = getattr(main_window, "transcription_receive_switch_box").get() - print(is_turned_on) + is_turned_on = main_window.transcription_receive_switch_box.get() + callFunctionIfCallable(main_window.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE, is_turned_on) toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_receive_selected_mark) - def toggleForegroundFeature(): - if callable(main_window.CALLBACK_TOGGLE_FOREGROUND) is True: - main_window.CALLBACK_TOGGLE_FOREGROUND() - is_turned_on = getattr(main_window, "foreground_switch_box").get() - print(is_turned_on) + is_turned_on = main_window.foreground_switch_box.get() + callFunctionIfCallable(main_window.CALLBACK_TOGGLE_FOREGROUND, is_turned_on) toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.foreground_selected_mark) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 836ca7c5..5874b318 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -20,10 +20,10 @@ class VRCT_GUI(CTk): self.YOUR_LANGUAGE = "Japanese\n(Japan)" self.TARGET_LANGUAGE = "English\n(United States)" - self.CALLBACK_TOGGLE_TRANSLATION = None - self.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = None - self.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = None - self.CALLBACK_TOGGLE_FOREGROUND = None + # self.CALLBACK_TOGGLE_TRANSLATION = None + # self.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = None + # self.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = None + # self.CALLBACK_TOGGLE_FOREGROUND = None self.CALLBACK_SELECTED_TAB_NO_1 = None self.CALLBACK_SELECTED_TAB_NO_2 = None self.CALLBACK_SELECTED_TAB_NO_3 = None @@ -33,7 +33,7 @@ class VRCT_GUI(CTk): self.settings = settings self.view_variable = view_variable - createMainWindowWidgets(vrct_gui=self, settings=self.settings.main, view_variable=self.view_variable) + createMainWindowWidgets(vrct_gui=self, settings=self.settings.main) self.config_window = ConfigWindow(vrct_gui=self, settings=self.settings.config_window, view_variable=self.view_variable) # self.information_window = ToplevelWindowInformation(self) From fa82961eb33f9f90f55ce64ef87b82f298121a0a Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 2 Sep 2023 04:19:35 +0900 Subject: [PATCH 071/355] =?UTF-8?q?Main=20Window:=20Language=20Preset=20Ta?= =?UTF-8?q?b=20Callback=E9=96=A2=E6=95=B0=E7=B3=BB=E3=80=82=E5=A4=89?= =?UTF-8?q?=E6=95=B0=E3=82=92view.py=E3=81=B8=E7=A7=BB=E5=8B=95=E3=80=82?= =?UTF-8?q?=20Callback=E6=99=82=E3=81=AB=E5=80=A4=E3=82=92=E5=8F=97?= =?UTF-8?q?=E3=81=91=E5=8F=96=E3=82=8C=E3=82=8B=E3=81=AE=E3=81=A7=E3=80=81?= =?UTF-8?q?tab=E7=95=AA=E5=8F=B7=E3=82=92=E3=82=82=E3=82=89=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E4=B8=80=E3=81=A4=E3=81=AE=E9=96=A2=E6=95=B0=E3=81=A7?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=81=A7=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 39 ++----------------- view.py | 9 ++--- .../main_window/widgets/create_sidebar.py | 19 ++------- vrct_gui/vrct_gui.py | 8 ---- 4 files changed, 9 insertions(+), 66 deletions(-) diff --git a/main.py b/main.py index 58e3fd49..6fa4987e 100644 --- a/main.py +++ b/main.py @@ -122,8 +122,8 @@ def setTargetLanguageAndCountry(select): config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) -def callbackSelectedTabNo1(): - config.SELECTED_TAB_NO = "1" +def callbackSelectedLanguagePresetTab(selected_tab_no): + config.SELECTED_TAB_NO = selected_tab_no view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) languages = config.SELECTED_TAB_YOUR_LANGUAGES select = languages[config.SELECTED_TAB_NO] @@ -137,37 +137,6 @@ def callbackSelectedTabNo1(): config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) -def callbackSelectedTabNo2(): - config.SELECTED_TAB_NO = "2" - view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) - languages = config.SELECTED_TAB_YOUR_LANGUAGES - select = languages[config.SELECTED_TAB_NO] - language, country = model.getLanguageAndCountry(select) - config.SOURCE_LANGUAGE = language - config.SOURCE_COUNTRY = country - languages = config.SELECTED_TAB_TARGET_LANGUAGES - select = languages[config.SELECTED_TAB_NO] - language, country = model.getLanguageAndCountry(select) - config.TARGET_LANGUAGE = language - config.TARGET_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - -def callbackSelectedTabNo3(): - config.SELECTED_TAB_NO = "3" - view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) - languages = config.SELECTED_TAB_YOUR_LANGUAGES - select = languages[config.SELECTED_TAB_NO] - language, country = model.getLanguageAndCountry(select) - config.SOURCE_LANGUAGE = language - config.SOURCE_COUNTRY = country - languages = config.SELECTED_TAB_TARGET_LANGUAGES - select = languages[config.SELECTED_TAB_NO] - language, country = model.getLanguageAndCountry(select) - config.TARGET_LANGUAGE = language - config.TARGET_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - - # command func def callbackToggleTranslation(is_turned_on): config.ENABLE_TRANSLATION = is_turned_on @@ -404,9 +373,7 @@ view.register( "callback_target_language": setTargetLanguageAndCountry, "values": model.getListLanguageAndCountry(), - "callback_selected_tab_no_1": callbackSelectedTabNo1, - "callback_selected_tab_no_2": callbackSelectedTabNo2, - "callback_selected_tab_no_3": callbackSelectedTabNo3, + "callback_selected_language_preset_tab": callbackSelectedLanguagePresetTab, }, entry_message_box_commands={ diff --git a/view.py b/view.py index 1c0c7abc..0f425c36 100644 --- a/view.py +++ b/view.py @@ -43,9 +43,8 @@ class View(): CALLBACK_TOGGLE_TRANSCRIPTION_SEND=None, CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE=None, CALLBACK_TOGGLE_FOREGROUND=None, - # CALLBACK_SELECTED_TAB_NO_1=None, - # CALLBACK_SELECTED_TAB_NO_2=None, - # CALLBACK_SELECTED_TAB_NO_3=None, + + CALLBACK_SELECTED_LANGUAGE_PRESET_TAB=None, @@ -215,9 +214,7 @@ class View(): vrct_gui.sqls__optionmenu_target_language.configure(command=language_presets["callback_target_language"]) vrct_gui.sqls__optionmenu_target_language.configure(variable=StringVar(value=config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO])) - vrct_gui.CALLBACK_SELECTED_TAB_NO_1 = language_presets["callback_selected_tab_no_1"] - vrct_gui.CALLBACK_SELECTED_TAB_NO_2 = language_presets["callback_selected_tab_no_2"] - vrct_gui.CALLBACK_SELECTED_TAB_NO_3 = language_presets["callback_selected_tab_no_3"] + vrct_gui.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets["callback_selected_language_preset_tab"] vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 9396d336..bbf3fb8d 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -99,34 +99,21 @@ def createSidebar(settings, main_window): - - - - def switchToPreset1(e): print("1") - if callable(main_window.CALLBACK_SELECTED_TAB_NO_1) is True: - main_window.CALLBACK_SELECTED_TAB_NO_1() - # main_window.YOUR_LANGUAGE = "Japanese\n(Japan)" - # main_window.TARGET_LANGUAGE = "English\n(United States)" + callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "1") target_active_widget = getattr(main_window, "sqls__presets_button_1") switchPresetTabFunction(target_active_widget) def switchToPreset2(e): print("2") - if callable(main_window.CALLBACK_SELECTED_TAB_NO_2) is True: - main_window.CALLBACK_SELECTED_TAB_NO_2() - # main_window.YOUR_LANGUAGE = "English\n(United States)" - # main_window.TARGET_LANGUAGE = "Japanese\n(Japan)" + callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "2") target_active_widget = getattr(main_window, "sqls__presets_button_2") switchPresetTabFunction(target_active_widget) def switchToPreset3(e): print("3") - if callable(main_window.CALLBACK_SELECTED_TAB_NO_3) is True: - main_window.CALLBACK_SELECTED_TAB_NO_3() - # main_window.YOUR_LANGUAGE = "Japanese\n(Japan)" - # main_window.TARGET_LANGUAGE = "Chinese, Cantonese\n(Traditional Hong Kong)" + callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "3") target_active_widget = getattr(main_window, "sqls__presets_button_3") switchPresetTabFunction(target_active_widget) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 5874b318..f0871ace 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -20,14 +20,6 @@ class VRCT_GUI(CTk): self.YOUR_LANGUAGE = "Japanese\n(Japan)" self.TARGET_LANGUAGE = "English\n(United States)" - # self.CALLBACK_TOGGLE_TRANSLATION = None - # self.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = None - # self.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = None - # self.CALLBACK_TOGGLE_FOREGROUND = None - self.CALLBACK_SELECTED_TAB_NO_1 = None - self.CALLBACK_SELECTED_TAB_NO_2 = None - self.CALLBACK_SELECTED_TAB_NO_3 = None - def createGUI(self, settings, view_variable): self.settings = settings From ce65a36b8dc52b706bcb9c7b126799e8c5e97b74 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 2 Sep 2023 05:27:52 +0900 Subject: [PATCH 072/355] =?UTF-8?q?Main=20Window:=20Language=20Setting?= =?UTF-8?q?=E3=81=AE=E5=A4=89=E6=95=B0YOUR=5FLANGUAGE=E3=81=A8TARGET=5FLAN?= =?UTF-8?q?GUAGE=E3=82=92view.py=E3=81=AB=E7=A7=BB=E5=8B=95=E3=80=82=20tab?= =?UTF-8?q?=E5=88=87=E3=82=8A=E6=9B=BF=E3=82=8F=E3=82=8A=E6=99=82=E3=81=AB?= =?UTF-8?q?=E3=80=81UI=E5=81=B4=E3=81=A7variable=E3=82=92=E3=81=AA?= =?UTF-8?q?=E3=81=9C=E3=81=8B=E6=96=B0=E3=81=9F=E3=81=AB=E3=82=BB=E3=83=83?= =?UTF-8?q?=E3=83=88=E3=81=97=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE=E3=81=A7?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3(set=E9=96=A2=E6=95=B0=E3=82=92=E4=BD=BF?= =?UTF-8?q?=E3=81=A3=E3=81=A6)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 13 ++++++++----- vrct_gui/main_window/createMainWindowWidgets.py | 9 --------- vrct_gui/main_window/widgets/create_sidebar.py | 8 ++++---- vrct_gui/vrct_gui.py | 4 ---- 4 files changed, 12 insertions(+), 22 deletions(-) diff --git a/view.py b/view.py index 0f425c36..5c113939 100644 --- a/view.py +++ b/view.py @@ -37,14 +37,17 @@ class View(): ) self.view_variable = SimpleNamespace( - # Main Window + # Sidebar Features CALLBACK_TOGGLE_TRANSLATION=None, CALLBACK_TOGGLE_TRANSCRIPTION_SEND=None, CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE=None, CALLBACK_TOGGLE_FOREGROUND=None, + # Language Settings CALLBACK_SELECTED_LANGUAGE_PRESET_TAB=None, + VAR_YOUR_LANGUAGE = StringVar(value="Japanese\n(Japan)"), + VAR_TARGET_LANGUAGE = StringVar(value="English\n(United States)"), @@ -241,6 +244,8 @@ class View(): self.view_variable.CALLBACK_SET_FONT_FAMILY = config_window["callback_set_font_family"] + + # vrct_gui.config_window.sb__optionmenu_font_family.configure(values=self.view_variable.LIST_FONT_FAMILY) # self.view_variable.VAR_FONT_FAMILY = StringVar(value=config.FONT_FAMILY) @@ -314,10 +319,8 @@ class View(): def updateGuiVariableByPresetTabNo(self, tab_no:str): - vrct_gui.YOUR_LANGUAGE = config.SELECTED_TAB_YOUR_LANGUAGES[tab_no] - vrct_gui.TARGET_LANGUAGE = config.SELECTED_TAB_TARGET_LANGUAGES[tab_no] - - + self.view_variable.VAR_YOUR_LANGUAGE.set(config.SELECTED_TAB_YOUR_LANGUAGES[tab_no]) + self.view_variable.VAR_TARGET_LANGUAGE.set(config.SELECTED_TAB_TARGET_LANGUAGES[tab_no]) def printToTextbox_enableTranslation(self): diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 980a15be..536aba6c 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -8,14 +8,6 @@ from ..ui_utils import createButtonWithImage, getImagePath def createMainWindowWidgets(vrct_gui, settings): vrct_gui.protocol("WM_DELETE_WINDOW", vrct_gui.quitVRCT) - # self.IS_DEVELOPER_MODE = False - # self.IS_DEVELOPER_MODE = True - - - - - # self.YOUR_LANGUAGE = "Japanese\n(Japan)" - # self.TARGET_LANGUAGE = "English\n(United States)" vrct_gui.iconbitmap(getImagePath("app.ico")) vrct_gui.title("VRCT") @@ -27,7 +19,6 @@ def createMainWindowWidgets(vrct_gui, settings): vrct_gui.grid_columnconfigure(1, weight=1) vrct_gui.configure(fg_color="#ff7f50") - # return # Main Container diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index bbf3fb8d..d69632d5 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -93,8 +93,8 @@ def createSidebar(settings, main_window): switchActiveAndPassivePresetsTabsColor(target_active_widget) switchActiveTabAndPassiveTab(target_active_widget, main_window.current_active_preset_tab, main_window.current_active_preset_tab.passive_function, settings.ctm.SQLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SQLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR) - main_window.sqls__optionmenu_your_language.configure(variable=StringVar(value=main_window.YOUR_LANGUAGE)) - main_window.sqls__optionmenu_target_language.configure(variable=StringVar(value=main_window.TARGET_LANGUAGE)) + main_window.sqls__optionmenu_your_language.set(main_window.view_variable.VAR_YOUR_LANGUAGE.get()) + main_window.sqls__optionmenu_target_language.set(main_window.view_variable.VAR_TARGET_LANGUAGE.get()) main_window.current_active_preset_tab = target_active_widget @@ -499,7 +499,7 @@ def createSidebar(settings, main_window): optionmenu_attr_name="sqls__optionmenu_your_language", dropdown_menu_attr_name="sqls__dropdown_menu_your_language", dropdown_menu_values=["1""2","pppp\npppp"], - variable=StringVar(value=main_window.YOUR_LANGUAGE) + variable=main_window.view_variable.VAR_YOUR_LANGUAGE ) main_window.sqls__box_your_language.grid(row=2, column=0, padx=0, pady=(settings.uism.SQLS__BOX_TOP_PADY,0),sticky="ew") @@ -549,7 +549,7 @@ def createSidebar(settings, main_window): optionmenu_attr_name="sqls__optionmenu_target_language", dropdown_menu_attr_name="sqls__dropdown_menu_target_language", dropdown_menu_values=["1""2","pppp\npppp2"], - variable=StringVar(value=main_window.TARGET_LANGUAGE) + variable=main_window.view_variable.VAR_TARGET_LANGUAGE ) main_window.sqls__box_target_language.grid(row=4, column=0, padx=0, pady=(0,0),sticky="ew") diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index f0871ace..89e7804c 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -16,10 +16,6 @@ from .ui_utils import _setDefaultActiveTab class VRCT_GUI(CTk): def __init__(self): super().__init__() - # self.settings = SimpleNamespace() - self.YOUR_LANGUAGE = "Japanese\n(Japan)" - self.TARGET_LANGUAGE = "English\n(United States)" - def createGUI(self, settings, view_variable): self.settings = settings From 3c172db7d1975b546ff6071fb76afb646c3a8bbf Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 2 Sep 2023 06:02:29 +0900 Subject: [PATCH 073/355] [Add] model logger func --- config.py | 11 +++++++++++ main.py | 10 ++++++++++ model.py | 19 +++++++++++++++++++ 3 files changed, 40 insertions(+) diff --git a/config.py b/config.py index 1e86fa4a..312ceb4a 100644 --- a/config.py +++ b/config.py @@ -442,6 +442,16 @@ class Config: self._SELECTED_TAB_TARGET_LANGUAGES = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property + def ENABLE_LOGGER(self): + return self._ENABLE_LOGGER + + @ENABLE_LOGGER.setter + def ENABLE_LOGGER(self, value): + if type(value) is bool: + self._ENABLE_LOGGER = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + # Config Window @property def IS_CONFIG_WINDOW_COMPACT_MODE(self): @@ -515,6 +525,7 @@ class Config: "2":"English\n(United States)", "3":"English\n(United States)", } + self._ENABLE_LOGGER = False # Config Window self._IS_CONFIG_WINDOW_COMPACT_MODE = False diff --git a/main.py b/main.py index c939d26b..028ee6d9 100644 --- a/main.py +++ b/main.py @@ -29,6 +29,8 @@ def sendMicMessage(message): view.printToTextbox_OSCError() view.printToTextbox_SentMessage(message, translation) + if config.ENABLE_LOGGER is True: + model.logger.info(f"[SEND] {message} ({translation})") def startTranscriptionSendMessage(): model.startMicTranscript(sendMicMessage) @@ -55,6 +57,8 @@ def receiveSpeakerMessage(message): xsoverlay_message = xsoverlay_message.replace("[translation]", translation) model.notificationXSOverlay(xsoverlay_message) view.printToTextbox_ReceivedMessage(message, translation) + if config.ENABLE_LOGGER is True: + model.logger.info(f"[RECEIVE] {message} ({translation})") def startTranscriptionReceiveMessage(): model.startSpeakerTranscript(receiveSpeakerMessage) @@ -88,6 +92,8 @@ def sendChatMessage(message): # update textbox message log view.printToTextbox_SentMessage(message, translation) + if config.ENABLE_LOGGER is True: + model.logger.info(f"[SEND] {message} ({translation})") # delete message in entry message box if config.ENABLE_AUTO_CLEAR_MESSAGE_BOX is True: @@ -386,6 +392,10 @@ model.checkOSCStarted() # check Software Updated model.checkSoftwareUpdated() +# init logger +if config.ENABLE_LOGGER is True: + model.startLogger() + # set UI and callback view.register( sidebar_features={ diff --git a/model.py b/model.py index 19bb3820..f2c63a25 100644 --- a/model.py +++ b/model.py @@ -1,3 +1,6 @@ +from os import makedirs +from datetime import datetime +from logging import getLogger, FileHandler, Formatter, INFO from time import sleep from queue import Queue from threading import Thread, Event @@ -49,6 +52,7 @@ class Model: return cls._instance def init(self): + self.logger = None self.mic_energy_recorder = None self.mic_energy_plot_progressbar = None self.speaker_energy_get_progressbar = None @@ -77,6 +81,21 @@ class Model: config.AUTH_KEYS = auth_keys return result + def startLogger(self): + makedirs("./logs", exist_ok=True) + logger = getLogger() + logger.setLevel(INFO) + file_name = f"./logs/{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log" + file_handler = FileHandler(file_name, encoding="utf-8", delay=True) + formatter = Formatter("[%(asctime)s] %(message)s") + file_handler.setFormatter(formatter) + logger.addHandler(file_handler) + self.logger = logger + + def stopLogger(self): + self.logger.disabled = True + self.logger = None + @staticmethod def getListLanguageAndCountry(): langs = [] From 53356dd58cd3126b73fed660a8ac310461386539 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 2 Sep 2023 14:39:35 +0900 Subject: [PATCH 074/355] =?UTF-8?q?[Chore]=20Main=20Window:=20side=20bar?= =?UTF-8?q?=20features'=20labels=20VRCT=201.x=E7=B3=BB=E3=81=A7=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=81=9F=E8=A1=A8=E8=A8=98=E3=81=AB=E4=B8=80=E6=97=A6?= =?UTF-8?q?=E6=88=BB=E3=81=97=E3=81=9F=E3=80=82=E3=81=9D=E3=82=82=E3=81=9D?= =?UTF-8?q?=E3=82=82Speaker2chatbox=E3=81=A8=E3=81=97=E3=81=A6=E3=81=9F?= =?UTF-8?q?=E3=81=AE=E3=81=AFtypo=E3=81=A7=E3=81=97=E3=81=9F=E3=80=82?= =?UTF-8?q?=E6=AD=A3=E3=81=97=E3=81=8F=E3=81=AFSpeaker2Log=E3=81=A7?= =?UTF-8?q?=E3=81=99=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/main_window/widgets/create_sidebar.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index d69632d5..5bdb4721 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -248,7 +248,7 @@ def createSidebar(settings, main_window): "label_attr_name": "label_transcription_send", "compact_mode_icon_attr_name": "transcription_send_compact_mode_icon", "selected_mark_attr_name": "transcription_send_selected_mark", - "text": "Voice2chatbox", + "text": "Voice2Chatbox", "icon_file_name": settings.image_filename.MIC_ICON, }, { @@ -259,7 +259,7 @@ def createSidebar(settings, main_window): "label_attr_name": "label_transcription_receive", "compact_mode_icon_attr_name": "transcription_receive_compact_mode_icon", "selected_mark_attr_name": "transcription_receive_selected_mark", - "text": "Speaker2chatbox", + "text": "Speaker2Log", "icon_file_name": settings.image_filename.HEADPHONES_ICON, }, { From 01245d9c630317a13a3a17329b32f850cf1540b5 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 2 Sep 2023 18:44:48 +0900 Subject: [PATCH 075/355] =?UTF-8?q?Main=20Window:=20Sidebar=20Features?= =?UTF-8?q?=E7=B3=BB=E3=81=AELABEL=E5=A4=89=E6=95=B0=E3=82=92=E4=BD=9C?= =?UTF-8?q?=E6=88=90=E3=80=82=20=E4=B8=8D=E8=A6=81=E3=81=AA=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E7=A7=BB=E5=8B=95=E3=81=A8=E5=89=8A=E9=99=A4:=20switc?= =?UTF-8?q?h=20toggle=E9=96=A2=E6=95=B0=E3=82=92lambda=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=A6=E4=BD=9C=E6=88=90=E6=99=82=E3=81=AB=E3=81=9D=E3=81=AE?= =?UTF-8?q?=E3=81=BE=E3=81=BE=E6=8C=87=E5=AE=9A=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 7 ++++ .../main_window/widgets/create_sidebar.py | 33 ++++++------------- 2 files changed, 17 insertions(+), 23 deletions(-) diff --git a/view.py b/view.py index 5c113939..2dfc44ce 100644 --- a/view.py +++ b/view.py @@ -39,9 +39,16 @@ class View(): self.view_variable = SimpleNamespace( # Main Window # Sidebar Features + VAR_LABEL_TRANSLATION=StringVar(value="Translation"), CALLBACK_TOGGLE_TRANSLATION=None, + + VAR_LABEL_TRANSCRIPTION_SEND=StringVar(value="Voice2Chatbox"), CALLBACK_TOGGLE_TRANSCRIPTION_SEND=None, + + VAR_LABEL_TRANSCRIPTION_RECEIVE=StringVar(value="Speaker2Log"), CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE=None, + + VAR_LABEL_FOREGROUND=StringVar(value="Foreground"), CALLBACK_TOGGLE_FOREGROUND=None, # Language Settings diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 5bdb4721..f6ce7e9d 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -34,19 +34,6 @@ def createSidebar(settings, main_window): - def toggleTranslationSwitchBox(e): - main_window.translation_switch_box.toggle() - - def toggleTranscriptionSendSwitchBox(e): - main_window.transcription_send_switch_box.toggle() - - def toggleTranscriptionReceiveSwitchBox(e): - main_window.transcription_receive_switch_box.toggle() - - def toggleForegroundSwitchBox(e): - main_window.foreground_switch_box.toggle() - - def changeSidebarFeaturesColorByEvents(ww, event_name): target_frame_widget = getattr(main_window, ww) if event_name == "enter": @@ -233,44 +220,44 @@ def createSidebar(settings, main_window): "frame_attr_name": "translation_frame", "command": toggleTranslationFeature, "switch_box_attr_name": "translation_switch_box", - "toggle_switch_box_command": toggleTranslationSwitchBox, + "toggle_switch_box_command": lambda e: main_window.translation_switch_box.toggle(), "label_attr_name": "label_translation", "compact_mode_icon_attr_name": "translation_compact_mode_icon", "selected_mark_attr_name": "translation_selected_mark", - "text": "Translation", + "var_label_text": main_window.view_variable.VAR_LABEL_TRANSLATION, "icon_file_name": settings.image_filename.TRANSLATION_ICON, }, { "frame_attr_name": "transcription_send_frame", "command": toggleTranscriptionSendFeature, "switch_box_attr_name": "transcription_send_switch_box", - "toggle_switch_box_command": toggleTranscriptionSendSwitchBox, + "toggle_switch_box_command": lambda e: main_window.transcription_send_switch_box.toggle(), "label_attr_name": "label_transcription_send", "compact_mode_icon_attr_name": "transcription_send_compact_mode_icon", "selected_mark_attr_name": "transcription_send_selected_mark", - "text": "Voice2Chatbox", + "var_label_text": main_window.view_variable.VAR_LABEL_TRANSCRIPTION_SEND, "icon_file_name": settings.image_filename.MIC_ICON, }, { "frame_attr_name": "transcription_receive_frame", "command": toggleTranscriptionReceiveFeature, "switch_box_attr_name": "transcription_receive_switch_box", - "toggle_switch_box_command": toggleTranscriptionReceiveSwitchBox, + "toggle_switch_box_command": lambda e: main_window.transcription_receive_switch_box.toggle(), "label_attr_name": "label_transcription_receive", "compact_mode_icon_attr_name": "transcription_receive_compact_mode_icon", "selected_mark_attr_name": "transcription_receive_selected_mark", - "text": "Speaker2Log", + "var_label_text": main_window.view_variable.VAR_LABEL_TRANSCRIPTION_RECEIVE, "icon_file_name": settings.image_filename.HEADPHONES_ICON, }, { "frame_attr_name": "foreground_frame", "command": toggleForegroundFeature, "switch_box_attr_name": "foreground_switch_box", - "toggle_switch_box_command": toggleForegroundSwitchBox, + "toggle_switch_box_command": lambda e: main_window.foreground_switch_box.toggle(), "label_attr_name": "label_foreground", "compact_mode_icon_attr_name": "foreground_compact_mode_icon", "selected_mark_attr_name": "foreground_selected_mark", - "text": "Foreground", + "var_label_text": main_window.view_variable.VAR_LABEL_FOREGROUND, "icon_file_name": settings.image_filename.FOREGROUND_ICON, }, ] @@ -286,7 +273,7 @@ def createSidebar(settings, main_window): label_attr_name = sfs["label_attr_name"] compact_mode_icon_attr_name = sfs["compact_mode_icon_attr_name"] selected_mark_attr_name = sfs["selected_mark_attr_name"] - text = sfs["text"] + var_label_text = sfs["var_label_text"] icon_file_name = sfs["icon_file_name"] frame_widget = CTkFrame(main_window.sidebar_features_container, corner_radius=0, fg_color=settings.ctm.SF__BG_COLOR, cursor="hand2", width=0, height=0) @@ -297,7 +284,7 @@ def createSidebar(settings, main_window): label_widget = CTkLabel( frame_widget, - text=text, + textvariable=var_label_text, height=0, corner_radius=0, font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SF__LABEL_FONT_SIZE, weight="normal"), From 63b220a2d015497f3608786f0124642aa82a1106 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 2 Sep 2023 19:19:35 +0900 Subject: [PATCH 076/355] =?UTF-8?q?=E5=A4=89=E6=95=B0=E5=90=8D=E5=A4=89?= =?UTF-8?q?=E6=9B=B4:=20Prefix=E3=81=AB=E3=81=97=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=9Fsqls=E3=82=92sls=E3=81=AB=E3=80=82Sidebar=20Quick=20La?= =?UTF-8?q?nguages=20Settings=E3=81=AE=E6=84=8F=E3=81=A0=E3=81=A3=E3=81=9F?= =?UTF-8?q?=E3=81=91=E3=81=A9=E3=80=81=E3=83=A1=E3=82=A4=E3=83=B3=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E3=81=AB=E3=81=82=E3=82=8B=E3=81=A8=E3=81=AF=E3=81=84?= =?UTF-8?q?=E3=81=88Quick=E3=81=A7=E3=82=82=E3=81=AA=E3=82=93=E3=81=A7?= =?UTF-8?q?=E3=82=82=E3=81=AA=E3=81=84=E3=81=AE=E3=81=A7sls(Sidebar=20Lang?= =?UTF-8?q?uages=20Settings)=E3=81=A8=E5=A4=89=E6=9B=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 12 +- vrct_gui/_changeMainWindowWidgetsStatus.py | 26 +-- .../main_window/widgets/create_sidebar.py | 158 +++++++++--------- .../main_window/widgets/create_textbox.py | 2 +- vrct_gui/ui_managers/ColorThemeManager.py | 50 +++--- vrct_gui/ui_managers/UiScalingManager.py | 26 +-- vrct_gui/vrct_gui.py | 6 +- 7 files changed, 140 insertions(+), 140 deletions(-) diff --git a/view.py b/view.py index 2dfc44ce..9baa605f 100644 --- a/view.py +++ b/view.py @@ -217,12 +217,12 @@ class View(): vrct_gui.CALLBACK_TOGGLE_FOREGROUND = sidebar_features["callback_toggle_foreground"] - vrct_gui.sqls__optionmenu_your_language.configure(values=language_presets["values"]) - vrct_gui.sqls__optionmenu_your_language.configure(command=language_presets["callback_your_language"]) - vrct_gui.sqls__optionmenu_your_language.configure(variable=StringVar(value=config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO])) - vrct_gui.sqls__optionmenu_target_language.configure(values=language_presets["values"]) - vrct_gui.sqls__optionmenu_target_language.configure(command=language_presets["callback_target_language"]) - vrct_gui.sqls__optionmenu_target_language.configure(variable=StringVar(value=config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO])) + vrct_gui.sls__optionmenu_your_language.configure(values=language_presets["values"]) + vrct_gui.sls__optionmenu_your_language.configure(command=language_presets["callback_your_language"]) + vrct_gui.sls__optionmenu_your_language.configure(variable=StringVar(value=config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO])) + vrct_gui.sls__optionmenu_target_language.configure(values=language_presets["values"]) + vrct_gui.sls__optionmenu_target_language.configure(command=language_presets["callback_target_language"]) + vrct_gui.sls__optionmenu_target_language.configure(variable=StringVar(value=config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO])) vrct_gui.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets["callback_selected_language_preset_tab"] vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index facca16a..af616f92 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -82,23 +82,23 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, status, target_names): case "quick_language_settings": if status == "disabled": - vrct_gui.sqls__container_title.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) - vrct_gui.sqls__title_text_your_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) - vrct_gui.sqls__title_text_target_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) + vrct_gui.sls__container_title.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) + vrct_gui.sls__title_text_your_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) + vrct_gui.sls__title_text_target_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) if settings.IS_SIDEBAR_COMPACT_MODE is False: - vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE) - vrct_gui.sqls__optionmenu_your_language.configure(state="disabled") - vrct_gui.sqls__optionmenu_target_language.configure(state="disabled") + vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE) + vrct_gui.sls__optionmenu_your_language.configure(state="disabled") + vrct_gui.sls__optionmenu_target_language.configure(state="disabled") elif status == "normal": - vrct_gui.sqls__container_title.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) - vrct_gui.sqls__title_text_your_language.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) - vrct_gui.sqls__title_text_target_language.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) + vrct_gui.sls__container_title.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) + vrct_gui.sls__title_text_your_language.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) + vrct_gui.sls__title_text_target_language.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) if settings.IS_SIDEBAR_COMPACT_MODE is False: - vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) - vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) - vrct_gui.sqls__optionmenu_your_language.configure(state="normal") - vrct_gui.sqls__optionmenu_target_language.configure(state="normal") + vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) + vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) + vrct_gui.sls__optionmenu_your_language.configure(state="normal") + vrct_gui.sls__optionmenu_target_language.configure(state="normal") case "config_button": diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index f6ce7e9d..32d0c0c7 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -62,26 +62,26 @@ def createSidebar(settings, main_window): def switchActiveAndPassivePresetsTabsColor(target_active_widget): quick_setting_tabs = [ - getattr(main_window, "sqls__presets_button_1"), - getattr(main_window, "sqls__presets_button_2"), - getattr(main_window, "sqls__presets_button_3") + getattr(main_window, "sls__presets_button_1"), + getattr(main_window, "sls__presets_button_2"), + getattr(main_window, "sls__presets_button_3") ] switchTabsColor( target_widget=target_active_widget, tab_buttons=quick_setting_tabs, - active_bg_color=settings.ctm.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR, - active_text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR, + active_bg_color=settings.ctm.SLS__PRESETS_TAB_BG_ACTIVE_COLOR, + active_text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR, passive_bg_color=settings.ctm.SIDEBAR_BG_COLOR, - passive_text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE + passive_text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE ) def switchPresetTabFunction(target_active_widget): switchActiveAndPassivePresetsTabsColor(target_active_widget) - switchActiveTabAndPassiveTab(target_active_widget, main_window.current_active_preset_tab, main_window.current_active_preset_tab.passive_function, settings.ctm.SQLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SQLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR) + switchActiveTabAndPassiveTab(target_active_widget, main_window.current_active_preset_tab, main_window.current_active_preset_tab.passive_function, settings.ctm.SLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR) - main_window.sqls__optionmenu_your_language.set(main_window.view_variable.VAR_YOUR_LANGUAGE.get()) - main_window.sqls__optionmenu_target_language.set(main_window.view_variable.VAR_TARGET_LANGUAGE.get()) + main_window.sls__optionmenu_your_language.set(main_window.view_variable.VAR_YOUR_LANGUAGE.get()) + main_window.sls__optionmenu_target_language.set(main_window.view_variable.VAR_TARGET_LANGUAGE.get()) main_window.current_active_preset_tab = target_active_widget @@ -89,19 +89,19 @@ def createSidebar(settings, main_window): def switchToPreset1(e): print("1") callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "1") - target_active_widget = getattr(main_window, "sqls__presets_button_1") + target_active_widget = getattr(main_window, "sls__presets_button_1") switchPresetTabFunction(target_active_widget) def switchToPreset2(e): print("2") callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "2") - target_active_widget = getattr(main_window, "sqls__presets_button_2") + target_active_widget = getattr(main_window, "sls__presets_button_2") switchPresetTabFunction(target_active_widget) def switchToPreset3(e): print("3") callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "3") - target_active_widget = getattr(main_window, "sqls__presets_button_3") + target_active_widget = getattr(main_window, "sls__presets_button_3") switchPresetTabFunction(target_active_widget) @@ -114,8 +114,8 @@ def createSidebar(settings, main_window): height=30, width=width, values=dropdown_menu_values, - button_color=settings.ctm.SQLS__DROPDOWN_MENU_BG_COLOR, - fg_color=settings.ctm.SQLS__DROPDOWN_MENU_BG_COLOR, + button_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, + fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, text_color=text_color, font=CTkFont(family=settings.FONT_FAMILY, size=font_size, weight="normal"), variable=variable, @@ -128,42 +128,42 @@ def createSidebar(settings, main_window): def createQuickLanguageSettingBox(parent_widget, title_text, title_text_attr_name, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values, variable): - sqls__box = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.SQLS__BOX_BG_COLOR, width=0, height=0) + sls__box = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) - sqls__box.columnconfigure((0,2), weight=1) + sls__box.columnconfigure((0,2), weight=1) - sqls__box_wrapper = CTkFrame(sqls__box, corner_radius=0, fg_color=settings.ctm.SQLS__BOX_BG_COLOR, width=0, height=0) - sqls__box_wrapper.grid(row=2, column=1, padx=0, pady=settings.uism.SQLS__BOX_IPADY) + sls__box_wrapper = CTkFrame(sls__box, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) + sls__box_wrapper.grid(row=2, column=1, padx=0, pady=settings.uism.SLS__BOX_IPADY) - sqls__label = CTkLabel( - sqls__box_wrapper, + sls__label = CTkLabel( + sls__box_wrapper, text=title_text, height=0, - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SQLS__BOX_SECTION_TITLE_FONT_SIZE, weight="normal"), - text_color=settings.ctm.SQLS__BOX_SECTION_TITLE_TEXT_COLOR + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_SECTION_TITLE_FONT_SIZE, weight="normal"), + text_color=settings.ctm.SLS__BOX_SECTION_TITLE_TEXT_COLOR ) - sqls__label.grid(row=0, column=0, pady=(0,settings.uism.SQLS__BOX_SECTION_TITLE_BOTTOM_PADY)) - setattr(main_window, title_text_attr_name, sqls__label) + sls__label.grid(row=0, column=0, pady=(0,settings.uism.SLS__BOX_SECTION_TITLE_BOTTOM_PADY)) + setattr(main_window, title_text_attr_name, sls__label) createOption_DropdownMenu_for_quickSettings( main_window, - sqls__box_wrapper, + sls__box_wrapper, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values=dropdown_menu_values, # command=self.fakeCommand, - width=settings.uism.SQLS__BOX_DROPDOWN_MENU_WIDTH, - font_size=settings.uism.SQLS__BOX_DROPDOWN_MENU_FONT_SIZE, + width=settings.uism.SLS__BOX_DROPDOWN_MENU_WIDTH, + font_size=settings.uism.SLS__BOX_DROPDOWN_MENU_FONT_SIZE, text_color=settings.ctm.LABELS_TEXT_COLOR, variable=variable, # variable=StringVar(value="Chinese, Cantonese\n(Traditional Hong Kong)"), ) getattr(main_window, optionmenu_attr_name).grid(row=1, column=0, padx=0, pady=0) - return sqls__box + return sls__box @@ -384,46 +384,46 @@ def createSidebar(settings, main_window): # Sidebar Quick Language Settings, SQLS - main_window.sqls__container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + main_window.sls__container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) if settings.IS_SIDEBAR_COMPACT_MODE is False: - main_window.sqls__container.grid(row=2, column=0, sticky="new") + main_window.sls__container.grid(row=2, column=0, sticky="new") - main_window.sqls__container.grid_columnconfigure(0, weight=1) + main_window.sls__container.grid_columnconfigure(0, weight=1) - main_window.sqls__container_title = CTkLabel(main_window.sqls__container, + main_window.sls__container_title = CTkLabel(main_window.sls__container, # text="言語設定", text="Language Settings", height=0, - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SQLS__TITLE_FONT_SIZE, weight="normal"), - text_color=settings.ctm.SQLS__TITLE_TEXT_COLOR + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__TITLE_FONT_SIZE, weight="normal"), + text_color=settings.ctm.SLS__TITLE_TEXT_COLOR ) - main_window.sqls__container_title.grid(row=0, column=0, pady=settings.uism.SQLS__TITLE_PADY, sticky="nsew") + main_window.sls__container_title.grid(row=0, column=0, pady=settings.uism.SLS__TITLE_PADY, sticky="nsew") # Presets buttons main_window.sidebar_bg_container.grid_rowconfigure(2, weight=1) - main_window.sqls__presets_buttons_box = CTkFrame(main_window.sqls__container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) - main_window.sqls__presets_buttons_box.grid(row=1, column=0, sticky="ew") + main_window.sls__presets_buttons_box = CTkFrame(main_window.sls__container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + main_window.sls__presets_buttons_box.grid(row=1, column=0, sticky="ew") - main_window.sqls__presets_buttons_box.grid_columnconfigure((0,1,2), weight=1) + main_window.sls__presets_buttons_box.grid_columnconfigure((0,1,2), weight=1) preset_tabs_settings = [ { - "preset_tab_attr_name": "sqls__presets_button_1", + "preset_tab_attr_name": "sls__presets_button_1", "command": switchToPreset1, "text": "1", }, { - "preset_tab_attr_name": "sqls__presets_button_2", + "preset_tab_attr_name": "sls__presets_button_2", "command": switchToPreset2, "text": "2", }, { - "preset_tab_attr_name": "sqls__presets_button_3", + "preset_tab_attr_name": "sls__presets_button_3", "command": switchToPreset3, "text": "3", }, @@ -439,9 +439,9 @@ def createSidebar(settings, main_window): main_window, preset_tab_attr_name, CTkFrame( - main_window.sqls__presets_buttons_box, + main_window.sls__presets_buttons_box, corner_radius=0, - fg_color=settings.ctm.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR, + fg_color=settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR, width=0, height=30, cursor="hand2", @@ -454,17 +454,17 @@ def createSidebar(settings, main_window): parent_widget, text=text, height=0, - fg_color=settings.ctm.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR, - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SQLS__PRESET_TAB_NUMBER_FONT_SIZE, weight="bold"), + fg_color=settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__PRESET_TAB_NUMBER_FONT_SIZE, weight="bold"), anchor="center", - text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE + text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE ) label_widget.place(relx=0.5, rely=0.5, anchor="center") - bindEnterAndLeaveColor([parent_widget, label_widget], settings.ctm.SQLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR) - bindButtonPressColor([parent_widget, label_widget], settings.ctm.SQLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR) + bindEnterAndLeaveColor([parent_widget, label_widget], settings.ctm.SLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR) + bindButtonPressColor([parent_widget, label_widget], settings.ctm.SLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR) parent_widget.passive_function = command bindButtonReleaseFunction([parent_widget, label_widget], command) @@ -473,72 +473,72 @@ def createSidebar(settings, main_window): # Quick Language settings BOX - main_window.sqls__box_frame = CTkFrame(main_window.sqls__container, corner_radius=0, fg_color=settings.ctm.SQLS__BG_COLOR, width=0, height=0) - main_window.sqls__box_frame.grid(row=2, column=0, sticky="ew") - main_window.sqls__box_frame.grid_columnconfigure(0, weight=1) + main_window.sls__box_frame = CTkFrame(main_window.sls__container, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) + main_window.sls__box_frame.grid(row=2, column=0, sticky="ew") + main_window.sls__box_frame.grid_columnconfigure(0, weight=1) # Your language - main_window.sqls__box_your_language = createQuickLanguageSettingBox( - parent_widget=main_window.sqls__box_frame, + main_window.sls__box_your_language = createQuickLanguageSettingBox( + parent_widget=main_window.sls__box_frame, # title_text="あなたの言語", title_text="Your Language", - title_text_attr_name="sqls__title_text_your_language", - optionmenu_attr_name="sqls__optionmenu_your_language", - dropdown_menu_attr_name="sqls__dropdown_menu_your_language", + title_text_attr_name="sls__title_text_your_language", + optionmenu_attr_name="sls__optionmenu_your_language", + dropdown_menu_attr_name="sls__dropdown_menu_your_language", dropdown_menu_values=["1""2","pppp\npppp"], variable=main_window.view_variable.VAR_YOUR_LANGUAGE ) - main_window.sqls__box_your_language.grid(row=2, column=0, padx=0, pady=(settings.uism.SQLS__BOX_TOP_PADY,0),sticky="ew") + main_window.sls__box_your_language.grid(row=2, column=0, padx=0, pady=(settings.uism.SLS__BOX_TOP_PADY,0),sticky="ew") # Both direction arrow icon - main_window.sqls__arrow_direction_box = CTkFrame(main_window.sqls__box_frame, corner_radius=0, fg_color=settings.ctm.SQLS__BG_COLOR, width=0, height=0) - main_window.sqls__arrow_direction_box.grid(row=3, column=0, padx=0, pady=settings.uism.SQLS__BOX_ARROWS_PADY,sticky="ew") + main_window.sls__arrow_direction_box = CTkFrame(main_window.sls__box_frame, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) + main_window.sls__arrow_direction_box.grid(row=3, column=0, padx=0, pady=settings.uism.SLS__BOX_ARROWS_PADY,sticky="ew") - main_window.sqls__arrow_direction_box.grid_columnconfigure((0,4), weight=1) + main_window.sls__arrow_direction_box.grid_columnconfigure((0,4), weight=1) - main_window.sqls__both_direction_up = CTkLabel( - main_window.sqls__arrow_direction_box, + main_window.sls__both_direction_up = CTkLabel( + main_window.sls__arrow_direction_box, text=None, height=0, - image=CTkImage(getImageFileFromUiUtils(settings.image_filename.NARROW_ARROW_DOWN).rotate(180),size=settings.uism.SQLS__BOX_ARROWS_IMAGE_SIZE) + image=CTkImage(getImageFileFromUiUtils(settings.image_filename.NARROW_ARROW_DOWN).rotate(180),size=settings.uism.SLS__BOX_ARROWS_IMAGE_SIZE) ) - main_window.sqls__both_direction_up.grid(row=0, column=1, pady=0) + main_window.sls__both_direction_up.grid(row=0, column=1, pady=0) - main_window.sqls__both_direction_desc = CTkLabel( - main_window.sqls__arrow_direction_box, + main_window.sls__both_direction_desc = CTkLabel( + main_window.sls__arrow_direction_box, # text="双方向に翻訳", text="Translate Each Other", height=0, - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SQLS__BOX_ARROWS_DESC_FONT_SIZE, weight="normal"), - text_color=settings.ctm.SQLS__BOX_ARROWS_TEXT_COLOR, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_ARROWS_DESC_FONT_SIZE, weight="normal"), + text_color=settings.ctm.SLS__BOX_ARROWS_TEXT_COLOR, ) - main_window.sqls__both_direction_desc.grid(row=0, column=2, padx=settings.uism.SQLS__BOX_ARROWS_DESC_PADX, pady=0) + main_window.sls__both_direction_desc.grid(row=0, column=2, padx=settings.uism.SLS__BOX_ARROWS_DESC_PADX, pady=0) - main_window.sqls__both_direction_label_down = CTkLabel( - main_window.sqls__arrow_direction_box, + main_window.sls__both_direction_label_down = CTkLabel( + main_window.sls__arrow_direction_box, text=None, height=0, - image=CTkImage(getImageFileFromUiUtils(settings.image_filename.NARROW_ARROW_DOWN).rotate(0),size=settings.uism.SQLS__BOX_ARROWS_IMAGE_SIZE) + image=CTkImage(getImageFileFromUiUtils(settings.image_filename.NARROW_ARROW_DOWN).rotate(0),size=settings.uism.SLS__BOX_ARROWS_IMAGE_SIZE) ) - main_window.sqls__both_direction_label_down.grid(row=0, column=3, pady=0) + main_window.sls__both_direction_label_down.grid(row=0, column=3, pady=0) # Target language - main_window.sqls__box_target_language = createQuickLanguageSettingBox( - parent_widget=main_window.sqls__box_frame, + main_window.sls__box_target_language = createQuickLanguageSettingBox( + parent_widget=main_window.sls__box_frame, # title_text="相手の言語", title_text="Target Language", - title_text_attr_name="sqls__title_text_target_language", - optionmenu_attr_name="sqls__optionmenu_target_language", - dropdown_menu_attr_name="sqls__dropdown_menu_target_language", + title_text_attr_name="sls__title_text_target_language", + optionmenu_attr_name="sls__optionmenu_target_language", + dropdown_menu_attr_name="sls__dropdown_menu_target_language", dropdown_menu_values=["1""2","pppp\npppp2"], variable=main_window.view_variable.VAR_TARGET_LANGUAGE ) - main_window.sqls__box_target_language.grid(row=4, column=0, padx=0, pady=(0,0),sticky="ew") + main_window.sls__box_target_language.grid(row=4, column=0, padx=0, pady=(0,0),sticky="ew") diff --git a/vrct_gui/main_window/widgets/create_textbox.py b/vrct_gui/main_window/widgets/create_textbox.py index c2d6f813..eb301ba5 100644 --- a/vrct_gui/main_window/widgets/create_textbox.py +++ b/vrct_gui/main_window/widgets/create_textbox.py @@ -52,7 +52,7 @@ def createTextbox(settings, main_window): target_widget=target_active_widget, tab_buttons=textbox_tabs, active_bg_color=settings.ctm.TEXTBOX_BG_COLOR, - active_text_color=settings.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR, + active_text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR, passive_bg_color=settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR, passive_text_color=settings.ctm.TEXTBOX_TAB_TEXT_PASSIVE_COLOR ) diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 5e229326..9189035b 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -116,23 +116,23 @@ class ColorThemeManager(): self.main.SF__SELECTED_MARK_DISABLE_BG_COLOR = self.main.SF__SWITCH_BOX_DISABLE_BG_COLOR - # Sidebar quick settings - self.main.SQLS__TITLE_TEXT_COLOR = self.DARK_400_COLOR + # Sidebar Languages Settings + self.main.SLS__TITLE_TEXT_COLOR = self.DARK_400_COLOR - self.main.SQLS__BG_COLOR = self.DARK_825_COLOR + self.main.SLS__BG_COLOR = self.DARK_825_COLOR - self.main.SQLS__PRESETS_TAB_BG_HOVERED_COLOR = self.DARK_850_COLOR - self.main.SQLS__PRESETS_TAB_BG_CLICKED_COLOR = self.DARK_888_COLOR - self.main.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR = self.main.SIDEBAR_BG_COLOR - self.main.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR = self.main.SQLS__BG_COLOR - self.main.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE = self.DARK_600_COLOR - self.main.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + self.main.SLS__PRESETS_TAB_BG_HOVERED_COLOR = self.DARK_850_COLOR + self.main.SLS__PRESETS_TAB_BG_CLICKED_COLOR = self.DARK_888_COLOR + self.main.SLS__PRESETS_TAB_BG_PASSIVE_COLOR = self.main.SIDEBAR_BG_COLOR + self.main.SLS__PRESETS_TAB_BG_ACTIVE_COLOR = self.main.SLS__BG_COLOR + self.main.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE = self.DARK_600_COLOR + self.main.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR = self.main.BASIC_TEXT_COLOR - self.main.SQLS__BOX_BG_COLOR = self.DARK_850_COLOR - self.main.SQLS__BOX_SECTION_TITLE_TEXT_COLOR = self.DARK_400_COLOR - self.main.SQLS__BOX_ARROWS_TEXT_COLOR = self.DARK_500_COLOR + self.main.SLS__BOX_BG_COLOR = self.DARK_850_COLOR + self.main.SLS__BOX_SECTION_TITLE_TEXT_COLOR = self.DARK_400_COLOR + self.main.SLS__BOX_ARROWS_TEXT_COLOR = self.DARK_500_COLOR - self.main.SQLS__DROPDOWN_MENU_BG_COLOR = self.DARK_900_COLOR + self.main.SLS__DROPDOWN_MENU_BG_COLOR = self.DARK_900_COLOR self.main.CONFIG_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR @@ -267,22 +267,22 @@ class ColorThemeManager(): # Sidebar quick settings - self.main.SQLS__TITLE_TEXT_COLOR = self.LIGHT_800_COLOR + self.main.SLS__TITLE_TEXT_COLOR = self.LIGHT_800_COLOR - self.main.SQLS__BG_COLOR = self.LIGHT_300_COLOR + self.main.SLS__BG_COLOR = self.LIGHT_300_COLOR - self.main.SQLS__PRESETS_TAB_BG_HOVERED_COLOR = self.LIGHT_350_COLOR - self.main.SQLS__PRESETS_TAB_BG_CLICKED_COLOR = self.LIGHT_800_COLOR - self.main.SQLS__PRESETS_TAB_BG_PASSIVE_COLOR = self.main.SIDEBAR_BG_COLOR - self.main.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR = self.main.SQLS__BG_COLOR - self.main.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE = self.LIGHT_600_COLOR - self.main.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + self.main.SLS__PRESETS_TAB_BG_HOVERED_COLOR = self.LIGHT_350_COLOR + self.main.SLS__PRESETS_TAB_BG_CLICKED_COLOR = self.LIGHT_800_COLOR + self.main.SLS__PRESETS_TAB_BG_PASSIVE_COLOR = self.main.SIDEBAR_BG_COLOR + self.main.SLS__PRESETS_TAB_BG_ACTIVE_COLOR = self.main.SLS__BG_COLOR + self.main.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE = self.LIGHT_600_COLOR + self.main.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR = self.main.BASIC_TEXT_COLOR - self.main.SQLS__BOX_BG_COLOR = self.LIGHT_350_COLOR - self.main.SQLS__BOX_SECTION_TITLE_TEXT_COLOR = self.LIGHT_800_COLOR - self.main.SQLS__BOX_ARROWS_TEXT_COLOR = self.LIGHT_700_COLOR + self.main.SLS__BOX_BG_COLOR = self.LIGHT_350_COLOR + self.main.SLS__BOX_SECTION_TITLE_TEXT_COLOR = self.LIGHT_800_COLOR + self.main.SLS__BOX_ARROWS_TEXT_COLOR = self.LIGHT_700_COLOR - self.main.SQLS__DROPDOWN_MENU_BG_COLOR = self.LIGHT_500_COLOR + self.main.SLS__DROPDOWN_MENU_BG_COLOR = self.LIGHT_500_COLOR self.main.CONFIG_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 2cbb2142..e7d244b2 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -55,21 +55,21 @@ class UiScalingManager(): # Sidebar Quick Language Settings, SQLS - self.main.SQLS__TITLE_FONT_SIZE = self._calculateUiSize(16) - self.main.SQLS__TITLE_PADY = (self._calculateUiSize(12), self._calculateUiSize(6)) + self.main.SLS__TITLE_FONT_SIZE = self._calculateUiSize(16) + self.main.SLS__TITLE_PADY = (self._calculateUiSize(12), self._calculateUiSize(6)) - self.main.SQLS__PRESET_TAB_NUMBER_FONT_SIZE = self._calculateUiSize(16) + self.main.SLS__PRESET_TAB_NUMBER_FONT_SIZE = self._calculateUiSize(16) - self.main.SQLS__BOX_SECTION_TITLE_FONT_SIZE = self._calculateUiSize(16) - self.main.SQLS__BOX_SECTION_TITLE_BOTTOM_PADY = self._calculateUiSize(10) - self.main.SQLS__BOX_IPADY = (self._calculateUiSize(8),self._calculateUiSize(18)) - self.main.SQLS__BOX_DROPDOWN_MENU_FONT_SIZE = self._calculateUiSize(14) - self.main.SQLS__BOX_DROPDOWN_MENU_WIDTH = self._calculateUiSize(200) - self.main.SQLS__BOX_ARROWS_PADY = self._calculateUiSize(10) - self.main.SQLS__BOX_ARROWS_IMAGE_SIZE = self.dupTuple(self._calculateUiSize(16)) - self.main.SQLS__BOX_ARROWS_DESC_FONT_SIZE = self._calculateUiSize(12) - self.main.SQLS__BOX_ARROWS_DESC_PADX = self._calculateUiSize(6) - self.main.SQLS__BOX_TOP_PADY = self._calculateUiSize(16) + self.main.SLS__BOX_SECTION_TITLE_FONT_SIZE = self._calculateUiSize(16) + self.main.SLS__BOX_SECTION_TITLE_BOTTOM_PADY = self._calculateUiSize(10) + self.main.SLS__BOX_IPADY = (self._calculateUiSize(8),self._calculateUiSize(18)) + self.main.SLS__BOX_DROPDOWN_MENU_FONT_SIZE = self._calculateUiSize(14) + self.main.SLS__BOX_DROPDOWN_MENU_WIDTH = self._calculateUiSize(200) + self.main.SLS__BOX_ARROWS_PADY = self._calculateUiSize(10) + self.main.SLS__BOX_ARROWS_IMAGE_SIZE = self.dupTuple(self._calculateUiSize(16)) + self.main.SLS__BOX_ARROWS_DESC_FONT_SIZE = self._calculateUiSize(12) + self.main.SLS__BOX_ARROWS_DESC_PADX = self._calculateUiSize(6) + self.main.SLS__BOX_TOP_PADY = self._calculateUiSize(16) self.main.SIDEBAR_CONFIG_BUTTON_CORNER_RADIUS = self._calculateUiSize(6) self.main.SIDEBAR_CONFIG_BUTTON_PADX = self._calculateUiSize(10) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 89e7804c..94de0c32 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -69,11 +69,11 @@ class VRCT_GUI(CTk): ) def setDefaultActiveLanguagePresetTab(self, tab_no:str): - self.current_active_preset_tab = getattr(self, f"sqls__presets_button_{tab_no}") + self.current_active_preset_tab = getattr(self, f"sls__presets_button_{tab_no}") _setDefaultActiveTab( active_tab_widget=self.current_active_preset_tab, - active_bg_color=self.settings.main.ctm.SQLS__PRESETS_TAB_BG_ACTIVE_COLOR, - active_text_color=self.settings.main.ctm.SQLS__PRESETS_TAB_ACTIVE_TEXT_COLOR + active_bg_color=self.settings.main.ctm.SLS__PRESETS_TAB_BG_ACTIVE_COLOR, + active_text_color=self.settings.main.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR ) From c232f5e5cdffbdfd909163d9c26b422456e52cf1 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 4 Sep 2023 00:06:43 +0900 Subject: [PATCH 077/355] =?UTF-8?q?view.py=E3=81=B8=E3=82=B5=E3=82=A4?= =?UTF-8?q?=E3=83=89=E3=83=90=E3=83=BC=E9=96=8B=E9=96=89=E5=9B=9E=E3=82=8A?= =?UTF-8?q?=E3=81=AE=E5=A4=89=E6=95=B0=E3=82=92=E7=A7=BB=E5=8B=95=E3=80=82?= =?UTF-8?q?=E9=96=A2=E6=95=B0=E3=81=AF(view.py=E3=81=8Cwrapper=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=82=8B=E3=81=91=E3=81=A9)vrct=5Fgui.py?= =?UTF-8?q?=E3=81=B8=E7=A7=BB=E5=8B=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 12 +++++++++++- vrct_gui/_changeMainWindowWidgetsStatus.py | 8 ++++---- .../widgets/create_minimize_sidebar_button.py | 18 +++++------------- vrct_gui/main_window/widgets/create_sidebar.py | 8 ++++---- vrct_gui/vrct_gui.py | 7 +++++++ 5 files changed, 31 insertions(+), 22 deletions(-) diff --git a/view.py b/view.py index 9baa605f..778f159a 100644 --- a/view.py +++ b/view.py @@ -24,7 +24,6 @@ class View(): self.settings.main = SimpleNamespace( ctm=all_ctm.main, uism=all_uism.main, - IS_SIDEBAR_COMPACT_MODE=False, COMPACT_MODE_ICON_SIZE=0, **common_args ) @@ -38,6 +37,10 @@ class View(): self.view_variable = SimpleNamespace( # Main Window + # Sidebar Compact Mode + IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE=False, + CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE=None, + # Sidebar Features VAR_LABEL_TRANSLATION=StringVar(value="Translation"), CALLBACK_TOGGLE_TRANSLATION=None, @@ -211,6 +214,8 @@ class View(): def register(self, sidebar_features, language_presets, entry_message_box_commands, config_window): + self.view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = self._toggleMainWindowSidebarCompactMode + vrct_gui.CALLBACK_TOGGLE_TRANSLATION = sidebar_features["callback_toggle_translation"] vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features["callback_toggle_transcription_send"] vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features["callback_toggle_transcription_receive"] @@ -325,6 +330,11 @@ class View(): vrct_gui.attributes("-topmost", False) + def _toggleMainWindowSidebarCompactMode(self, is_turned_on): + self.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = is_turned_on + vrct_gui.recreateMainWindowSidebar() + + def updateGuiVariableByPresetTabNo(self, tab_no:str): self.view_variable.VAR_YOUR_LANGUAGE.set(config.SELECTED_TAB_YOUR_LANGUAGES[tab_no]) self.view_variable.VAR_TARGET_LANGUAGE.set(config.SELECTED_TAB_TARGET_LANGUAGES[tab_no]) diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index af616f92..af8b51d8 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -85,7 +85,7 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, status, target_names): vrct_gui.sls__container_title.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) vrct_gui.sls__title_text_your_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) vrct_gui.sls__title_text_target_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) - if settings.IS_SIDEBAR_COMPACT_MODE is False: + if vrct_gui.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE) vrct_gui.sls__optionmenu_your_language.configure(state="disabled") vrct_gui.sls__optionmenu_target_language.configure(state="disabled") @@ -94,7 +94,7 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, status, target_names): vrct_gui.sls__container_title.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) vrct_gui.sls__title_text_your_language.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) vrct_gui.sls__title_text_target_language.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) - if settings.IS_SIDEBAR_COMPACT_MODE is False: + if vrct_gui.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) vrct_gui.sls__optionmenu_your_language.configure(state="normal") @@ -120,7 +120,7 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, status, target_names): vrct_gui.minimize_sidebar_button_container.configure(cursor="") - if settings.IS_SIDEBAR_COMPACT_MODE is True: + if vrct_gui.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT_DISABLED).rotate(180), size=LOGO_SIZE) else: image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT_DISABLED), size=LOGO_SIZE) @@ -128,7 +128,7 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, status, target_names): elif status == "normal": vrct_gui.minimize_sidebar_button_container.configure(cursor="hand2") - if settings.IS_SIDEBAR_COMPACT_MODE is True: + if vrct_gui.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT).rotate(180), size=LOGO_SIZE) else: image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT), size=LOGO_SIZE) diff --git a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py index 97414c70..6cfd0ce0 100644 --- a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py +++ b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py @@ -3,24 +3,16 @@ from customtkinter import CTkFrame, CTkLabel, CTkImage from ...ui_utils import getImageFileFromUiUtils, bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction -from .create_sidebar import createSidebar - +from utils import callFunctionIfCallable def createMinimizeSidebarButton(settings, main_window): def enableCompactMode(e): - settings.IS_SIDEBAR_COMPACT_MODE = True - main_window.minimize_sidebar_button_container.destroy() - createMinimizeSidebarButton(settings, main_window) - main_window.sidebar_bg_container.destroy() - createSidebar(settings, main_window) + callFunctionIfCallable(main_window.view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE, True) def disableCompactMode(e): - settings.IS_SIDEBAR_COMPACT_MODE = False - main_window.minimize_sidebar_button_container.destroy() - createMinimizeSidebarButton(settings, main_window) - main_window.sidebar_bg_container.destroy() - createSidebar(settings, main_window) + callFunctionIfCallable(main_window.view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE, False) + main_window.minimize_sidebar_button_container = CTkFrame(main_window.main_topbar_container, corner_radius=0, fg_color=settings.ctm.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR, cursor="hand2", width=0, height=0) @@ -36,7 +28,7 @@ def createMinimizeSidebarButton(settings, main_window): ) - if settings.IS_SIDEBAR_COMPACT_MODE is True: + if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT).rotate(180),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) bindButtonReleaseFunction([main_window.minimize_sidebar_button_container, main_window.minimize_sidebar_button], disableCompactMode) diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 32d0c0c7..e0c9f1ae 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -175,7 +175,7 @@ def createSidebar(settings, main_window): main_window.sidebar_bg_container.grid(row=0, column=0, sticky="nsew") - MIN_SIDEBAR_WIDTH = settings.uism.COMPACT_MODE_SIDEBAR_WIDTH if settings.IS_SIDEBAR_COMPACT_MODE is True else settings.uism.SIDEBAR_WIDTH + MIN_SIDEBAR_WIDTH = settings.uism.COMPACT_MODE_SIDEBAR_WIDTH if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True else settings.uism.SIDEBAR_WIDTH main_window.sidebar_bg_container.grid_columnconfigure(0, weight=0, minsize=MIN_SIDEBAR_WIDTH) main_window.grid_rowconfigure(0, weight=1) @@ -200,7 +200,7 @@ def createSidebar(settings, main_window): image=CTkImage(img, size=(width,height)) ) - if settings.IS_SIDEBAR_COMPACT_MODE is True: + if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: main_window.sidebar_compact_mode_logo.grid(row=0, column=0, pady=( int(settings.uism.SF__LOGO_PADY[0]+a_s/2), int(settings.uism.SF__LOGO_PADY[1]+a_s/2) @@ -338,7 +338,7 @@ def createSidebar(settings, main_window): # Arrange - if settings.IS_SIDEBAR_COMPACT_MODE is True: + if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: compact_mode_icon_widget.grid(row=row, column=0, pady=settings.uism.SF__COMPACT_MODE_ICON_PADY) selected_mark_widget.place(relx=-1, rely=0.5, relheight=0.75, anchor="center") else: @@ -386,7 +386,7 @@ def createSidebar(settings, main_window): # Sidebar Quick Language Settings, SQLS main_window.sls__container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) - if settings.IS_SIDEBAR_COMPACT_MODE is False: + if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: main_window.sls__container.grid(row=2, column=0, sticky="new") main_window.sls__container.grid_columnconfigure(0, weight=1) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 94de0c32..de958f3d 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -12,6 +12,8 @@ from .main_window import createMainWindowWidgets from .config_window import ConfigWindow from .ui_utils import _setDefaultActiveTab +from .main_window.widgets import createSidebar, createMinimizeSidebarButton + class VRCT_GUI(CTk): def __init__(self): @@ -76,6 +78,11 @@ class VRCT_GUI(CTk): active_text_color=self.settings.main.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR ) + def recreateMainWindowSidebar(self): + self.minimize_sidebar_button_container.destroy() + createMinimizeSidebarButton(self.settings.main, self) + self.sidebar_bg_container.destroy() + createSidebar(self.settings.main, self) vrct_gui = VRCT_GUI() \ No newline at end of file From 4466e01458f0cebc23dd424f814a8cfe9bf0e415 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 4 Sep 2023 02:41:26 +0900 Subject: [PATCH 078/355] [Refactor] separate function create_sidebar into createSidebarFeatures and createSidebarLanguagesSettings --- .../widgets/_create_sidebar/__init__.py | 2 + .../_create_sidebar/createSidebarFeatures.py | 276 +++++++++ .../createSidebarLanguagesSettings.py | 312 ++++++++++ .../main_window/widgets/create_sidebar.py | 568 +----------------- 4 files changed, 593 insertions(+), 565 deletions(-) create mode 100644 vrct_gui/main_window/widgets/_create_sidebar/__init__.py create mode 100644 vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py create mode 100644 vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py diff --git a/vrct_gui/main_window/widgets/_create_sidebar/__init__.py b/vrct_gui/main_window/widgets/_create_sidebar/__init__.py new file mode 100644 index 00000000..cda02e0d --- /dev/null +++ b/vrct_gui/main_window/widgets/_create_sidebar/__init__.py @@ -0,0 +1,2 @@ +from .createSidebarFeatures import createSidebarFeatures +from .createSidebarLanguagesSettings import createSidebarLanguagesSettings \ No newline at end of file diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py new file mode 100644 index 00000000..4787cea4 --- /dev/null +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py @@ -0,0 +1,276 @@ +from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkSwitch, CTkImage, StringVar + +from ....ui_utils import getImageFileFromUiUtils, openImageKeepAspectRatio, retag, getLatestHeight, bindEnterAndLeaveColor, bindButtonPressColor, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressAndReleaseFunction, bindButtonFunctionAndColor, switchActiveTabAndPassiveTab, switchTabsColor + +from utils import callFunctionIfCallable + + +def createSidebarFeatures(settings, main_window): + + def toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, mark): + mark.place(relx=0.85) if is_turned_on else mark.place(relx=-1) + + + def toggleTranslationFeature(): + is_turned_on = main_window.translation_switch_box.get() + callFunctionIfCallable(main_window.CALLBACK_TOGGLE_TRANSLATION, is_turned_on) + toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.translation_selected_mark) + + def toggleTranscriptionSendFeature(): + is_turned_on = main_window.transcription_send_switch_box.get() + callFunctionIfCallable(main_window.CALLBACK_TOGGLE_TRANSCRIPTION_SEND, is_turned_on) + toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_send_selected_mark) + + def toggleTranscriptionReceiveFeature(): + is_turned_on = main_window.transcription_receive_switch_box.get() + callFunctionIfCallable(main_window.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE, is_turned_on) + toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_receive_selected_mark) + + def toggleForegroundFeature(): + is_turned_on = main_window.foreground_switch_box.get() + callFunctionIfCallable(main_window.CALLBACK_TOGGLE_FOREGROUND, is_turned_on) + toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.foreground_selected_mark) + + + + + def changeSidebarFeaturesColorByEvents(ww, event_name): + target_frame_widget = getattr(main_window, ww) + if event_name == "enter": + target_frame_widget.configure(fg_color=settings.ctm.SF__HOVERED_BG_COLOR) + target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_HOVERED_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR) + target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_HOVERED_BG_COLOR) + + elif event_name == "leave": + target_frame_widget.configure(fg_color=settings.ctm.SF__BG_COLOR) + target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR) + target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) + + elif event_name == "button_press": + target_frame_widget.configure(fg_color=settings.ctm.SF__CLICKED_BG_COLOR) + target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_CLICKED_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR) + target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_CLICKED_BG_COLOR) + + elif event_name == "button_release": + target_frame_widget.configure(fg_color=settings.ctm.SF__BG_COLOR) + target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR) + target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) + + + + + + + + # # Side Bar Container + # main_window.sidebar_bg_container = CTkFrame(main_window, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + # main_window.sidebar_bg_container.grid(row=0, column=0, sticky="nsew") + + + MIN_SIDEBAR_WIDTH = settings.uism.COMPACT_MODE_SIDEBAR_WIDTH if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True else settings.uism.SIDEBAR_WIDTH + main_window.sidebar_bg_container.grid_columnconfigure(0, weight=0, minsize=MIN_SIDEBAR_WIDTH) + main_window.grid_rowconfigure(0, weight=1) + + + (img, width, height) = openImageKeepAspectRatio(settings.image_filename.VRCT_LOGO, settings.uism.SF__LOGO_MAX_SIZE) + main_window.sidebar_logo = CTkLabel( + main_window.sidebar_bg_container, + fg_color=settings.ctm.SIDEBAR_BG_COLOR, + text=None, + height=0, + image=CTkImage(img, size=(width,height)) + ) + + # "height-a_s" represents the adjustment in size, and the "pady+a_s/2" indicates a padding increase of 4 pixels to compensate for the reduction. + a_s = settings.uism.SF__LOGO_HEIGHT_FOR_ADJUSTMENT + (img, width, height) = openImageKeepAspectRatio(settings.image_filename.VRCT_LOGO_MARK, height-a_s) + main_window.sidebar_compact_mode_logo = CTkLabel( + main_window.sidebar_bg_container, + fg_color=settings.ctm.SIDEBAR_BG_COLOR, + text=None, + height=0, + image=CTkImage(img, size=(width,height)) + ) + + if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: + main_window.sidebar_compact_mode_logo.grid(row=0, column=0, pady=( + int(settings.uism.SF__LOGO_PADY[0]+a_s/2), + int(settings.uism.SF__LOGO_PADY[1]+a_s/2) + )) + else: + main_window.sidebar_logo.grid(row=0, column=0, pady=settings.uism.SF__LOGO_PADY) + + # Sidebar Features + main_window.sidebar_features_container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color="transparent", width=0, height=0) + main_window.sidebar_features_container.grid(row=1, column=0, pady=0, sticky="ew") + main_window.sidebar_features_container.grid_columnconfigure(0, weight=1) + + + + sidebar_features_settings = [ + { + "frame_attr_name": "translation_frame", + "command": toggleTranslationFeature, + "switch_box_attr_name": "translation_switch_box", + "toggle_switch_box_command": lambda e: main_window.translation_switch_box.toggle(), + "label_attr_name": "label_translation", + "compact_mode_icon_attr_name": "translation_compact_mode_icon", + "selected_mark_attr_name": "translation_selected_mark", + "var_label_text": main_window.view_variable.VAR_LABEL_TRANSLATION, + "icon_file_name": settings.image_filename.TRANSLATION_ICON, + }, + { + "frame_attr_name": "transcription_send_frame", + "command": toggleTranscriptionSendFeature, + "switch_box_attr_name": "transcription_send_switch_box", + "toggle_switch_box_command": lambda e: main_window.transcription_send_switch_box.toggle(), + "label_attr_name": "label_transcription_send", + "compact_mode_icon_attr_name": "transcription_send_compact_mode_icon", + "selected_mark_attr_name": "transcription_send_selected_mark", + "var_label_text": main_window.view_variable.VAR_LABEL_TRANSCRIPTION_SEND, + "icon_file_name": settings.image_filename.MIC_ICON, + }, + { + "frame_attr_name": "transcription_receive_frame", + "command": toggleTranscriptionReceiveFeature, + "switch_box_attr_name": "transcription_receive_switch_box", + "toggle_switch_box_command": lambda e: main_window.transcription_receive_switch_box.toggle(), + "label_attr_name": "label_transcription_receive", + "compact_mode_icon_attr_name": "transcription_receive_compact_mode_icon", + "selected_mark_attr_name": "transcription_receive_selected_mark", + "var_label_text": main_window.view_variable.VAR_LABEL_TRANSCRIPTION_RECEIVE, + "icon_file_name": settings.image_filename.HEADPHONES_ICON, + }, + { + "frame_attr_name": "foreground_frame", + "command": toggleForegroundFeature, + "switch_box_attr_name": "foreground_switch_box", + "toggle_switch_box_command": lambda e: main_window.foreground_switch_box.toggle(), + "label_attr_name": "label_foreground", + "compact_mode_icon_attr_name": "foreground_compact_mode_icon", + "selected_mark_attr_name": "foreground_selected_mark", + "var_label_text": main_window.view_variable.VAR_LABEL_FOREGROUND, + "icon_file_name": settings.image_filename.FOREGROUND_ICON, + }, + ] + + + + row=0 + for sfs in sidebar_features_settings: + frame_attr_name = sfs["frame_attr_name"] + command = sfs["command"] + switch_box_attr_name = sfs["switch_box_attr_name"] + toggle_switch_box_command = sfs["toggle_switch_box_command"] + label_attr_name = sfs["label_attr_name"] + compact_mode_icon_attr_name = sfs["compact_mode_icon_attr_name"] + selected_mark_attr_name = sfs["selected_mark_attr_name"] + var_label_text = sfs["var_label_text"] + icon_file_name = sfs["icon_file_name"] + + frame_widget = CTkFrame(main_window.sidebar_features_container, corner_radius=0, fg_color=settings.ctm.SF__BG_COLOR, cursor="hand2", width=0, height=0) + setattr(main_window, frame_attr_name, frame_widget) + + frame_widget.grid(row=row, column=0, pady=(0,1), sticky="ew") + frame_widget.grid_columnconfigure(0, weight=1) + + label_widget = CTkLabel( + frame_widget, + textvariable=var_label_text, + height=0, + corner_radius=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SF__LABEL_FONT_SIZE, weight="normal"), + anchor="w", + text_color=settings.ctm.LABELS_TEXT_COLOR, + ) + setattr(main_window, label_attr_name, label_widget) + + + switch_box_widget = CTkSwitch( + frame_widget, + text=None, + height=0, + width=0, + corner_radius=int(settings.uism.SF__SWITCH_BOX_HEIGHT/2), + border_width=0, + switch_height=settings.uism.SF__SWITCH_BOX_HEIGHT, + switch_width=settings.uism.SF__SWITCH_BOX_WIDTH, + onvalue=True, + offvalue=False, + command=command, + fg_color=settings.ctm.SF__SWITCH_BOX_BG_COLOR, + bg_color=settings.ctm.SF__BG_COLOR, + progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR, + ) + # # if sfs["is_checked"] is True: + # # target_attr.select() + # # else: + # # target_attr.deselect() + setattr(main_window, switch_box_attr_name, switch_box_widget) + + + if settings.COMPACT_MODE_ICON_SIZE == 0: + label_widget.grid(row=row, column=0, pady=settings.uism.SF__LABELS_IPADY, padx=(settings.uism.SF__LABEL_LEFT_PAD,0), sticky="ew") + settings.COMPACT_MODE_ICON_SIZE = int(getLatestHeight(frame_widget) - settings.uism.SF__COMPACT_MODE_ICON_PADY*2) + label_widget.grid_remove() + + + # for compact mode + compact_mode_icon_widget = CTkLabel( + frame_widget, + text=None, + height=0, + corner_radius=0, + image=CTkImage(getImageFileFromUiUtils(icon_file_name),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)), + ) + setattr(main_window, compact_mode_icon_attr_name, compact_mode_icon_widget) + + selected_mark_widget = CTkFrame(frame_widget, corner_radius=0, fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR, width=settings.uism.SF__SELECTED_MARK_WIDTH, height=0) + setattr(main_window, selected_mark_attr_name, selected_mark_widget) + + + # Arrange + if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: + compact_mode_icon_widget.grid(row=row, column=0, pady=settings.uism.SF__COMPACT_MODE_ICON_PADY) + selected_mark_widget.place(relx=-1, rely=0.5, relheight=0.75, anchor="center") + else: + label_widget.grid(row=row, column=0, pady=settings.uism.SF__LABELS_IPADY, padx=(settings.uism.SF__LABEL_LEFT_PAD,0), sticky="ew") + switch_box_widget.grid(row=row, column=0, padx=(0,settings.uism.SF__SWITCH_BOX_RIGHT_PAD), sticky="e") + + + # Unbind the event "" originally set up by the widget to manually control it. + switch_box_widget._canvas.unbind("") + switch_box_widget._text_label.unbind("") + + bindButtonReleaseFunction([compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], toggle_switch_box_command) + + retag("vrct_"+frame_attr_name, compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget) + + def commonEventFunction(e, event_type): + for ww in e.widget.master.bindtags(): + if ww.startswith("vrct_"): + ww = ww.replace("vrct_", "") + changeSidebarFeaturesColorByEvents(ww, event_type) + break + + def enterFunction(e): + commonEventFunction(e, "enter") + + def leaveFunction(e): + commonEventFunction(e, "leave") + + def buttonPressFunction(e): + commonEventFunction(e, "button_press") + + def buttonReleasedFunction(e): + commonEventFunction(e, "button_release") + + bindEnterAndLeaveFunction([compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], enterFunction, leaveFunction) + + bindButtonPressAndReleaseFunction([compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], buttonPressFunction, buttonReleasedFunction) + + row+=1 + + + + diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py new file mode 100644 index 00000000..c4d8ab4b --- /dev/null +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -0,0 +1,312 @@ +from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkSwitch, CTkImage, StringVar + +from ....ui_utils import getImageFileFromUiUtils, openImageKeepAspectRatio, retag, getLatestHeight, bindEnterAndLeaveColor, bindButtonPressColor, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressAndReleaseFunction, bindButtonFunctionAndColor, switchActiveTabAndPassiveTab, switchTabsColor + +from utils import callFunctionIfCallable + + +def createSidebarLanguagesSettings(settings, main_window): + + + def switchActiveAndPassivePresetsTabsColor(target_active_widget): + quick_setting_tabs = [ + getattr(main_window, "sls__presets_button_1"), + getattr(main_window, "sls__presets_button_2"), + getattr(main_window, "sls__presets_button_3") + ] + + switchTabsColor( + target_widget=target_active_widget, + tab_buttons=quick_setting_tabs, + active_bg_color=settings.ctm.SLS__PRESETS_TAB_BG_ACTIVE_COLOR, + active_text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR, + passive_bg_color=settings.ctm.SIDEBAR_BG_COLOR, + passive_text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE + ) + + def switchPresetTabFunction(target_active_widget): + switchActiveAndPassivePresetsTabsColor(target_active_widget) + switchActiveTabAndPassiveTab(target_active_widget, main_window.current_active_preset_tab, main_window.current_active_preset_tab.passive_function, settings.ctm.SLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR) + + main_window.sls__optionmenu_your_language.set(main_window.view_variable.VAR_YOUR_LANGUAGE.get()) + main_window.sls__optionmenu_target_language.set(main_window.view_variable.VAR_TARGET_LANGUAGE.get()) + main_window.current_active_preset_tab = target_active_widget + + + + def switchToPreset1(e): + print("1") + callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "1") + target_active_widget = getattr(main_window, "sls__presets_button_1") + switchPresetTabFunction(target_active_widget) + + def switchToPreset2(e): + print("2") + callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "2") + target_active_widget = getattr(main_window, "sls__presets_button_2") + switchPresetTabFunction(target_active_widget) + + def switchToPreset3(e): + print("3") + callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "3") + target_active_widget = getattr(main_window, "sls__presets_button_3") + switchPresetTabFunction(target_active_widget) + + + + + + def createOption_DropdownMenu_for_quickSettings(setattr_obj, parent_widget, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values=None, width:int = 200, font_size:int = 10, text_color="white", command=None, variable=""): + setattr(setattr_obj, optionmenu_attr_name, CTkOptionMenu( + parent_widget, + height=30, + width=width, + values=dropdown_menu_values, + button_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, + fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, + text_color=text_color, + font=CTkFont(family=settings.FONT_FAMILY, size=font_size, weight="normal"), + variable=variable, + anchor="center", + )) + target_optionmenu_attr = getattr(setattr_obj, optionmenu_attr_name) + target_optionmenu_attr.grid(row=0, column=0, sticky="e") + + + + + def createQuickLanguageSettingBox(parent_widget, title_text, title_text_attr_name, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values, variable): + sls__box = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) + + sls__box.columnconfigure((0,2), weight=1) + + sls__box_wrapper = CTkFrame(sls__box, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) + sls__box_wrapper.grid(row=2, column=1, padx=0, pady=settings.uism.SLS__BOX_IPADY) + + sls__label = CTkLabel( + sls__box_wrapper, + text=title_text, + height=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_SECTION_TITLE_FONT_SIZE, weight="normal"), + text_color=settings.ctm.SLS__BOX_SECTION_TITLE_TEXT_COLOR + ) + sls__label.grid(row=0, column=0, pady=(0,settings.uism.SLS__BOX_SECTION_TITLE_BOTTOM_PADY)) + setattr(main_window, title_text_attr_name, sls__label) + + + + + createOption_DropdownMenu_for_quickSettings( + main_window, + sls__box_wrapper, + optionmenu_attr_name, + dropdown_menu_attr_name, + dropdown_menu_values=dropdown_menu_values, + # command=self.fakeCommand, + width=settings.uism.SLS__BOX_DROPDOWN_MENU_WIDTH, + font_size=settings.uism.SLS__BOX_DROPDOWN_MENU_FONT_SIZE, + text_color=settings.ctm.LABELS_TEXT_COLOR, + variable=variable, + # variable=StringVar(value="Chinese, Cantonese\n(Traditional Hong Kong)"), + ) + getattr(main_window, optionmenu_attr_name).grid(row=1, column=0, padx=0, pady=0) + + return sls__box + + + + + + # Sidebar Languages Settings, SLS + main_window.sls__container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + + if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: + main_window.sls__container.grid(row=2, column=0, sticky="new") + + main_window.sls__container.grid_columnconfigure(0, weight=1) + + + main_window.sls__container_title = CTkLabel(main_window.sls__container, + # text="言語設定", + text="Language Settings", + height=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__TITLE_FONT_SIZE, weight="normal"), + text_color=settings.ctm.SLS__TITLE_TEXT_COLOR + ) + main_window.sls__container_title.grid(row=0, column=0, pady=settings.uism.SLS__TITLE_PADY, sticky="nsew") + + + + # Presets buttons + main_window.sidebar_bg_container.grid_rowconfigure(2, weight=1) + main_window.sls__presets_buttons_box = CTkFrame(main_window.sls__container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + main_window.sls__presets_buttons_box.grid(row=1, column=0, sticky="ew") + + main_window.sls__presets_buttons_box.grid_columnconfigure((0,1,2), weight=1) + + + preset_tabs_settings = [ + { + "preset_tab_attr_name": "sls__presets_button_1", + "command": switchToPreset1, + "text": "1", + }, + { + "preset_tab_attr_name": "sls__presets_button_2", + "command": switchToPreset2, + "text": "2", + }, + { + "preset_tab_attr_name": "sls__presets_button_3", + "command": switchToPreset3, + "text": "3", + }, + ] + + column=0 + for preset_tab_settings in preset_tabs_settings: + preset_tab_attr_name = preset_tab_settings["preset_tab_attr_name"] + command = preset_tab_settings["command"] + text = preset_tab_settings["text"] + + setattr( + main_window, + preset_tab_attr_name, + CTkFrame( + main_window.sls__presets_buttons_box, + corner_radius=0, + fg_color=settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR, + width=0, + height=30, + cursor="hand2", + ) + ) + parent_widget = getattr(main_window, preset_tab_attr_name) + parent_widget.grid(row=0, column=column, sticky="ew") + + label_widget = CTkLabel( + parent_widget, + text=text, + height=0, + fg_color=settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__PRESET_TAB_NUMBER_FONT_SIZE, weight="bold"), + anchor="center", + text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE + ) + label_widget.place(relx=0.5, rely=0.5, anchor="center") + + + + bindEnterAndLeaveColor([parent_widget, label_widget], settings.ctm.SLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR) + bindButtonPressColor([parent_widget, label_widget], settings.ctm.SLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR) + + parent_widget.passive_function = command + bindButtonReleaseFunction([parent_widget, label_widget], command) + + column+=1 + + + # Quick Language settings BOX + main_window.sls__box_frame = CTkFrame(main_window.sls__container, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) + main_window.sls__box_frame.grid(row=2, column=0, sticky="ew") + main_window.sls__box_frame.grid_columnconfigure(0, weight=1) + + # Your language + main_window.sls__box_your_language = createQuickLanguageSettingBox( + parent_widget=main_window.sls__box_frame, + # title_text="あなたの言語", + title_text="Your Language", + title_text_attr_name="sls__title_text_your_language", + optionmenu_attr_name="sls__optionmenu_your_language", + dropdown_menu_attr_name="sls__dropdown_menu_your_language", + dropdown_menu_values=["1""2","pppp\npppp"], + variable=main_window.view_variable.VAR_YOUR_LANGUAGE + ) + main_window.sls__box_your_language.grid(row=2, column=0, padx=0, pady=(settings.uism.SLS__BOX_TOP_PADY,0),sticky="ew") + + + # Both direction arrow icon + main_window.sls__arrow_direction_box = CTkFrame(main_window.sls__box_frame, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) + main_window.sls__arrow_direction_box.grid(row=3, column=0, padx=0, pady=settings.uism.SLS__BOX_ARROWS_PADY,sticky="ew") + + main_window.sls__arrow_direction_box.grid_columnconfigure((0,4), weight=1) + + main_window.sls__both_direction_up = CTkLabel( + main_window.sls__arrow_direction_box, + text=None, + height=0, + image=CTkImage(getImageFileFromUiUtils(settings.image_filename.NARROW_ARROW_DOWN).rotate(180),size=settings.uism.SLS__BOX_ARROWS_IMAGE_SIZE) + + ) + main_window.sls__both_direction_up.grid(row=0, column=1, pady=0) + + main_window.sls__both_direction_desc = CTkLabel( + main_window.sls__arrow_direction_box, + # text="双方向に翻訳", + text="Translate Each Other", + height=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_ARROWS_DESC_FONT_SIZE, weight="normal"), + text_color=settings.ctm.SLS__BOX_ARROWS_TEXT_COLOR, + ) + main_window.sls__both_direction_desc.grid(row=0, column=2, padx=settings.uism.SLS__BOX_ARROWS_DESC_PADX, pady=0) + + main_window.sls__both_direction_label_down = CTkLabel( + main_window.sls__arrow_direction_box, + text=None, + height=0, + image=CTkImage(getImageFileFromUiUtils(settings.image_filename.NARROW_ARROW_DOWN).rotate(0),size=settings.uism.SLS__BOX_ARROWS_IMAGE_SIZE) + + ) + main_window.sls__both_direction_label_down.grid(row=0, column=3, pady=0) + + + + # Target language + main_window.sls__box_target_language = createQuickLanguageSettingBox( + parent_widget=main_window.sls__box_frame, + # title_text="相手の言語", + title_text="Target Language", + title_text_attr_name="sls__title_text_target_language", + optionmenu_attr_name="sls__optionmenu_target_language", + dropdown_menu_attr_name="sls__dropdown_menu_target_language", + dropdown_menu_values=["1""2","pppp\npppp2"], + variable=main_window.view_variable.VAR_TARGET_LANGUAGE + ) + main_window.sls__box_target_language.grid(row=4, column=0, padx=0, pady=(0,0),sticky="ew") + + + + + + # Config Button + main_window.sidebar_config_button_container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, width=0, height=0) + main_window.sidebar_config_button_container.grid(row=3, column=0, sticky="ew") + + + main_window.sidebar_config_button_container.grid_columnconfigure(0, weight=1) + main_window.sidebar_config_button_wrapper = CTkFrame(main_window.sidebar_config_button_container, corner_radius=settings.uism.SIDEBAR_CONFIG_BUTTON_CORNER_RADIUS, fg_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, height=0, width=0, cursor="hand2") + main_window.sidebar_config_button_wrapper.grid(row=0, column=0, padx=settings.uism.SIDEBAR_CONFIG_BUTTON_PADX, pady=settings.uism.SIDEBAR_CONFIG_BUTTON_PADY, sticky="ew") + + + + + main_window.sidebar_config_button_wrapper.grid_columnconfigure(0, weight=1) + + settings.uism.CONFIG_BUTTON_PADX= 0 + main_window.sidebar_config_button = CTkLabel( + main_window.sidebar_config_button_wrapper, + text=None, + height=0, + image=CTkImage(getImageFileFromUiUtils(settings.image_filename.CONFIGURATION_ICON),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)) + ) + main_window.sidebar_config_button.grid(row=0, column=0, padx=0, pady=settings.uism.SIDEBAR_CONFIG_BUTTON_IPADY) + + + bindButtonFunctionAndColor( + target_widgets=[main_window.sidebar_config_button_wrapper, main_window.sidebar_config_button], + enter_color=settings.ctm.CONFIG_BUTTON_HOVERED_BG_COLOR, + leave_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, + clicked_color=settings.ctm.CONFIG_BUTTON_CLICKED_BG_COLOR, + buttonReleasedFunction=main_window.openConfigWindow, + ) + diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index e0c9f1ae..c6672ad2 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -4,575 +4,13 @@ from ...ui_utils import getImageFileFromUiUtils, openImageKeepAspectRatio, retag from utils import callFunctionIfCallable +from ._create_sidebar import createSidebarFeatures, createSidebarLanguagesSettings def createSidebar(settings, main_window): - - def toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, mark): - mark.place(relx=0.85) if is_turned_on else mark.place(relx=-1) - - - def toggleTranslationFeature(): - is_turned_on = main_window.translation_switch_box.get() - callFunctionIfCallable(main_window.CALLBACK_TOGGLE_TRANSLATION, is_turned_on) - toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.translation_selected_mark) - - def toggleTranscriptionSendFeature(): - is_turned_on = main_window.transcription_send_switch_box.get() - callFunctionIfCallable(main_window.CALLBACK_TOGGLE_TRANSCRIPTION_SEND, is_turned_on) - toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_send_selected_mark) - - def toggleTranscriptionReceiveFeature(): - is_turned_on = main_window.transcription_receive_switch_box.get() - callFunctionIfCallable(main_window.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE, is_turned_on) - toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_receive_selected_mark) - - def toggleForegroundFeature(): - is_turned_on = main_window.foreground_switch_box.get() - callFunctionIfCallable(main_window.CALLBACK_TOGGLE_FOREGROUND, is_turned_on) - toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.foreground_selected_mark) - - - - - def changeSidebarFeaturesColorByEvents(ww, event_name): - target_frame_widget = getattr(main_window, ww) - if event_name == "enter": - target_frame_widget.configure(fg_color=settings.ctm.SF__HOVERED_BG_COLOR) - target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_HOVERED_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR) - target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_HOVERED_BG_COLOR) - - elif event_name == "leave": - target_frame_widget.configure(fg_color=settings.ctm.SF__BG_COLOR) - target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR) - target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) - - elif event_name == "button_press": - target_frame_widget.configure(fg_color=settings.ctm.SF__CLICKED_BG_COLOR) - target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_CLICKED_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR) - target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_CLICKED_BG_COLOR) - - elif event_name == "button_release": - target_frame_widget.configure(fg_color=settings.ctm.SF__BG_COLOR) - target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR) - target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) - - - - - - def switchActiveAndPassivePresetsTabsColor(target_active_widget): - quick_setting_tabs = [ - getattr(main_window, "sls__presets_button_1"), - getattr(main_window, "sls__presets_button_2"), - getattr(main_window, "sls__presets_button_3") - ] - - switchTabsColor( - target_widget=target_active_widget, - tab_buttons=quick_setting_tabs, - active_bg_color=settings.ctm.SLS__PRESETS_TAB_BG_ACTIVE_COLOR, - active_text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR, - passive_bg_color=settings.ctm.SIDEBAR_BG_COLOR, - passive_text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE - ) - - def switchPresetTabFunction(target_active_widget): - switchActiveAndPassivePresetsTabsColor(target_active_widget) - switchActiveTabAndPassiveTab(target_active_widget, main_window.current_active_preset_tab, main_window.current_active_preset_tab.passive_function, settings.ctm.SLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR) - - main_window.sls__optionmenu_your_language.set(main_window.view_variable.VAR_YOUR_LANGUAGE.get()) - main_window.sls__optionmenu_target_language.set(main_window.view_variable.VAR_TARGET_LANGUAGE.get()) - main_window.current_active_preset_tab = target_active_widget - - - - def switchToPreset1(e): - print("1") - callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "1") - target_active_widget = getattr(main_window, "sls__presets_button_1") - switchPresetTabFunction(target_active_widget) - - def switchToPreset2(e): - print("2") - callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "2") - target_active_widget = getattr(main_window, "sls__presets_button_2") - switchPresetTabFunction(target_active_widget) - - def switchToPreset3(e): - print("3") - callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "3") - target_active_widget = getattr(main_window, "sls__presets_button_3") - switchPresetTabFunction(target_active_widget) - - - - - - def createOption_DropdownMenu_for_quickSettings(setattr_obj, parent_widget, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values=None, width:int = 200, font_size:int = 10, text_color="white", command=None, variable=""): - setattr(setattr_obj, optionmenu_attr_name, CTkOptionMenu( - parent_widget, - height=30, - width=width, - values=dropdown_menu_values, - button_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, - fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, - text_color=text_color, - font=CTkFont(family=settings.FONT_FAMILY, size=font_size, weight="normal"), - variable=variable, - anchor="center", - )) - target_optionmenu_attr = getattr(setattr_obj, optionmenu_attr_name) - target_optionmenu_attr.grid(row=0, column=0, sticky="e") - - - - - def createQuickLanguageSettingBox(parent_widget, title_text, title_text_attr_name, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values, variable): - sls__box = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) - - sls__box.columnconfigure((0,2), weight=1) - - sls__box_wrapper = CTkFrame(sls__box, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) - sls__box_wrapper.grid(row=2, column=1, padx=0, pady=settings.uism.SLS__BOX_IPADY) - - sls__label = CTkLabel( - sls__box_wrapper, - text=title_text, - height=0, - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_SECTION_TITLE_FONT_SIZE, weight="normal"), - text_color=settings.ctm.SLS__BOX_SECTION_TITLE_TEXT_COLOR - ) - sls__label.grid(row=0, column=0, pady=(0,settings.uism.SLS__BOX_SECTION_TITLE_BOTTOM_PADY)) - setattr(main_window, title_text_attr_name, sls__label) - - - - - createOption_DropdownMenu_for_quickSettings( - main_window, - sls__box_wrapper, - optionmenu_attr_name, - dropdown_menu_attr_name, - dropdown_menu_values=dropdown_menu_values, - # command=self.fakeCommand, - width=settings.uism.SLS__BOX_DROPDOWN_MENU_WIDTH, - font_size=settings.uism.SLS__BOX_DROPDOWN_MENU_FONT_SIZE, - text_color=settings.ctm.LABELS_TEXT_COLOR, - variable=variable, - # variable=StringVar(value="Chinese, Cantonese\n(Traditional Hong Kong)"), - ) - getattr(main_window, optionmenu_attr_name).grid(row=1, column=0, padx=0, pady=0) - - return sls__box - - - - - - # Side Bar Container main_window.sidebar_bg_container = CTkFrame(main_window, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) main_window.sidebar_bg_container.grid(row=0, column=0, sticky="nsew") - MIN_SIDEBAR_WIDTH = settings.uism.COMPACT_MODE_SIDEBAR_WIDTH if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True else settings.uism.SIDEBAR_WIDTH - main_window.sidebar_bg_container.grid_columnconfigure(0, weight=0, minsize=MIN_SIDEBAR_WIDTH) - main_window.grid_rowconfigure(0, weight=1) - - - (img, width, height) = openImageKeepAspectRatio(settings.image_filename.VRCT_LOGO, settings.uism.SF__LOGO_MAX_SIZE) - main_window.sidebar_logo = CTkLabel( - main_window.sidebar_bg_container, - fg_color=settings.ctm.SIDEBAR_BG_COLOR, - text=None, - height=0, - image=CTkImage(img, size=(width,height)) - ) - - # "height-a_s" represents the adjustment in size, and the "pady+a_s/2" indicates a padding increase of 4 pixels to compensate for the reduction. - a_s = settings.uism.SF__LOGO_HEIGHT_FOR_ADJUSTMENT - (img, width, height) = openImageKeepAspectRatio(settings.image_filename.VRCT_LOGO_MARK, height-a_s) - main_window.sidebar_compact_mode_logo = CTkLabel( - main_window.sidebar_bg_container, - fg_color=settings.ctm.SIDEBAR_BG_COLOR, - text=None, - height=0, - image=CTkImage(img, size=(width,height)) - ) - - if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - main_window.sidebar_compact_mode_logo.grid(row=0, column=0, pady=( - int(settings.uism.SF__LOGO_PADY[0]+a_s/2), - int(settings.uism.SF__LOGO_PADY[1]+a_s/2) - )) - else: - main_window.sidebar_logo.grid(row=0, column=0, pady=settings.uism.SF__LOGO_PADY) - - # Sidebar Features - main_window.sidebar_features_container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color="transparent", width=0, height=0) - main_window.sidebar_features_container.grid(row=1, column=0, pady=0, sticky="ew") - main_window.sidebar_features_container.grid_columnconfigure(0, weight=1) - - - - sidebar_features_settings = [ - { - "frame_attr_name": "translation_frame", - "command": toggleTranslationFeature, - "switch_box_attr_name": "translation_switch_box", - "toggle_switch_box_command": lambda e: main_window.translation_switch_box.toggle(), - "label_attr_name": "label_translation", - "compact_mode_icon_attr_name": "translation_compact_mode_icon", - "selected_mark_attr_name": "translation_selected_mark", - "var_label_text": main_window.view_variable.VAR_LABEL_TRANSLATION, - "icon_file_name": settings.image_filename.TRANSLATION_ICON, - }, - { - "frame_attr_name": "transcription_send_frame", - "command": toggleTranscriptionSendFeature, - "switch_box_attr_name": "transcription_send_switch_box", - "toggle_switch_box_command": lambda e: main_window.transcription_send_switch_box.toggle(), - "label_attr_name": "label_transcription_send", - "compact_mode_icon_attr_name": "transcription_send_compact_mode_icon", - "selected_mark_attr_name": "transcription_send_selected_mark", - "var_label_text": main_window.view_variable.VAR_LABEL_TRANSCRIPTION_SEND, - "icon_file_name": settings.image_filename.MIC_ICON, - }, - { - "frame_attr_name": "transcription_receive_frame", - "command": toggleTranscriptionReceiveFeature, - "switch_box_attr_name": "transcription_receive_switch_box", - "toggle_switch_box_command": lambda e: main_window.transcription_receive_switch_box.toggle(), - "label_attr_name": "label_transcription_receive", - "compact_mode_icon_attr_name": "transcription_receive_compact_mode_icon", - "selected_mark_attr_name": "transcription_receive_selected_mark", - "var_label_text": main_window.view_variable.VAR_LABEL_TRANSCRIPTION_RECEIVE, - "icon_file_name": settings.image_filename.HEADPHONES_ICON, - }, - { - "frame_attr_name": "foreground_frame", - "command": toggleForegroundFeature, - "switch_box_attr_name": "foreground_switch_box", - "toggle_switch_box_command": lambda e: main_window.foreground_switch_box.toggle(), - "label_attr_name": "label_foreground", - "compact_mode_icon_attr_name": "foreground_compact_mode_icon", - "selected_mark_attr_name": "foreground_selected_mark", - "var_label_text": main_window.view_variable.VAR_LABEL_FOREGROUND, - "icon_file_name": settings.image_filename.FOREGROUND_ICON, - }, - ] - - - - row=0 - for sfs in sidebar_features_settings: - frame_attr_name = sfs["frame_attr_name"] - command = sfs["command"] - switch_box_attr_name = sfs["switch_box_attr_name"] - toggle_switch_box_command = sfs["toggle_switch_box_command"] - label_attr_name = sfs["label_attr_name"] - compact_mode_icon_attr_name = sfs["compact_mode_icon_attr_name"] - selected_mark_attr_name = sfs["selected_mark_attr_name"] - var_label_text = sfs["var_label_text"] - icon_file_name = sfs["icon_file_name"] - - frame_widget = CTkFrame(main_window.sidebar_features_container, corner_radius=0, fg_color=settings.ctm.SF__BG_COLOR, cursor="hand2", width=0, height=0) - setattr(main_window, frame_attr_name, frame_widget) - - frame_widget.grid(row=row, column=0, pady=(0,1), sticky="ew") - frame_widget.grid_columnconfigure(0, weight=1) - - label_widget = CTkLabel( - frame_widget, - textvariable=var_label_text, - height=0, - corner_radius=0, - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SF__LABEL_FONT_SIZE, weight="normal"), - anchor="w", - text_color=settings.ctm.LABELS_TEXT_COLOR, - ) - setattr(main_window, label_attr_name, label_widget) - - - switch_box_widget = CTkSwitch( - frame_widget, - text=None, - height=0, - width=0, - corner_radius=int(settings.uism.SF__SWITCH_BOX_HEIGHT/2), - border_width=0, - switch_height=settings.uism.SF__SWITCH_BOX_HEIGHT, - switch_width=settings.uism.SF__SWITCH_BOX_WIDTH, - onvalue=True, - offvalue=False, - command=command, - fg_color=settings.ctm.SF__SWITCH_BOX_BG_COLOR, - bg_color=settings.ctm.SF__BG_COLOR, - progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR, - ) - # # if sfs["is_checked"] is True: - # # target_attr.select() - # # else: - # # target_attr.deselect() - setattr(main_window, switch_box_attr_name, switch_box_widget) - - - if settings.COMPACT_MODE_ICON_SIZE == 0: - label_widget.grid(row=row, column=0, pady=settings.uism.SF__LABELS_IPADY, padx=(settings.uism.SF__LABEL_LEFT_PAD,0), sticky="ew") - settings.COMPACT_MODE_ICON_SIZE = int(getLatestHeight(frame_widget) - settings.uism.SF__COMPACT_MODE_ICON_PADY*2) - label_widget.grid_remove() - - - # for compact mode - compact_mode_icon_widget = CTkLabel( - frame_widget, - text=None, - height=0, - corner_radius=0, - image=CTkImage(getImageFileFromUiUtils(icon_file_name),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)), - ) - setattr(main_window, compact_mode_icon_attr_name, compact_mode_icon_widget) - - selected_mark_widget = CTkFrame(frame_widget, corner_radius=0, fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR, width=settings.uism.SF__SELECTED_MARK_WIDTH, height=0) - setattr(main_window, selected_mark_attr_name, selected_mark_widget) - - - # Arrange - if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - compact_mode_icon_widget.grid(row=row, column=0, pady=settings.uism.SF__COMPACT_MODE_ICON_PADY) - selected_mark_widget.place(relx=-1, rely=0.5, relheight=0.75, anchor="center") - else: - label_widget.grid(row=row, column=0, pady=settings.uism.SF__LABELS_IPADY, padx=(settings.uism.SF__LABEL_LEFT_PAD,0), sticky="ew") - switch_box_widget.grid(row=row, column=0, padx=(0,settings.uism.SF__SWITCH_BOX_RIGHT_PAD), sticky="e") - - - # Unbind the event "" originally set up by the widget to manually control it. - switch_box_widget._canvas.unbind("") - switch_box_widget._text_label.unbind("") - - bindButtonReleaseFunction([compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], toggle_switch_box_command) - - retag("vrct_"+frame_attr_name, compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget) - - def commonEventFunction(e, event_type): - for ww in e.widget.master.bindtags(): - if ww.startswith("vrct_"): - ww = ww.replace("vrct_", "") - changeSidebarFeaturesColorByEvents(ww, event_type) - break - - def enterFunction(e): - commonEventFunction(e, "enter") - - def leaveFunction(e): - commonEventFunction(e, "leave") - - def buttonPressFunction(e): - commonEventFunction(e, "button_press") - - def buttonReleasedFunction(e): - commonEventFunction(e, "button_release") - - bindEnterAndLeaveFunction([compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], enterFunction, leaveFunction) - - bindButtonPressAndReleaseFunction([compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], buttonPressFunction, buttonReleasedFunction) - - row+=1 - - - - - - # Sidebar Quick Language Settings, SQLS - main_window.sls__container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) - - if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: - main_window.sls__container.grid(row=2, column=0, sticky="new") - - main_window.sls__container.grid_columnconfigure(0, weight=1) - - - main_window.sls__container_title = CTkLabel(main_window.sls__container, - # text="言語設定", - text="Language Settings", - height=0, - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__TITLE_FONT_SIZE, weight="normal"), - text_color=settings.ctm.SLS__TITLE_TEXT_COLOR - ) - main_window.sls__container_title.grid(row=0, column=0, pady=settings.uism.SLS__TITLE_PADY, sticky="nsew") - - - - # Presets buttons - main_window.sidebar_bg_container.grid_rowconfigure(2, weight=1) - main_window.sls__presets_buttons_box = CTkFrame(main_window.sls__container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) - main_window.sls__presets_buttons_box.grid(row=1, column=0, sticky="ew") - - main_window.sls__presets_buttons_box.grid_columnconfigure((0,1,2), weight=1) - - - preset_tabs_settings = [ - { - "preset_tab_attr_name": "sls__presets_button_1", - "command": switchToPreset1, - "text": "1", - }, - { - "preset_tab_attr_name": "sls__presets_button_2", - "command": switchToPreset2, - "text": "2", - }, - { - "preset_tab_attr_name": "sls__presets_button_3", - "command": switchToPreset3, - "text": "3", - }, - ] - - column=0 - for preset_tab_settings in preset_tabs_settings: - preset_tab_attr_name = preset_tab_settings["preset_tab_attr_name"] - command = preset_tab_settings["command"] - text = preset_tab_settings["text"] - - setattr( - main_window, - preset_tab_attr_name, - CTkFrame( - main_window.sls__presets_buttons_box, - corner_radius=0, - fg_color=settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR, - width=0, - height=30, - cursor="hand2", - ) - ) - parent_widget = getattr(main_window, preset_tab_attr_name) - parent_widget.grid(row=0, column=column, sticky="ew") - - label_widget = CTkLabel( - parent_widget, - text=text, - height=0, - fg_color=settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR, - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__PRESET_TAB_NUMBER_FONT_SIZE, weight="bold"), - anchor="center", - text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE - ) - label_widget.place(relx=0.5, rely=0.5, anchor="center") - - - - bindEnterAndLeaveColor([parent_widget, label_widget], settings.ctm.SLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR) - bindButtonPressColor([parent_widget, label_widget], settings.ctm.SLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR) - - parent_widget.passive_function = command - bindButtonReleaseFunction([parent_widget, label_widget], command) - - column+=1 - - - # Quick Language settings BOX - main_window.sls__box_frame = CTkFrame(main_window.sls__container, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) - main_window.sls__box_frame.grid(row=2, column=0, sticky="ew") - main_window.sls__box_frame.grid_columnconfigure(0, weight=1) - - # Your language - main_window.sls__box_your_language = createQuickLanguageSettingBox( - parent_widget=main_window.sls__box_frame, - # title_text="あなたの言語", - title_text="Your Language", - title_text_attr_name="sls__title_text_your_language", - optionmenu_attr_name="sls__optionmenu_your_language", - dropdown_menu_attr_name="sls__dropdown_menu_your_language", - dropdown_menu_values=["1""2","pppp\npppp"], - variable=main_window.view_variable.VAR_YOUR_LANGUAGE - ) - main_window.sls__box_your_language.grid(row=2, column=0, padx=0, pady=(settings.uism.SLS__BOX_TOP_PADY,0),sticky="ew") - - - # Both direction arrow icon - main_window.sls__arrow_direction_box = CTkFrame(main_window.sls__box_frame, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) - main_window.sls__arrow_direction_box.grid(row=3, column=0, padx=0, pady=settings.uism.SLS__BOX_ARROWS_PADY,sticky="ew") - - main_window.sls__arrow_direction_box.grid_columnconfigure((0,4), weight=1) - - main_window.sls__both_direction_up = CTkLabel( - main_window.sls__arrow_direction_box, - text=None, - height=0, - image=CTkImage(getImageFileFromUiUtils(settings.image_filename.NARROW_ARROW_DOWN).rotate(180),size=settings.uism.SLS__BOX_ARROWS_IMAGE_SIZE) - - ) - main_window.sls__both_direction_up.grid(row=0, column=1, pady=0) - - main_window.sls__both_direction_desc = CTkLabel( - main_window.sls__arrow_direction_box, - # text="双方向に翻訳", - text="Translate Each Other", - height=0, - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_ARROWS_DESC_FONT_SIZE, weight="normal"), - text_color=settings.ctm.SLS__BOX_ARROWS_TEXT_COLOR, - ) - main_window.sls__both_direction_desc.grid(row=0, column=2, padx=settings.uism.SLS__BOX_ARROWS_DESC_PADX, pady=0) - - main_window.sls__both_direction_label_down = CTkLabel( - main_window.sls__arrow_direction_box, - text=None, - height=0, - image=CTkImage(getImageFileFromUiUtils(settings.image_filename.NARROW_ARROW_DOWN).rotate(0),size=settings.uism.SLS__BOX_ARROWS_IMAGE_SIZE) - - ) - main_window.sls__both_direction_label_down.grid(row=0, column=3, pady=0) - - - - # Target language - main_window.sls__box_target_language = createQuickLanguageSettingBox( - parent_widget=main_window.sls__box_frame, - # title_text="相手の言語", - title_text="Target Language", - title_text_attr_name="sls__title_text_target_language", - optionmenu_attr_name="sls__optionmenu_target_language", - dropdown_menu_attr_name="sls__dropdown_menu_target_language", - dropdown_menu_values=["1""2","pppp\npppp2"], - variable=main_window.view_variable.VAR_TARGET_LANGUAGE - ) - main_window.sls__box_target_language.grid(row=4, column=0, padx=0, pady=(0,0),sticky="ew") - - - - - - # Config Button - main_window.sidebar_config_button_container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, width=0, height=0) - main_window.sidebar_config_button_container.grid(row=3, column=0, sticky="ew") - - - main_window.sidebar_config_button_container.grid_columnconfigure(0, weight=1) - main_window.sidebar_config_button_wrapper = CTkFrame(main_window.sidebar_config_button_container, corner_radius=settings.uism.SIDEBAR_CONFIG_BUTTON_CORNER_RADIUS, fg_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, height=0, width=0, cursor="hand2") - main_window.sidebar_config_button_wrapper.grid(row=0, column=0, padx=settings.uism.SIDEBAR_CONFIG_BUTTON_PADX, pady=settings.uism.SIDEBAR_CONFIG_BUTTON_PADY, sticky="ew") - - - - - main_window.sidebar_config_button_wrapper.grid_columnconfigure(0, weight=1) - - settings.uism.CONFIG_BUTTON_PADX= 0 - main_window.sidebar_config_button = CTkLabel( - main_window.sidebar_config_button_wrapper, - text=None, - height=0, - image=CTkImage(getImageFileFromUiUtils(settings.image_filename.CONFIGURATION_ICON),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)) - ) - main_window.sidebar_config_button.grid(row=0, column=0, padx=0, pady=settings.uism.SIDEBAR_CONFIG_BUTTON_IPADY) - - - bindButtonFunctionAndColor( - target_widgets=[main_window.sidebar_config_button_wrapper, main_window.sidebar_config_button], - enter_color=settings.ctm.CONFIG_BUTTON_HOVERED_BG_COLOR, - leave_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, - clicked_color=settings.ctm.CONFIG_BUTTON_CLICKED_BG_COLOR, - buttonReleasedFunction=main_window.openConfigWindow, - ) - + createSidebarFeatures(settings, main_window) + createSidebarLanguagesSettings(settings, main_window) \ No newline at end of file From 434faafe94e3022f138e088bdcafe50cf9293ec0 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 4 Sep 2023 04:53:37 +0900 Subject: [PATCH 079/355] =?UTF-8?q?[bugfix]=20Main=20Window:=20Sidebar=20c?= =?UTF-8?q?ompact=20mode.=20=E3=82=B5=E3=82=A4=E3=83=89=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=91=E3=82=AF=E3=83=88=E3=83=A2=E3=83=BC?= =?UTF-8?q?=E3=83=89=E5=88=87=E3=82=8A=E6=9B=BF=E3=81=88=E6=99=82=E3=81=AB?= =?UTF-8?q?=E3=80=81=E5=90=84=E6=A9=9F=E8=83=BD=E3=81=AEON/OFF=E7=8A=B6?= =?UTF-8?q?=E6=85=8B=E3=80=81=E9=81=B8=E6=8A=9E=E3=81=95=E3=82=8C=E3=81=9F?= =?UTF-8?q?language=20preset=20tab=20no=E3=82=92=E4=BF=9D=E6=8C=81?= =?UTF-8?q?=E3=81=97=E3=81=9F=E3=81=BE=E3=81=BE=E9=96=8B=E9=96=89=E3=81=A7?= =?UTF-8?q?=E3=81=8D=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=80=82=20?= =?UTF-8?q?=E5=85=B7=E4=BD=93=E7=9A=84=E3=81=AB=E3=81=AF=E4=BB=8A=E3=81=BE?= =?UTF-8?q?=E3=81=A7=E3=82=B5=E3=82=A4=E3=83=89=E3=83=90=E3=83=BC=E9=96=8B?= =?UTF-8?q?=E9=96=89=E3=81=AFdestroy=E9=96=A2=E6=95=B0=E3=82=92=E4=BD=BF?= =?UTF-8?q?=E7=94=A8=E3=81=97=E3=81=9F=E5=BE=8Cwidget=E3=81=94=E3=81=A8?= =?UTF-8?q?=E5=86=8D=E7=94=9F=E6=88=90=E3=81=97=E3=81=A6=E3=81=84=E3=81=9F?= =?UTF-8?q?=E3=81=AE=E3=82=92=E3=80=81grid=5Fremove()=20->=20grid()=20?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=97=E3=80=81=E5=86=8D=E7=94=9F?= =?UTF-8?q?=E6=88=90=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F=E5=86=8D=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E3=81=AB=E3=81=97=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_create_sidebar/createSidebarFeatures.py | 90 +++++++++++-------- .../createSidebarLanguagesSettings.py | 3 +- .../main_window/widgets/create_sidebar.py | 19 +++- vrct_gui/vrct_gui.py | 9 +- 4 files changed, 77 insertions(+), 44 deletions(-) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py index 4787cea4..cb99303b 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py @@ -36,25 +36,30 @@ def createSidebarFeatures(settings, main_window): def changeSidebarFeaturesColorByEvents(ww, event_name): target_frame_widget = getattr(main_window, ww) + target_compact_mode_frame_widget = getattr(main_window, "compact_mode_"+ww) if event_name == "enter": target_frame_widget.configure(fg_color=settings.ctm.SF__HOVERED_BG_COLOR) target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_HOVERED_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR) - target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_HOVERED_BG_COLOR) + target_compact_mode_frame_widget.configure(fg_color=settings.ctm.SF__HOVERED_BG_COLOR) + target_compact_mode_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_HOVERED_BG_COLOR) elif event_name == "leave": target_frame_widget.configure(fg_color=settings.ctm.SF__BG_COLOR) target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR) - target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) + target_compact_mode_frame_widget.configure(fg_color=settings.ctm.SF__BG_COLOR) + target_compact_mode_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) elif event_name == "button_press": target_frame_widget.configure(fg_color=settings.ctm.SF__CLICKED_BG_COLOR) target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_CLICKED_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR) - target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_CLICKED_BG_COLOR) + target_compact_mode_frame_widget.configure(fg_color=settings.ctm.SF__CLICKED_BG_COLOR) + target_compact_mode_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_CLICKED_BG_COLOR) elif event_name == "button_release": target_frame_widget.configure(fg_color=settings.ctm.SF__BG_COLOR) target_frame_widget.children["!ctkswitch"].configure(fg_color=settings.ctm.SF__SWITCH_BOX_BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR) - target_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) + target_compact_mode_frame_widget.configure(fg_color=settings.ctm.SF__BG_COLOR) + target_compact_mode_frame_widget.children["!ctkframe"].configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) @@ -62,14 +67,6 @@ def createSidebarFeatures(settings, main_window): - # # Side Bar Container - # main_window.sidebar_bg_container = CTkFrame(main_window, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) - # main_window.sidebar_bg_container.grid(row=0, column=0, sticky="nsew") - - - MIN_SIDEBAR_WIDTH = settings.uism.COMPACT_MODE_SIDEBAR_WIDTH if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True else settings.uism.SIDEBAR_WIDTH - main_window.sidebar_bg_container.grid_columnconfigure(0, weight=0, minsize=MIN_SIDEBAR_WIDTH) - main_window.grid_rowconfigure(0, weight=1) (img, width, height) = openImageKeepAspectRatio(settings.image_filename.VRCT_LOGO, settings.uism.SF__LOGO_MAX_SIZE) @@ -80,25 +77,22 @@ def createSidebarFeatures(settings, main_window): height=0, image=CTkImage(img, size=(width,height)) ) + main_window.sidebar_logo.grid(row=0, column=0, pady=settings.uism.SF__LOGO_PADY) # "height-a_s" represents the adjustment in size, and the "pady+a_s/2" indicates a padding increase of 4 pixels to compensate for the reduction. a_s = settings.uism.SF__LOGO_HEIGHT_FOR_ADJUSTMENT (img, width, height) = openImageKeepAspectRatio(settings.image_filename.VRCT_LOGO_MARK, height-a_s) main_window.sidebar_compact_mode_logo = CTkLabel( - main_window.sidebar_bg_container, + main_window.sidebar_compact_mode_bg_container, fg_color=settings.ctm.SIDEBAR_BG_COLOR, text=None, height=0, image=CTkImage(img, size=(width,height)) ) - - if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - main_window.sidebar_compact_mode_logo.grid(row=0, column=0, pady=( - int(settings.uism.SF__LOGO_PADY[0]+a_s/2), - int(settings.uism.SF__LOGO_PADY[1]+a_s/2) - )) - else: - main_window.sidebar_logo.grid(row=0, column=0, pady=settings.uism.SF__LOGO_PADY) + main_window.sidebar_compact_mode_logo.grid(row=0, column=0, pady=( + int(settings.uism.SF__LOGO_PADY[0]+a_s/2), + int(settings.uism.SF__LOGO_PADY[1]+a_s/2) + )) # Sidebar Features main_window.sidebar_features_container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color="transparent", width=0, height=0) @@ -107,6 +101,13 @@ def createSidebarFeatures(settings, main_window): + # Sidebar Features Compact Mode + main_window.sidebar_compact_mode_features_container = CTkFrame(main_window.sidebar_compact_mode_bg_container, corner_radius=0, fg_color="transparent", width=0, height=0) + main_window.sidebar_compact_mode_features_container.grid(row=1, column=0, pady=0, sticky="ew") + main_window.sidebar_compact_mode_features_container.grid_columnconfigure(0, weight=1) + + + sidebar_features_settings = [ { "frame_attr_name": "translation_frame", @@ -115,6 +116,7 @@ def createSidebarFeatures(settings, main_window): "toggle_switch_box_command": lambda e: main_window.translation_switch_box.toggle(), "label_attr_name": "label_translation", "compact_mode_icon_attr_name": "translation_compact_mode_icon", + "compact_mode_frame_attr_name": "compact_mode_translation_frame", "selected_mark_attr_name": "translation_selected_mark", "var_label_text": main_window.view_variable.VAR_LABEL_TRANSLATION, "icon_file_name": settings.image_filename.TRANSLATION_ICON, @@ -126,6 +128,7 @@ def createSidebarFeatures(settings, main_window): "toggle_switch_box_command": lambda e: main_window.transcription_send_switch_box.toggle(), "label_attr_name": "label_transcription_send", "compact_mode_icon_attr_name": "transcription_send_compact_mode_icon", + "compact_mode_frame_attr_name": "compact_mode_transcription_send_frame", "selected_mark_attr_name": "transcription_send_selected_mark", "var_label_text": main_window.view_variable.VAR_LABEL_TRANSCRIPTION_SEND, "icon_file_name": settings.image_filename.MIC_ICON, @@ -137,6 +140,7 @@ def createSidebarFeatures(settings, main_window): "toggle_switch_box_command": lambda e: main_window.transcription_receive_switch_box.toggle(), "label_attr_name": "label_transcription_receive", "compact_mode_icon_attr_name": "transcription_receive_compact_mode_icon", + "compact_mode_frame_attr_name": "compact_mode_transcription_receive_frame", "selected_mark_attr_name": "transcription_receive_selected_mark", "var_label_text": main_window.view_variable.VAR_LABEL_TRANSCRIPTION_RECEIVE, "icon_file_name": settings.image_filename.HEADPHONES_ICON, @@ -148,6 +152,7 @@ def createSidebarFeatures(settings, main_window): "toggle_switch_box_command": lambda e: main_window.foreground_switch_box.toggle(), "label_attr_name": "label_foreground", "compact_mode_icon_attr_name": "foreground_compact_mode_icon", + "compact_mode_frame_attr_name": "compact_mode_foreground_frame", "selected_mark_attr_name": "foreground_selected_mark", "var_label_text": main_window.view_variable.VAR_LABEL_FOREGROUND, "icon_file_name": settings.image_filename.FOREGROUND_ICON, @@ -164,6 +169,7 @@ def createSidebarFeatures(settings, main_window): toggle_switch_box_command = sfs["toggle_switch_box_command"] label_attr_name = sfs["label_attr_name"] compact_mode_icon_attr_name = sfs["compact_mode_icon_attr_name"] + compact_mode_frame_attr_name = sfs["compact_mode_frame_attr_name"] selected_mark_attr_name = sfs["selected_mark_attr_name"] var_label_text = sfs["var_label_text"] icon_file_name = sfs["icon_file_name"] @@ -174,6 +180,13 @@ def createSidebarFeatures(settings, main_window): frame_widget.grid(row=row, column=0, pady=(0,1), sticky="ew") frame_widget.grid_columnconfigure(0, weight=1) + compact_mode_frame_widget = CTkFrame(main_window.sidebar_compact_mode_features_container, corner_radius=0, fg_color=settings.ctm.SF__BG_COLOR, cursor="hand2", width=0, height=0) + setattr(main_window, compact_mode_frame_attr_name, compact_mode_frame_widget) + + compact_mode_frame_widget.grid(row=row, column=0, pady=(0,1), sticky="ew") + compact_mode_frame_widget.grid_columnconfigure(0, weight=1) + + label_widget = CTkLabel( frame_widget, textvariable=var_label_text, @@ -217,7 +230,7 @@ def createSidebarFeatures(settings, main_window): # for compact mode compact_mode_icon_widget = CTkLabel( - frame_widget, + compact_mode_frame_widget, text=None, height=0, corner_radius=0, @@ -225,26 +238,31 @@ def createSidebarFeatures(settings, main_window): ) setattr(main_window, compact_mode_icon_attr_name, compact_mode_icon_widget) - selected_mark_widget = CTkFrame(frame_widget, corner_radius=0, fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR, width=settings.uism.SF__SELECTED_MARK_WIDTH, height=0) + selected_mark_widget = CTkFrame( + compact_mode_frame_widget, + corner_radius=0, + fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR, + width=settings.uism.SF__SELECTED_MARK_WIDTH, + height=0 + ) setattr(main_window, selected_mark_attr_name, selected_mark_widget) # Arrange - if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - compact_mode_icon_widget.grid(row=row, column=0, pady=settings.uism.SF__COMPACT_MODE_ICON_PADY) - selected_mark_widget.place(relx=-1, rely=0.5, relheight=0.75, anchor="center") - else: - label_widget.grid(row=row, column=0, pady=settings.uism.SF__LABELS_IPADY, padx=(settings.uism.SF__LABEL_LEFT_PAD,0), sticky="ew") - switch_box_widget.grid(row=row, column=0, padx=(0,settings.uism.SF__SWITCH_BOX_RIGHT_PAD), sticky="e") + compact_mode_icon_widget.grid(row=row, column=0, pady=settings.uism.SF__COMPACT_MODE_ICON_PADY) + selected_mark_widget.place(relx=-1, rely=0.5, relheight=0.75, anchor="center") + + label_widget.grid(row=row, column=0, pady=settings.uism.SF__LABELS_IPADY, padx=(settings.uism.SF__LABEL_LEFT_PAD,0), sticky="ew") + switch_box_widget.grid(row=row, column=0, padx=(0,settings.uism.SF__SWITCH_BOX_RIGHT_PAD), sticky="e") # Unbind the event "" originally set up by the widget to manually control it. switch_box_widget._canvas.unbind("") switch_box_widget._text_label.unbind("") - bindButtonReleaseFunction([compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], toggle_switch_box_command) + bindButtonReleaseFunction([compact_mode_icon_widget, frame_widget, compact_mode_frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], toggle_switch_box_command) - retag("vrct_"+frame_attr_name, compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget) + retag("vrct_"+frame_attr_name, compact_mode_icon_widget, frame_widget, compact_mode_frame_widget, label_widget, selected_mark_widget, switch_box_widget) def commonEventFunction(e, event_type): for ww in e.widget.master.bindtags(): @@ -265,12 +283,8 @@ def createSidebarFeatures(settings, main_window): def buttonReleasedFunction(e): commonEventFunction(e, "button_release") - bindEnterAndLeaveFunction([compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], enterFunction, leaveFunction) - - bindButtonPressAndReleaseFunction([compact_mode_icon_widget, frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], buttonPressFunction, buttonReleasedFunction) - - row+=1 - - + bindEnterAndLeaveFunction([compact_mode_icon_widget, frame_widget, compact_mode_frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], enterFunction, leaveFunction) + bindButtonPressAndReleaseFunction([compact_mode_icon_widget, frame_widget, compact_mode_frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], buttonPressFunction, buttonReleasedFunction) + row+=1 \ No newline at end of file diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index c4d8ab4b..00ed8044 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -120,8 +120,7 @@ def createSidebarLanguagesSettings(settings, main_window): # Sidebar Languages Settings, SLS main_window.sls__container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) - if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: - main_window.sls__container.grid(row=2, column=0, sticky="new") + main_window.sls__container.grid(row=2, column=0, sticky="new") main_window.sls__container.grid_columnconfigure(0, weight=1) diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index c6672ad2..b9bc0f99 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -8,9 +8,24 @@ from ._create_sidebar import createSidebarFeatures, createSidebarLanguagesSettin def createSidebar(settings, main_window): # Side Bar Container + main_window.grid_rowconfigure(0, weight=1) + main_window.sidebar_bg_container = CTkFrame(main_window, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) - main_window.sidebar_bg_container.grid(row=0, column=0, sticky="nsew") + main_window.sidebar_compact_mode_bg_container = CTkFrame(main_window, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + + + main_window.sidebar_bg_container.grid_columnconfigure(0, weight=0, minsize=settings.uism.SIDEBAR_WIDTH) + main_window.sidebar_compact_mode_bg_container.grid_columnconfigure(0, weight=0, minsize=settings.uism.COMPACT_MODE_SIDEBAR_WIDTH) createSidebarFeatures(settings, main_window) - createSidebarLanguagesSettings(settings, main_window) \ No newline at end of file + createSidebarLanguagesSettings(settings, main_window) + + + main_window.sidebar_bg_container.grid(row=0, column=0, sticky="nsew") + main_window.sidebar_compact_mode_bg_container.grid(row=0, column=0, sticky="nsew") + + if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE: + main_window.sidebar_bg_container.grid_remove() + else: + main_window.sidebar_compact_mode_bg_container.grid_remove() \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index de958f3d..b33d3728 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -81,8 +81,13 @@ class VRCT_GUI(CTk): def recreateMainWindowSidebar(self): self.minimize_sidebar_button_container.destroy() createMinimizeSidebarButton(self.settings.main, self) - self.sidebar_bg_container.destroy() - createSidebar(self.settings.main, self) + + if self.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE: + self.sidebar_bg_container.grid_remove() + self.sidebar_compact_mode_bg_container.grid() + else: + self.sidebar_compact_mode_bg_container.grid_remove() + self.sidebar_bg_container.grid() vrct_gui = VRCT_GUI() \ No newline at end of file From b014b1f3cc9a659b550ffe0ccff79ca61a39d751 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 4 Sep 2023 05:04:37 +0900 Subject: [PATCH 080/355] =?UTF-8?q?[Update]=20model=E3=81=A7config?= =?UTF-8?q?=E3=81=AB=E3=82=BB=E3=83=83=E3=83=88=E3=81=97=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=9F=E9=83=A8=E5=88=86=E3=82=92callback=E3=81=A8=E3=81=97?= =?UTF-8?q?=E3=81=A6main=E3=81=A7=E3=82=BB=E3=83=83=E3=83=88=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 17 +++++++++++++---- model.py | 12 ++++++------ 2 files changed, 19 insertions(+), 10 deletions(-) diff --git a/main.py b/main.py index b0c22381..1ed44b42 100644 --- a/main.py +++ b/main.py @@ -143,6 +143,15 @@ def callbackSelectedLanguagePresetTab(selected_tab_no): config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) +def callbackSetAuthKeys(keys): + config.AUTH_KEYS = keys + +def callbackChangeStatusOSC(value): + config.ENABLE_OSC = value + +def callbackChangeStatusSoftwareUpdated(value): + config.UPDATE_FLAG = value + # command func def callbackToggleTranslation(is_turned_on): config.ENABLE_TRANSLATION = is_turned_on @@ -230,7 +239,7 @@ def callbackSetDeeplAuthkey(value): print("callbackSetDeeplAuthkey", str(value)) # config.AUTH_KEYS["DeepL(auth)"] = str(value) # if len(value) > 0: - # if model.authenticationTranslator(choice_translator="DeepL(auth)", auth_key=value) is True: + # if model.authenticationTranslator(callbackSetAuthKeys, choice_translator="DeepL(auth)", auth_key=value) is True: # print_textbox(self.parent.textbox_message_log, "Auth key update completed", "INFO") # print_textbox(self.parent.textbox_message_system_log, "Auth key update completed", "INFO") # else: @@ -352,7 +361,7 @@ def callbackSetOscPort(value): view.createGUI() # init config -if model.authenticationTranslator() is False: +if model.authenticationTranslator(callbackSetAuthKeys) is False: # error update Auth key view.printToTextbox_AuthenticationError() @@ -360,10 +369,10 @@ if model.authenticationTranslator() is False: model.addKeywords() # check OSC started -model.checkOSCStarted() +model.checkOSCStarted(callbackChangeStatusOSC) # check Software Updated -model.checkSoftwareUpdated() +model.checkSoftwareUpdated(callbackChangeStatusSoftwareUpdated) # init logger if config.ENABLE_LOGGER is True: diff --git a/model.py b/model.py index f2c63a25..48735c3d 100644 --- a/model.py +++ b/model.py @@ -68,7 +68,7 @@ class Model: del self.translator self.keyword_processor = KeywordProcessor() - def authenticationTranslator(self, choice_translator=None, auth_key=None): + def authenticationTranslator(self, fnc, choice_translator=None, auth_key=None): if choice_translator == None: choice_translator = config.CHOICE_TRANSLATOR if auth_key == None: @@ -78,7 +78,7 @@ class Model: if result: auth_keys = config.AUTH_KEYS auth_keys[choice_translator] = auth_key - config.AUTH_KEYS = auth_keys + fnc(auth_key) return result def startLogger(self): @@ -165,10 +165,10 @@ class Model: sendMessage(message, config.OSC_IP_ADDRESS, config.OSC_PORT) @staticmethod - def checkOSCStarted(): + def checkOSCStarted(fnc): def checkOscReceive(address, osc_arguments): if config.ENABLE_OSC is False: - config.ENABLE_OSC = True + fnc(True) # start receive osc th_receive_osc_parameters = Thread(target=receiveOscParameters, args=(checkOscReceive,)) @@ -179,12 +179,12 @@ class Model: sendTestAction() @staticmethod - def checkSoftwareUpdated(): + def checkSoftwareUpdated(fnc): # check update response = requests_get(config.GITHUB_URL) tag_name = response.json()["tag_name"] if tag_name != config.VERSION: - config.UPDATE_FLAG = True + fnc(True) @staticmethod def getListInputHost(): From 45d5dffd3c8333a9c896cb3b4a3eafbbd1b6ba27 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 4 Sep 2023 05:05:43 +0900 Subject: [PATCH 081/355] [Chore] remove the code that is no longer in use. --- .../main_window/createMainWindowWidgets.py | 21 +------------------ .../_create_sidebar/createSidebarFeatures.py | 4 ++-- .../createSidebarLanguagesSettings.py | 4 ++-- .../main_window/widgets/create_sidebar.py | 6 +----- 4 files changed, 6 insertions(+), 29 deletions(-) diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 536aba6c..08687539 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -62,23 +62,4 @@ def createMainWindowWidgets(vrct_gui, settings): createTextbox(settings, vrct_gui) - createEntryMessageBox(settings, vrct_gui) - - - - - - # def delete_window(self): - # self.vrct_gui.quit() - # self.vrct_gui.destroy() - - # def openConfigWindow(self, e): - # self.config_window.deiconify() - # self.config_window.focus_set() - # self.config_window.focus() - # self.config_window.grab_set() - - # def openHelpAndInfoWindow(self, e): - # self.information_window.deiconify() - # self.information_window.focus_set() - # self.information_window.focus() \ No newline at end of file + createEntryMessageBox(settings, vrct_gui) \ No newline at end of file diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py index cb99303b..7547746e 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py @@ -1,6 +1,6 @@ -from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkSwitch, CTkImage, StringVar +from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkSwitch, CTkImage -from ....ui_utils import getImageFileFromUiUtils, openImageKeepAspectRatio, retag, getLatestHeight, bindEnterAndLeaveColor, bindButtonPressColor, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressAndReleaseFunction, bindButtonFunctionAndColor, switchActiveTabAndPassiveTab, switchTabsColor +from ....ui_utils import getImageFileFromUiUtils, openImageKeepAspectRatio, retag, getLatestHeight, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressAndReleaseFunction from utils import callFunctionIfCallable diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 00ed8044..a84e8384 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -1,6 +1,6 @@ -from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkSwitch, CTkImage, StringVar +from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkImage -from ....ui_utils import getImageFileFromUiUtils, openImageKeepAspectRatio, retag, getLatestHeight, bindEnterAndLeaveColor, bindButtonPressColor, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressAndReleaseFunction, bindButtonFunctionAndColor, switchActiveTabAndPassiveTab, switchTabsColor +from ....ui_utils import getImageFileFromUiUtils, bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, bindButtonFunctionAndColor, switchActiveTabAndPassiveTab, switchTabsColor from utils import callFunctionIfCallable diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index b9bc0f99..967d3268 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -1,8 +1,4 @@ -from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkSwitch, CTkImage, StringVar - -from ...ui_utils import getImageFileFromUiUtils, openImageKeepAspectRatio, retag, getLatestHeight, bindEnterAndLeaveColor, bindButtonPressColor, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressAndReleaseFunction, bindButtonFunctionAndColor, switchActiveTabAndPassiveTab, switchTabsColor - -from utils import callFunctionIfCallable +from customtkinter import CTkFrame from ._create_sidebar import createSidebarFeatures, createSidebarLanguagesSettings From 768e1d4de4136ad630ebd564a404709b85c86da8 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 4 Sep 2023 09:00:47 +0900 Subject: [PATCH 082/355] =?UTF-8?q?[bugfix]=20=E3=81=A9=E3=81=AE=E3=83=87?= =?UTF-8?q?=E3=82=A3=E3=83=AC=E3=82=AF=E3=83=88=E3=83=AA=E3=81=A7mian.py?= =?UTF-8?q?=E3=82=92=E5=AE=9F=E8=A1=8C=E3=81=97=E3=81=A6=E3=82=82main.py?= =?UTF-8?q?=E3=81=AE=E3=83=87=E3=82=A3=E3=83=AC=E3=82=AF=E3=83=88=E3=83=AA?= =?UTF-8?q?=E3=81=ABconfig.json=E3=81=8C=E7=94=9F=E6=88=90=E3=81=95?= =?UTF-8?q?=E3=81=9B=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/config.py b/config.py index 312ceb4a..42c8b151 100644 --- a/config.py +++ b/config.py @@ -1,3 +1,4 @@ +import sys from json import load, dump import inspect from os import path as os_path @@ -465,7 +466,7 @@ class Config: def init_config(self): self._VERSION = "1.3.2" - self._PATH_CONFIG = "./config.json" + self._PATH_CONFIG = os_path.join(os_path.dirname(sys.argv[0]), "config.json") self._ENABLE_TRANSLATION = False self._ENABLE_TRANSCRIPTION_SEND = False self._ENABLE_TRANSCRIPTION_RECEIVE = False From d9eb202b6f7290492f6f03a246c192dbd1353af0 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 4 Sep 2023 09:14:40 +0900 Subject: [PATCH 083/355] =?UTF-8?q?[Update]=20=E4=B8=8D=E8=A6=81=E3=81=AAm?= =?UTF-8?q?odule=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ctk_scrollable_dropdown.py | 350 ---------- window_config.py | 1344 ------------------------------------ window_information.py | 161 ----- 3 files changed, 1855 deletions(-) delete mode 100644 ctk_scrollable_dropdown.py delete mode 100644 window_config.py delete mode 100644 window_information.py diff --git a/ctk_scrollable_dropdown.py b/ctk_scrollable_dropdown.py deleted file mode 100644 index f0f48c3e..00000000 --- a/ctk_scrollable_dropdown.py +++ /dev/null @@ -1,350 +0,0 @@ -""" -CustomTkinter Scrollable Dropdown Menu -Author: Akash Bora -License: MIT -This is a custom dropdown menu for customtkinter. -Homepage: https://github.com/Akascape/CTkScrollableDropdown - -Advanced Scrollable Dropdown class for customtkinter widgets -Author: Akash Bora -""" -import customtkinter -import sys -import time - -class CTkScrollableDropdown(customtkinter.CTkToplevel): - - def __init__(self, attach, x=None, y=None, button_color=None, height: int = 200, width: int = None, - fg_color=None, max_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, double_click=False, - resize=True, frame_border_color=None, text_color=None, autocomplete=False, - max_height: int = None, button_pady: int = 0, min_show_button_num: int = 1, - button_corner_radius: int = None, frame_corner_radius=0, - **button_kwargs): - - super().__init__(takefocus=1) - self.transient(self.master) - self.alpha = alpha - self.attach = attach - self.corner = frame_corner_radius - # とりあえずframe_corner_radiusはframe_corner_radiusの名前のまま使いたい - self.frame_corner_radius = frame_corner_radius - self.padding = 0 - self.focus_something = False - self.disable = True - self.font_size = 12 if button_kwargs.get("font", None) is None else button_kwargs["font"]._size - - 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('', lambda e: self._withdraw() if not self.disable else None, add="+") - self.attach.winfo_toplevel().bind('', lambda e: self._withdraw() if not self.disable else None, add="+") - self.attach.winfo_toplevel().bind("", lambda e: self._withdraw() if not self.disable else None, add="+") - self.attach.winfo_toplevel().bind("", lambda e: self._withdraw() if not self.disable else None, add="+") - self.attach.winfo_toplevel().bind("", 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 - self.max_height = max_height - self.button_pady = button_pady - self.min_show_button_num = min_show_button_num - self.button_corner_radius = button_corner_radius - - if justify.lower()=="left": - self.justify = "w" - elif justify.lower()=="right": - self.justify = "e" - else: - self.justify = "c" - - self.button_height = max_button_height + self.font_size - 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._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('', lambda e: self._iconify(), add="+") - else: - self.attach.bind('', lambda e: self._iconify(), add="+") - - if self.attach.winfo_name().startswith("!ctkcombobox"): - self.attach._canvas.tag_bind("right_parts", "", lambda e: self._iconify()) - self.attach._canvas.tag_bind("dropdown_arrow", "", 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("", lambda e: self._iconify()) - self.attach._text_label.bind("", lambda e: self._iconify()) - if self.command is None: - self.command = self.attach.set - - 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 _withdraw(self): - if self.hide is False: self.withdraw() - 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("", 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("", 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, - corner_radius= self.button_corner_radius, - command=lambda k=row: self._attach_key_press(k), **button_kwargs) - pady = 0 if self.button_num-1 == self.i else self.button_pady - self.widgets[self.i].pack(fill="x", pady=(0, pady), 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: - button_height_include_pady = self.button_height + self.button_pady - - if self.min_show_button_num < self.button_num: - min_buttons_height = button_height_include_pady * self.min_show_button_num - else: - min_buttons_height = button_height_include_pady * self.button_num - # delete last one's pady px - min_buttons_height -= self.button_pady - - # minor adjustment + 5px - min_buttons_height += 5 - # adjust by frame_corner_radius - min_buttons_height += (self.frame_corner_radius * 2) - - if self.max_height: - if min_buttons_height>self.max_height: - min_buttons_height = self.max_height - - self.height_new = min_buttons_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) - - def _iconify(self): - if self.disable: return - if self.hide: - self._deiconify() - self.place_dropdown() - if self.focus_something: - self.dummy_entry.pack() - self.dummy_entry.focus_set() - self.after(100, self.dummy_entry.pack_forget) - self.hide = False - self.focus_set() - self.focus() - else: - self.withdraw() - self.hide = True - - def _attach_key_press(self, k): - 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: - self._deiconify() - i=1 - for key in self.widgets.keys(): - s = self.widgets[key].cget("text") - if not s.startswith(string): - 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 - 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) diff --git a/window_config.py b/window_config.py deleted file mode 100644 index b45a61c1..00000000 --- a/window_config.py +++ /dev/null @@ -1,1344 +0,0 @@ -from os import path as os_path -from tkinter import DoubleVar, IntVar -from tkinter import font as tk_font -import customtkinter -from customtkinter import CTkToplevel, CTkTabview, CTkFont, CTkLabel, CTkSlider, CTkOptionMenu, StringVar, CTkEntry, CTkCheckBox, CTkProgressBar - -from threading import Thread -from config import config -from model import model -from utils import print_textbox, get_localized_text, get_key_by_value, widget_config_window_label_setter -from languages import selectable_languages -from models.translation.translation_languages import translation_lang -from models.transcription.transcription_languages import transcription_lang -from ctk_scrollable_dropdown import CTkScrollableDropdown - -SCROLLABLE_DROPDOWN = False - -class ToplevelWindowConfig(CTkToplevel): - - def __init__(self, parent, *args, **kwargs): - super().__init__(parent, *args, **kwargs) - - self.withdraw() - self.parent = parent - # self.geometry(f"{350}x{270}") - # self.resizable(False, False) - self.grid_columnconfigure(0, weight=1) - self.grid_rowconfigure(0, weight=1) - - self.after(200, lambda: self.iconbitmap(os_path.join(os_path.dirname(__file__), "img", "app.ico"))) - self.title("Config") - - # load ui language data - language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") - # add tabview config - self.add_tabview_config(language_yaml_data, selectable_languages) - # set all config window labels - widget_config_window_label_setter(self, language_yaml_data) - - self.protocol("WM_DELETE_WINDOW", self.delete_window) - - def slider_transparency_callback(self, value): - self.parent.wm_attributes("-alpha", value/100) - config.TRANSPARENCY = value - - def optionmenu_appearance_theme_callback(self, choice): - self.optionmenu_appearance_theme.set(choice) - - customtkinter.set_appearance_mode(choice) - config.APPEARANCE_THEME = choice - - def optionmenu_ui_scaling_callback(self, choice): - self.optionmenu_ui_scaling.set(choice) - - new_scaling_float = int(choice.replace("%", "")) / 100 - customtkinter.set_widget_scaling(new_scaling_float) - config.UI_SCALING = choice - - def optionmenu_font_family_callback(self, choice): - self.optionmenu_font_family.set(choice) - - # tab menu - self.tabview_config._segmented_button.configure(font=CTkFont(family=choice)) - - # tab UI - self.label_transparency.configure(font=CTkFont(family=choice)) - self.label_appearance_theme.configure(font=CTkFont(family=choice)) - self.optionmenu_appearance_theme.configure(font=CTkFont(family=choice)) - self.optionmenu_appearance_theme._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_ui_scaling.configure(font=CTkFont(family=choice)) - self.optionmenu_ui_scaling.configure(font=CTkFont(family=choice)) - self.optionmenu_ui_scaling._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_font_family.configure(font=CTkFont(family=choice)) - self.optionmenu_font_family.configure(font=CTkFont(family=choice)) - self.optionmenu_font_family._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_ui_language.configure(font=CTkFont(family=choice)) - self.optionmenu_ui_language.configure(font=CTkFont(family=choice)) - self.optionmenu_ui_language._dropdown_menu.configure(font=CTkFont(family=choice)) - - # tab Translation - self.label_translation_translator.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_translator.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_translator._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_translation_input_language.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_input_source_language.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_input_source_language._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_translation_input_arrow.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_input_target_language.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_input_target_language._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_translation_output_language.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_output_source_language.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_output_source_language._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_translation_output_arrow.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_output_target_language.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_output_target_language._dropdown_menu.configure(font=CTkFont(family=choice)) - - # tab Transcription - self.label_input_mic_host.configure(font=CTkFont(family=choice)) - self.optionmenu_input_mic_host.configure(font=CTkFont(family=choice)) - self.optionmenu_input_mic_host._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_input_mic_device.configure(font=CTkFont(family=choice)) - self.optionmenu_input_mic_device.configure(font=CTkFont(family=choice)) - self.optionmenu_input_mic_device._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_input_mic_voice_language.configure(font=CTkFont(family=choice)) - self.optionmenu_input_mic_voice_language.configure(font=CTkFont(family=choice)) - self.optionmenu_input_mic_voice_language._dropdown_menu.configure(font=CTkFont(family=choice)) - self.checkbox_input_mic_threshold_check.configure(font=CTkFont(family=choice)) - self.label_input_mic_energy_threshold.configure(font=CTkFont(family=choice)) - self.label_input_mic_dynamic_energy_threshold.configure(font=CTkFont(family=choice)) - self.label_input_mic_record_timeout.configure(font=CTkFont(family=choice)) - self.entry_input_mic_record_timeout.configure(font=CTkFont(family=choice)) - self.label_input_mic_phrase_timeout.configure(font=CTkFont(family=choice)) - self.entry_input_mic_phrase_timeout.configure(font=CTkFont(family=choice)) - self.label_input_mic_max_phrases.configure(font=CTkFont(family=choice)) - self.entry_input_mic_max_phrases.configure(font=CTkFont(family=choice)) - self.label_input_mic_word_filter.configure(font=CTkFont(family=choice)) - self.entry_input_mic_word_filter.configure(font=CTkFont(family=choice)) - self.label_input_speaker_device.configure(font=CTkFont(family=choice)) - self.optionmenu_input_speaker_device.configure(font=CTkFont(family=choice)) - self.optionmenu_input_speaker_device._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_input_speaker_voice_language.configure(font=CTkFont(family=choice)) - self.optionmenu_input_speaker_voice_language.configure(font=CTkFont(family=choice)) - self.optionmenu_input_speaker_voice_language._dropdown_menu.configure(font=CTkFont(family=choice)) - self.checkbox_input_speaker_threshold_check.configure(font=CTkFont(family=choice)) - self.label_input_speaker_energy_threshold.configure(font=CTkFont(family=choice)) - self.label_input_speaker_dynamic_energy_threshold.configure(font=CTkFont(family=choice)) - self.label_input_speaker_record_timeout.configure(font=CTkFont(family=choice)) - self.entry_input_speaker_record_timeout.configure(font=CTkFont(family=choice)) - self.label_input_speaker_phrase_timeout.configure(font=CTkFont(family=choice)) - self.entry_input_speaker_phrase_timeout.configure(font=CTkFont(family=choice)) - self.label_input_speaker_max_phrases.configure(font=CTkFont(family=choice)) - self.entry_input_speaker_max_phrases.configure(font=CTkFont(family=choice)) - - # tab Parameter - self.label_ip_address.configure(font=CTkFont(family=choice)) - self.entry_ip_address.configure(font=CTkFont(family=choice)) - self.label_port.configure(font=CTkFont(family=choice)) - self.entry_port.configure(font=CTkFont(family=choice)) - self.label_authkey.configure(font=CTkFont(family=choice)) - self.entry_authkey.configure(font=CTkFont(family=choice)) - self.label_message_format.configure(font=CTkFont(family=choice)) - self.entry_message_format.configure(font=CTkFont(family=choice)) - - # tab Others - self.label_checkbox_auto_clear_chatbox.configure(font=CTkFont(family=choice)) - - # main window - self.parent.checkbox_translation.configure(font=CTkFont(family=choice)) - self.parent.checkbox_transcription_send.configure(font=CTkFont(family=choice)) - self.parent.checkbox_transcription_receive.configure(font=CTkFont(family=choice)) - self.parent.checkbox_foreground.configure(font=CTkFont(family=choice)) - self.parent.textbox_message_log.configure(font=CTkFont(family=choice)) - self.parent.textbox_message_send_log.configure(font=CTkFont(family=choice)) - self.parent.textbox_message_receive_log.configure(font=CTkFont(family=choice)) - self.parent.textbox_message_system_log.configure(font=CTkFont(family=choice)) - self.parent.entry_message_box.configure(font=CTkFont(family=choice)) - self.parent.tabview_logs._segmented_button.configure(font=CTkFont(family=choice)) - - # window information - try: - self.parent.information_window.textbox_information.configure(font=CTkFont(family=choice)) - except: - pass - - config.FONT_FAMILY = choice - - def optionmenu_ui_language_callback(self, choice): - self.optionmenu_ui_language.set(choice) - - self.withdraw() - pre_language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") - config.UI_LANGUAGE = get_key_by_value(selectable_languages, choice) - language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") - - # delete - self.parent.delete_tabview_logs(pre_language_yaml_data) - self.delete_tabview_config(pre_language_yaml_data) - # add tabview textbox - self.parent.add_tabview_logs(language_yaml_data) - self.add_tabview_config(language_yaml_data, selectable_languages) - - # 翻訳予定 - # window information - # try: - # self.parent.information_window.textbox_information.configure(font=customtkinter.CTkFont(family=choice)) - # except: - # pass - self.deiconify() - - def optionmenu_translation_translator_callback(self, choice): - self.optionmenu_translation_translator.set(choice) - - if model.authenticationTranslator(choice_translator=choice) is False: - print_textbox(self.parent.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") - print_textbox(self.parent.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") - else: - self.optionmenu_translation_input_source_language.configure( - values=list(translation_lang[choice]["source"].keys()), - variable=StringVar(value=list(translation_lang[choice]["source"].keys())[0])) - self.optionmenu_translation_input_target_language.configure( - values=list(translation_lang[choice]["target"].keys()), - variable=StringVar(value=list(translation_lang[choice]["target"].keys())[1])) - self.optionmenu_translation_output_source_language.configure( - values=list(translation_lang[choice]["source"].keys()), - variable=StringVar(value=list(translation_lang[choice]["source"].keys())[1])) - self.optionmenu_translation_output_target_language.configure( - values=list(translation_lang[choice]["target"].keys()), - variable=StringVar(value=list(translation_lang[choice]["target"].keys())[0])) - - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_translation_input_source_language.configure( - values=list(translation_lang[choice]["source"].keys())) - self.scrollableDropdown_translation_input_target_language.configure( - values=list(translation_lang[choice]["target"].keys())) - self.scrollableDropdown_translation_output_source_language.configure( - values=list(translation_lang[choice]["source"].keys())) - self.scrollableDropdown_translation_output_target_language.configure( - values=list(translation_lang[choice]["target"].keys())) - - config.CHOICE_TRANSLATOR = choice - config.INPUT_SOURCE_LANG = list(translation_lang[choice]["source"].keys())[0] - config.INPUT_TARGET_LANG = list(translation_lang[choice]["target"].keys())[1] - config.OUTPUT_SOURCE_LANG = list(translation_lang[choice]["source"].keys())[1] - config.OUTPUT_TARGET_LANG = list(translation_lang[choice]["target"].keys())[0] - - def optionmenu_translation_input_source_language_callback(self, choice): - self.optionmenu_translation_input_source_language.set(choice) - config.INPUT_SOURCE_LANG = choice - - def optionmenu_translation_input_target_language_callback(self, choice): - self.optionmenu_translation_input_target_language.set(choice) - config.INPUT_TARGET_LANG = choice - - def optionmenu_translation_output_source_language_callback(self, choice): - self.optionmenu_translation_output_source_language.set(choice) - config.OUTPUT_SOURCE_LANG = choice - - def optionmenu_translation_output_target_language_callback(self, choice): - self.optionmenu_translation_output_target_language.set(choice) - config.OUTPUT_TARGET_LANG = choice - - def optionmenu_input_mic_host_callback(self, choice): - self.optionmenu_input_mic_host.set(choice) - config.CHOICE_MIC_HOST = choice - config.CHOICE_MIC_DEVICE = model.getInputDefaultDevice() - - self.optionmenu_input_mic_device.configure( - values=model.getListInputDevice(), - variable=StringVar(value=model.getInputDefaultDevice())) - - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_input_mic_device.configure(values=model.getListInputDevice()) - - def optionmenu_input_mic_device_callback(self, choice): - self.optionmenu_input_mic_device.set(choice) - config.CHOICE_MIC_DEVICE = choice - self.checkbox_input_mic_threshold_check.deselect() - self.checkbox_input_mic_threshold_check_callback() - - def optionmenu_input_mic_voice_language_callback(self, choice): - self.optionmenu_input_mic_voice_language.set(choice) - config.INPUT_MIC_VOICE_LANGUAGE = choice - - def mic_threshold_check_start(self): - def plotProgressBar(energy): - try: - self.progressBar_input_mic_energy_threshold.set(energy/config.MAX_MIC_ENERGY_THRESHOLD) - except: - pass - model.startCheckMicEnergy(plotProgressBar) - self.checkbox_input_mic_threshold_check.configure(state="normal") - self.checkbox_input_speaker_threshold_check.configure(state="normal") - - def mic_threshold_check_stop(self): - model.stopCheckMicEnergy() - self.progressBar_input_mic_energy_threshold.set(0) - self.checkbox_input_mic_threshold_check.configure(state="normal") - self.checkbox_input_speaker_threshold_check.configure(state="normal") - - def checkbox_input_mic_threshold_check_callback(self): - self.checkbox_input_mic_threshold_check.configure(state="disabled") - self.checkbox_input_speaker_threshold_check.configure(state="disabled") - self.update() - if self.checkbox_input_mic_threshold_check.get(): - th_mic_threshold_check_start = Thread(target=self.mic_threshold_check_start) - th_mic_threshold_check_start.daemon = True - th_mic_threshold_check_start.start() - else: - th_mic_threshold_check_stop = Thread(target=self.mic_threshold_check_stop) - th_mic_threshold_check_stop.daemon = True - th_mic_threshold_check_stop.start() - - def slider_input_mic_energy_threshold_callback(self, value): - config.INPUT_MIC_ENERGY_THRESHOLD = int(value) - - def checkbox_input_mic_dynamic_energy_threshold_callback(self): - config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = self.checkbox_input_mic_dynamic_energy_threshold.get() - - def entry_input_mic_record_timeout_callback(self, event): - config.INPUT_MIC_RECORD_TIMEOUT = int(self.entry_input_mic_record_timeout.get()) - - def entry_input_mic_phrase_timeout_callback(self, event): - config.INPUT_MIC_PHRASE_TIMEOUT = int(self.entry_input_mic_phrase_timeout.get()) - - def entry_input_mic_max_phrases_callback(self, event): - config.INPUT_MIC_MAX_PHRASES = int(self.entry_input_mic_max_phrases.get()) - - def entry_input_mic_word_filters_callback(self, event): - word_filter = self.entry_input_mic_word_filter.get() - word_filter = [w.strip() for w in word_filter.split(",") if len(w.strip()) > 0] - word_filter = ",".join(word_filter) - if len(word_filter) > 0: - config.INPUT_MIC_WORD_FILTER = word_filter.split(",") - else: - config.INPUT_MIC_WORD_FILTER = [] - model.resetKeywordProcessor() - model.addKeywords() - - def optionmenu_input_speaker_device_callback(self, choice): - if model.checkSpeakerStatus(choice): - self.optionmenu_input_speaker_device.set(choice) - config.CHOICE_SPEAKER_DEVICE = choice - else: - print_textbox(self.parent.textbox_message_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") - print_textbox(self.parent.textbox_message_system_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") - self.optionmenu_input_speaker_device.configure(variable=StringVar(value=config.CHOICE_SPEAKER_DEVICE)) - - def optionmenu_input_speaker_voice_language_callback(self, choice): - self.optionmenu_input_speaker_voice_language.set(choice) - config.INPUT_SPEAKER_VOICE_LANGUAGE = choice - - def speaker_threshold_check_start(self): - def plotProgressBar(energy): - try: - self.progressBar_input_speaker_energy_threshold.set(energy/config.MAX_MIC_ENERGY_THRESHOLD) - except: - pass - model.startCheckSpeakerEnergy(plotProgressBar) - self.checkbox_input_mic_threshold_check.configure(state="normal") - self.checkbox_input_speaker_threshold_check.configure(state="normal") - - def speaker_threshold_check_stop(self): - model.stopCheckSpeakerEnergy() - self.progressBar_input_speaker_energy_threshold.set(0) - self.checkbox_input_mic_threshold_check.configure(state="normal") - self.checkbox_input_speaker_threshold_check.configure(state="normal") - - def checkbox_input_speaker_threshold_check_callback(self): - self.checkbox_input_mic_threshold_check.configure(state="disabled") - self.checkbox_input_speaker_threshold_check.configure(state="disabled") - self.update() - if self.checkbox_input_speaker_threshold_check.get(): - if model.checkSpeakerStatus(): - th_speaker_threshold_check_start = Thread(target=self.speaker_threshold_check_start) - th_speaker_threshold_check_start.daemon = True - th_speaker_threshold_check_start.start() - else: - print_textbox(self.parent.textbox_message_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") - print_textbox(self.parent.textbox_message_system_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") - self.checkbox_input_speaker_threshold_check.deselect() - else: - th_speaker_threshold_check_stop = Thread(target=self.speaker_threshold_check_stop) - th_speaker_threshold_check_stop.daemon = True - th_speaker_threshold_check_stop.start() - - def slider_input_speaker_energy_threshold_callback(self, value): - config.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) - - def checkbox_input_speaker_dynamic_energy_threshold_callback(self): - config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = self.checkbox_input_speaker_dynamic_energy_threshold.get() - - def entry_input_speaker_record_timeout_callback(self, event): - config.INPUT_SPEAKER_RECORD_TIMEOUT = int(self.entry_input_speaker_record_timeout.get()) - - def entry_input_speaker_phrase_timeout_callback(self, event): - config.INPUT_SPEAKER_PHRASE_TIMEOUT = int(self.entry_input_speaker_phrase_timeout.get()) - - def entry_input_speaker_max_phrases_callback(self, event): - config.INPUT_SPEAKER_MAX_PHRASES = int(self.entry_input_speaker_max_phrases.get()) - - def entry_ip_address_callback(self, event): - config.OSC_IP_ADDRESS = self.entry_ip_address.get() - - def entry_port_callback(self, event): - config.OSC_PORT = self.entry_port.get() - - def entry_authkey_callback(self, event): - value = self.entry_authkey.get() - if len(value) > 0: - if model.authenticationTranslator(choice_translator="DeepL(auth)", auth_key=value) is True: - print_textbox(self.parent.textbox_message_log, "Auth key update completed", "INFO") - print_textbox(self.parent.textbox_message_system_log, "Auth key update completed", "INFO") - else: - pass - - def checkbox_auto_clear_chatbox_callback(self): - config.ENABLE_AUTO_CLEAR_CHATBOX = self.checkbox_auto_clear_chatbox.get() - - def checkbox_notice_xsoverlay_callback(self): - config.ENABLE_NOTICE_XSOVERLAY = self.checkbox_notice_xsoverlay.get() - - def delete_window(self): - self.checkbox_input_mic_threshold_check.deselect() - self.checkbox_input_speaker_threshold_check.deselect() - self.checkbox_input_mic_threshold_check_callback() - self.checkbox_input_speaker_threshold_check_callback() - self.parent.transcription_start() - self.parent.foreground_start() - self.parent.checkbox_translation.configure(state="normal") - self.parent.checkbox_transcription_send.configure(state="normal") - self.parent.checkbox_transcription_receive.configure(state="normal") - self.parent.checkbox_foreground.configure(state="normal") - self.parent.tabview_logs.configure(state="normal") - self.parent.textbox_message_log.configure(state="normal") - self.parent.textbox_message_send_log.configure(state="normal") - self.parent.textbox_message_receive_log.configure(state="normal") - self.parent.textbox_message_system_log.configure(state="normal") - self.parent.entry_message_box.configure(state="normal") - self.parent.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) - self.parent.button_information.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) - self.withdraw() - self.grab_release() - - def entry_message_format_callback(self, event): - value = self.entry_message_format.get() - if len(value) > 0: - config.MESSAGE_FORMAT = value - - def delete_tabview_config(self, pre_language_yaml_data): - self.tabview_config.delete(pre_language_yaml_data["config_tab_title_ui"]) - self.tabview_config.delete(pre_language_yaml_data["config_tab_title_translation"]) - self.tabview_config.delete(pre_language_yaml_data["config_tab_title_transcription"]) - self.tabview_config.delete(pre_language_yaml_data["config_tab_title_parameter"]) - self.tabview_config.delete(pre_language_yaml_data["config_tab_title_others"]) - - def add_tabview_config(self, language_yaml_data, selectable_languages): - config_tab_title_ui = language_yaml_data["config_tab_title_ui"] - config_tab_title_translation = language_yaml_data["config_tab_title_translation"] - config_tab_title_transcription = language_yaml_data["config_tab_title_transcription"] - config_tab_title_parameter = language_yaml_data["config_tab_title_parameter"] - config_tab_title_others = language_yaml_data["config_tab_title_others"] - - init_lang_text = "Loading..." - - # tabwiew config - self.tabview_config = CTkTabview(self) - self.tabview_config.grid(row=0, column=0, padx=5, pady=5, sticky="nsew") - self.tabview_config.add(config_tab_title_ui) - self.tabview_config.add(config_tab_title_translation) - self.tabview_config.add(config_tab_title_transcription) - self.tabview_config.add(config_tab_title_parameter) - self.tabview_config.add(config_tab_title_others) - self.tabview_config.tab(config_tab_title_ui).grid_columnconfigure(1, weight=1) - self.tabview_config.tab(config_tab_title_translation).grid_columnconfigure([1,2,3], weight=1) - self.tabview_config.tab(config_tab_title_transcription).grid_columnconfigure(1, weight=1) - self.tabview_config.tab(config_tab_title_parameter).grid_columnconfigure(1, weight=1) - self.tabview_config.tab(config_tab_title_others).grid_columnconfigure(1, weight=1) - self.tabview_config._segmented_button.configure(font=CTkFont(family=config.FONT_FAMILY)) - self.tabview_config._segmented_button.grid(sticky="W") - - # tab UI - ## slider transparency - row = 0 - padx = 5 - pady = 1 - self.label_transparency = CTkLabel( - self.tabview_config.tab(config_tab_title_ui), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_transparency.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.slider_transparency = CTkSlider( - self.tabview_config.tab(config_tab_title_ui), - from_=50, - to=100, - command=self.slider_transparency_callback, - variable=DoubleVar(value=config.TRANSPARENCY), - ) - self.slider_transparency.grid(row=row, column=1, columnspan=1, padx=padx, pady=10, sticky="nsew") - - ## optionmenu theme - row += 1 - self.label_appearance_theme = CTkLabel( - self.tabview_config.tab(config_tab_title_ui), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_appearance_theme.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_appearance_theme = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_ui), - values=["Light", "Dark", "System"], - command=self.optionmenu_appearance_theme_callback, - variable=StringVar(value=config.APPEARANCE_THEME), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_appearance_theme.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown appearance theme - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_appearance_theme = CTkScrollableDropdown( - self.optionmenu_appearance_theme, - values=["Light", "Dark", "System"], - justify="left", - button_color="transparent", - command=self.optionmenu_appearance_theme_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_appearance_theme.bind( - "", - lambda e: self.scrollableDropdown_appearance_theme._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_appearance_theme.frame._parent_frame)) else None, - ) - - ## optionmenu UI scaling - row += 1 - self.label_ui_scaling = CTkLabel( - self.tabview_config.tab(config_tab_title_ui), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_ui_scaling.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_ui_scaling = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_ui), - values=["80%", "90%", "100%", "110%", "120%"], - command=self.optionmenu_ui_scaling_callback, - variable=StringVar(value=config.UI_SCALING), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_ui_scaling.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown ui scaling - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_ui_scaling = CTkScrollableDropdown( - self.optionmenu_ui_scaling, - values=["80%", "90%", "100%", "110%", "120%"], - justify="left", - button_color="transparent", - command=self.optionmenu_ui_scaling_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_ui_scaling.bind( - "", - lambda e: self.scrollableDropdown_ui_scaling._iconify() if not str(e.widget).startswith(str(self.scrollableDropdown_ui_scaling.frame._parent_frame)) else None, - ) - - ## optionmenu font family - row += 1 - self.label_font_family = CTkLabel( - self.tabview_config.tab(config_tab_title_ui), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_font_family.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - font_families = list(tk_font.families()) - self.optionmenu_font_family = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_ui), - values=font_families, - command=self.optionmenu_font_family_callback, - variable=StringVar(value=config.FONT_FAMILY), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_font_family.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown font family - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_font_family = CTkScrollableDropdown( - self.optionmenu_font_family, - values=font_families, - justify="left", - button_color="transparent", - command=self.optionmenu_font_family_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_font_family.bind( - "", - lambda e: self.scrollableDropdown_font_family._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_font_family.frame._parent_frame)) else None, - ) - - ## optionmenu ui language - row += 1 - self.label_ui_language = CTkLabel( - self.tabview_config.tab(config_tab_title_ui), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_ui_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - selectable_languages_values = list(selectable_languages.values()) - self.optionmenu_ui_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_ui), - values=selectable_languages_values, - command=self.optionmenu_ui_language_callback, - variable=StringVar(value=selectable_languages[config.UI_LANGUAGE]), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_ui_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown ui language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_ui_language = CTkScrollableDropdown( - self.optionmenu_ui_language, - values=selectable_languages_values, - justify="left", - button_color="transparent", - command=self.optionmenu_ui_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_ui_language.bind( - "", - lambda e: self.scrollableDropdown_ui_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_ui_language.frame._parent_frame)) else None, - ) - - # tab Translation - ## optionmenu translation translator - row = 0 - padx = 5 - pady = 1 - self.label_translation_translator = CTkLabel( - self.tabview_config.tab(config_tab_title_translation), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY), - ) - self.label_translation_translator.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_translation_translator = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_translation), - values=model.getListTranslatorName(), - command=self.optionmenu_translation_translator_callback, - variable=StringVar(value=config.CHOICE_TRANSLATOR), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_translation_translator.grid(row=row, column=1, columnspan=3, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown translation translator - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_translation_translator = CTkScrollableDropdown( - self.optionmenu_translation_translator, - values=model.getListTranslatorName(), - justify="left", - button_color="transparent", - command=self.optionmenu_translation_translator_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_translation_translator.bind( - "", - lambda e: self.scrollableDropdown_translation_translator._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_translator.frame._parent_frame)) else None, - ) - - ## optionmenu translation input language - row +=1 - self.label_translation_input_language = CTkLabel( - self.tabview_config.tab(config_tab_title_translation), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_translation_input_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - - ## select translation input source language - self.optionmenu_translation_input_source_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_translation), - command=self.optionmenu_translation_input_source_language_callback, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), - variable=StringVar(value=config.INPUT_SOURCE_LANG), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_translation_input_source_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown translation input source language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_translation_input_source_language = CTkScrollableDropdown( - self.optionmenu_translation_input_source_language, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), - justify="left", - button_color="transparent", - command=self.optionmenu_translation_input_source_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_translation_input_source_language.bind( - "", - lambda e: self.scrollableDropdown_translation_input_source_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_input_source_language.frame._parent_frame)) else None, - ) - - ## label translation input arrow - self.label_translation_input_arrow = CTkLabel( - self.tabview_config.tab(config_tab_title_translation), - text="-->", - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_translation_input_arrow.grid(row=row, column=2, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## select translation input target language - self.optionmenu_translation_input_target_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_translation), - command=self.optionmenu_translation_input_target_language_callback, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), - variable=StringVar(value=config.INPUT_TARGET_LANG), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_translation_input_target_language.grid(row=row, column=3, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown translation input target language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_translation_input_target_language = CTkScrollableDropdown( - self.optionmenu_translation_input_target_language, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), - justify="left", - button_color="transparent", - command=self.optionmenu_translation_input_target_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_translation_input_target_language.bind( - "", - lambda e: self.scrollableDropdown_translation_input_target_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_input_target_language.frame._parent_frame)) else None, - ) - - ## optionmenu translation output language - row +=1 - self.label_translation_output_language = CTkLabel( - self.tabview_config.tab(config_tab_title_translation), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_translation_output_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - - ## select translation output source language - self.optionmenu_translation_output_source_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_translation), - command=self.optionmenu_translation_output_source_language_callback, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), - variable=StringVar(value=config.OUTPUT_SOURCE_LANG), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_translation_output_source_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown translation output source language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_translation_output_source_language = CTkScrollableDropdown( - self.optionmenu_translation_output_source_language, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), - justify="left", - button_color="transparent", - command=self.optionmenu_translation_output_source_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_translation_output_source_language.bind( - "", - lambda e: self.scrollableDropdown_translation_output_source_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_output_source_language.frame._parent_frame)) else None, - ) - - ## label translation output arrow - self.label_translation_output_arrow = CTkLabel( - self.tabview_config.tab(config_tab_title_translation), - text="-->", - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_translation_output_arrow.grid(row=row, column=2, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## select translation output target language - self.optionmenu_translation_output_target_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_translation), - command=self.optionmenu_translation_output_target_language_callback, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), - variable=StringVar(value=config.OUTPUT_TARGET_LANG), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_translation_output_target_language.grid(row=row, column=3, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown translation output target language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_translation_output_target_language = CTkScrollableDropdown( - self.optionmenu_translation_output_target_language, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), - justify="left", - button_color="transparent", - command=self.optionmenu_translation_output_target_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_translation_output_target_language.bind( - "", - lambda e: self.scrollableDropdown_translation_output_target_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_output_target_language.frame._parent_frame)) else None, - ) - - # tab Transcription - ## optionmenu input mic device's host - row = 0 - padx = 5 - pady = 1 - self.label_input_mic_host = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_host.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_input_mic_host = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_transcription), - values=model.getListInputHost(), - command=self.optionmenu_input_mic_host_callback, - variable=StringVar(value=config.CHOICE_MIC_HOST), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_input_mic_host.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown input mic device's host - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_input_mic_host = CTkScrollableDropdown( - self.optionmenu_input_mic_host, - values=model.getListInputHost(), - justify="left", - button_color="transparent", - command=self.optionmenu_input_mic_host_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_input_mic_host.bind( - "", - lambda e: self.scrollableDropdown_input_mic_host._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_mic_host.frame._parent_frame)) else None, - ) - - ## optionmenu input mic device - row += 1 - self.label_input_mic_device = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_device.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_input_mic_device = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_transcription), - values=model.getListInputDevice(), - command=self.optionmenu_input_mic_device_callback, - variable=StringVar(value=config.CHOICE_MIC_DEVICE), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_input_mic_device.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown input mic device - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_input_mic_device = CTkScrollableDropdown( - self.optionmenu_input_mic_device, - values=model.getListInputDevice(), - justify="left", - button_color="transparent", - command=self.optionmenu_input_mic_device_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_input_mic_device.bind( - "", - lambda e: self.scrollableDropdown_input_mic_device._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_mic_device.frame._parent_frame)) else None, - ) - - ## optionmenu input mic voice language - row +=1 - self.label_input_mic_voice_language = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_voice_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_input_mic_voice_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_transcription), - values=list(transcription_lang.keys()), - command=self.optionmenu_input_mic_voice_language_callback, - variable=StringVar(value=config.INPUT_MIC_VOICE_LANGUAGE), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_input_mic_voice_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown input mic voice language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_input_voice_language = CTkScrollableDropdown( - self.optionmenu_input_mic_voice_language, - values=list(transcription_lang.keys()), - justify="left", - button_color="transparent", - command=self.optionmenu_input_mic_voice_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_input_voice_language.bind( - "", - lambda e: self.scrollableDropdown_input_voice_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_voice_language.frame._parent_frame)) else None, - ) - - ## slider input mic energy threshold - row +=1 - self.label_input_mic_energy_threshold = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - - self.slider_input_mic_energy_threshold = CTkSlider( - self.tabview_config.tab(config_tab_title_transcription), - from_=0, - to=config.MAX_MIC_ENERGY_THRESHOLD, - border_width=7, - button_length=0, - button_corner_radius=3, - number_of_steps=config.MAX_MIC_ENERGY_THRESHOLD, - command=self.slider_input_mic_energy_threshold_callback, - variable=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), - ) - self.slider_input_mic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=0, pady=5, sticky="nsew") - - ## progressBar input mic energy threshold - row +=1 - self.checkbox_input_mic_threshold_check = CTkCheckBox( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_input_mic_threshold_check_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_input_mic_threshold_check.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - - self.progressBar_input_mic_energy_threshold = CTkProgressBar( - self.tabview_config.tab(config_tab_title_transcription), - corner_radius=0 - ) - self.progressBar_input_mic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=5, sticky="nsew") - self.progressBar_input_mic_energy_threshold.set(0) - - ## checkbox input mic dynamic energy threshold - row +=1 - self.label_input_mic_dynamic_energy_threshold = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_dynamic_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.checkbox_input_mic_dynamic_energy_threshold = CTkCheckBox( - self.tabview_config.tab(config_tab_title_transcription), - text="", - onvalue=True, - offvalue=False, - command=self.checkbox_input_mic_dynamic_energy_threshold_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_input_mic_dynamic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - if config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD is True: - self.checkbox_input_mic_dynamic_energy_threshold.select() - else: - self.checkbox_input_mic_dynamic_energy_threshold.deselect() - - ## entry input mic record timeout - row +=1 - self.label_input_mic_record_timeout = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_record_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_input_mic_record_timeout = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=config.INPUT_MIC_RECORD_TIMEOUT), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_mic_record_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_mic_record_timeout.bind("", self.entry_input_mic_record_timeout_callback) - - ## entry input mic phrase timeout - row +=1 - self.label_input_mic_phrase_timeout = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_phrase_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_input_mic_phrase_timeout = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=config.INPUT_MIC_PHRASE_TIMEOUT), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_mic_phrase_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_mic_phrase_timeout.bind("", self.entry_input_mic_phrase_timeout_callback) - - ## entry input mic max phrases - row +=1 - self.label_input_mic_max_phrases = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_max_phrases.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_input_mic_max_phrases = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=config.INPUT_MIC_MAX_PHRASES), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_mic_max_phrases.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_mic_max_phrases.bind("", self.entry_input_mic_max_phrases_callback) - - ## entry input mic word filter - row +=1 - self.label_input_mic_word_filter = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_word_filter.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - if len(config.INPUT_MIC_WORD_FILTER) > 0: - textvariable=StringVar(value=",".join(config.INPUT_MIC_WORD_FILTER)) - else: - textvariable=None - self.entry_input_mic_word_filter = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=textvariable, - placeholder_text="AAA,BBB,CCC", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_mic_word_filter.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_mic_word_filter.bind("", self.entry_input_mic_word_filters_callback) - - ## optionmenu input speaker device - row +=1 - self.label_input_speaker_device = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_device.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_input_speaker_device = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_transcription), - values=model.getListOutputDevice(), - command=self.optionmenu_input_speaker_device_callback, - variable=StringVar(value=config.CHOICE_SPEAKER_DEVICE), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_input_speaker_device.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown input speaker device - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_input_speaker_device = CTkScrollableDropdown( - self.optionmenu_input_speaker_device, - values=model.getListOutputDevice(), - justify="left", - button_color="transparent", - command=self.optionmenu_input_speaker_device_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_input_speaker_device.bind( - "", - lambda e: self.scrollableDropdown_input_speaker_device._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_speaker_device.frame._parent_frame)) else None, - ) - - ## optionmenu input speaker voice language - row +=1 - self.label_input_speaker_voice_language = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_voice_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_input_speaker_voice_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_transcription), - values=list(transcription_lang.keys()), - command=self.optionmenu_input_speaker_voice_language_callback, - variable=StringVar(value=config.INPUT_SPEAKER_VOICE_LANGUAGE), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_input_speaker_voice_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown input speaker voice language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_input_speaker_voice_language = CTkScrollableDropdown( - self.optionmenu_input_speaker_voice_language, - values=list(transcription_lang.keys()), - justify="left", - button_color="transparent", - command=self.optionmenu_input_speaker_voice_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_input_speaker_voice_language.bind( - "", - lambda e: self.scrollableDropdown_input_speaker_voice_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_speaker_voice_language.frame._parent_frame)) else None, - ) - - ## entry input speaker energy threshold - row +=1 - self.label_input_speaker_energy_threshold = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - - ## progressBar input speaker energy threshold - self.slider_input_speaker_energy_threshold = CTkSlider( - self.tabview_config.tab(config_tab_title_transcription), - from_=0, - to=config.MAX_SPEAKER_ENERGY_THRESHOLD, - border_width=7, - button_length=0, - button_corner_radius=3, - number_of_steps=config.MAX_SPEAKER_ENERGY_THRESHOLD, - command=self.slider_input_speaker_energy_threshold_callback, - variable=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), - ) - self.slider_input_speaker_energy_threshold.grid(row=row, column=1, columnspan=1, padx=0, pady=5, sticky="nsew") - - ## progressBar input speaker energy threshold - row +=1 - self.checkbox_input_speaker_threshold_check = CTkCheckBox( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_input_speaker_threshold_check_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_input_speaker_threshold_check.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - - self.progressBar_input_speaker_energy_threshold = CTkProgressBar( - self.tabview_config.tab(config_tab_title_transcription), - corner_radius=0 - ) - self.progressBar_input_speaker_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=5, sticky="nsew") - self.progressBar_input_speaker_energy_threshold.set(0) - - ## checkbox input speaker dynamic energy threshold - row +=1 - self.label_input_speaker_dynamic_energy_threshold = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_dynamic_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.checkbox_input_speaker_dynamic_energy_threshold = CTkCheckBox( - self.tabview_config.tab(config_tab_title_transcription), - text="", - onvalue=True, - offvalue=False, - command=self.checkbox_input_speaker_dynamic_energy_threshold_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_input_speaker_dynamic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - if config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD is True: - self.checkbox_input_speaker_dynamic_energy_threshold.select() - else: - self.checkbox_input_speaker_dynamic_energy_threshold.deselect() - - ## entry input speaker record timeout - row +=1 - self.label_input_speaker_record_timeout = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_record_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_input_speaker_record_timeout = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=config.INPUT_SPEAKER_RECORD_TIMEOUT), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_speaker_record_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_speaker_record_timeout.bind("", self.entry_input_speaker_record_timeout_callback) - - ## entry input speaker phrase timeout - row +=1 - self.label_input_speaker_phrase_timeout = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_phrase_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_input_speaker_phrase_timeout = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=config.INPUT_SPEAKER_PHRASE_TIMEOUT), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_speaker_phrase_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_speaker_phrase_timeout.bind("", self.entry_input_speaker_phrase_timeout_callback) - - ## entry input speaker max phrases - row +=1 - self.label_input_speaker_max_phrases = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_max_phrases.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_input_speaker_max_phrases = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=config.INPUT_SPEAKER_MAX_PHRASES), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_speaker_max_phrases.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_speaker_max_phrases.bind("", self.entry_input_speaker_max_phrases_callback) - - # tab Parameter - ## entry ip address - row = 0 - padx = 5 - pady = 1 - self.label_ip_address = CTkLabel( - self.tabview_config.tab(config_tab_title_parameter), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_ip_address.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_ip_address = CTkEntry( - self.tabview_config.tab(config_tab_title_parameter), - textvariable=StringVar(value=config.OSC_IP_ADDRESS), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_ip_address.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_ip_address.bind("", self.entry_ip_address_callback) - - ## entry port - row +=1 - self.label_port = CTkLabel( - self.tabview_config.tab(config_tab_title_parameter), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_port.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_port = CTkEntry( - self.tabview_config.tab(config_tab_title_parameter), - textvariable=StringVar(value=config.OSC_PORT), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_port.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_port.bind("", self.entry_port_callback) - - ## entry authkey - row +=1 - self.label_authkey = CTkLabel( - self.tabview_config.tab(config_tab_title_parameter), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_authkey.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_authkey = CTkEntry( - self.tabview_config.tab(config_tab_title_parameter), - textvariable=StringVar(value=config.AUTH_KEYS["DeepL(auth)"]), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_authkey.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_authkey.bind("", self.entry_authkey_callback) - - ## entry message format - row +=1 - self.label_message_format = CTkLabel( - self.tabview_config.tab(config_tab_title_parameter), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_message_format.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_message_format = CTkEntry( - self.tabview_config.tab(config_tab_title_parameter), - textvariable=StringVar(value=config.MESSAGE_FORMAT), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_message_format.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_message_format.bind("", self.entry_message_format_callback) - - # tab Others - ## checkbox auto clear chat box - row = 0 - self.label_checkbox_auto_clear_chatbox = CTkLabel( - self.tabview_config.tab(config_tab_title_others), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_checkbox_auto_clear_chatbox.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.checkbox_auto_clear_chatbox = CTkCheckBox( - self.tabview_config.tab(config_tab_title_others), - text="", - onvalue=True, - offvalue=False, - command=self.checkbox_auto_clear_chatbox_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_auto_clear_chatbox.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - if config.ENABLE_AUTO_CLEAR_CHATBOX is True: - self.checkbox_auto_clear_chatbox.select() - else: - self.checkbox_auto_clear_chatbox.deselect() - - # checkbox notice xsoverlay - row += 1 - self.label_checkbox_notice_xsoverlay = CTkLabel( - self.tabview_config.tab(config_tab_title_others), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_checkbox_notice_xsoverlay.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.checkbox_notice_xsoverlay = CTkCheckBox( - self.tabview_config.tab(config_tab_title_others), - text="", - onvalue=True, - offvalue=False, - command=self.checkbox_notice_xsoverlay_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_notice_xsoverlay.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - if config.ENABLE_NOTICE_XSOVERLAY is True: - self.checkbox_notice_xsoverlay.select() - else: - self.checkbox_notice_xsoverlay.deselect() - widget_config_window_label_setter(self, language_yaml_data) \ No newline at end of file diff --git a/window_information.py b/window_information.py deleted file mode 100644 index cc8d8792..00000000 --- a/window_information.py +++ /dev/null @@ -1,161 +0,0 @@ -import os -from customtkinter import CTkToplevel, CTkTextbox, CTkFont -from config import config - -class ToplevelWindowInformation(CTkToplevel): - def __init__(self, parent, *args, **kwargs): - super().__init__(parent, *args, **kwargs) - self.withdraw() - self.parent = parent - self.grid_columnconfigure(0, weight=1) - self.grid_rowconfigure(0, weight=1) - # self.geometry(f"{500}x{300}") - self.minsize(500, 300) - - self.after(200, lambda: self.iconbitmap(os.path.join(os.path.dirname(__file__), "img", "app.ico"))) - self.title("Information") - # create textbox information - self.textbox_information = CTkTextbox( - self, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.textbox_information.grid(row=0, column=0, padx=(10, 10), pady=(10, 10), sticky="nsew") - textbox_information_message = """VRCT(v1.3.2) - -# 概要 -VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツールになります。 -翻訳エンジンを使用してメッセージとその翻訳部分を同時に送信することができます。 -(翻訳エンジンはDeepL,Google,Bingに対応) - -# 使用方法 - 初期設定時 - 0. VRChatのOSCを有効にする(重要) - - (任意) - 1. DeepLのAPIを使用するためにアカウント登録し、認証キーを取得する - 2. ギアアイコンのボタンでconfigウィンドウを開く - 3. ParameterタブのDeepL Auth Keyに認証キーを記載 - 4. configウィンドウを閉じる - - 通常使用時 - 1. メッセージボックスにメッセージを記入 - 2. Enterキーを押し、メッセージを送信する - -# その他の設定 - translation チェックボックス: 翻訳の有効無効 - voice2chatbox チェックボックス : マイクの音声を文字起こししてチャットボックスに送信する - speaker2log チェックボックス : スピーカーの音声から文字起こししてログに表示する - foreground チェックボックス: 最前面表示の有効無効 - - テキストボックス - logタブ - すべてのログを表示 - sendタブ - 送信したメッセージを表示 - receiveタブ - 受信したメッセージを表示 - systemタブ - 機能についてのメッセージを表示 - - configウィンドウ - UIタブ - Transparency: ウィンドウの透過度の調整 - Appearance Theme: ウィンドウテーマを選択 - UI Scaling: UIサイズを調整 - Font Family: 表示フォントを選択 - UI Language: UIの表示言語を選択 - Translationタブ - Select Translator: 翻訳エンジンの変更 - Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択 - Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択 - Transcriptionタブ - Input Mic Host: マイクのホストAPIを選択 - Input Mic Device: マイクを選択 - Input Mic Voice Language: 入力する音声の言語 - Input Mic Energy Threshold: 音声取得のしきい値 - Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化 - Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - Input Mic Record Timeout: 音声の区切りの無音時間 - Input Mic Phase Timeout: 文字起こしする音声時間の上限 - Input Mic Max Phrases: 保留する単語の上限 - Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC) - Input Speaker Device: スピーカーを選択 - Input Speaker Voice Language: 受信する音声の言語 - Input Speaker Energy Threshold: 音声取得のしきい値 - Check threshold point: Input Speaker Energy Thresholdのしきい値を視覚化 - Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - Input Speaker Record Timeout: 音声の区切りの無音時間 - Input Speaker Phase Timeout: 文字起こしする音声時間の上限 - Input Speaker Max Phrases: 保留する単語の上限 - Parameterタブ - OSC IP address: 変更不要 - OSC port: 変更不要 - DeepL Auth key: DeepLの認証キーの設定 - Message Format: 送信するメッセージのデコレーションの設定 - [message]がメッセージボックスに記入したメッセージに置換される - [translation]が翻訳されたメッセージに置換される - 初期フォーマット:"[message]([translation])" - Othersタブ - Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする - (New!) Notification XSOverlay: XSOverlayの通知機能を有効(VR only) - - 設定の初期化 - config.jsonを削除 - -# お問い合わせ -要望などはTwitterまで -https://twitter.com/misya_ai - -# アップデート履歴 -[2023-05-29: v0.1b] v0.1b リリース -[2023-05-30: v0.2b] -- configボタンをギアアイコンに変更 -- 詳細情報のボタンを追加 -- 翻訳機能有効無効のチェックボックスを追加 -- 最前面表示の有効無効のチェックボックスを追加 -- いくつかのバグを修正 -[2023-06-03: v0.3b] -- 全体的にUIを刷新 -- 透過機能を追加 -- テーマのLight/Dark/Systemのモードの変更機能を追加 -- UIのスケール変更機能を追加 -- フォントの変更機能を追加 -[2023-06-06: v0.4b] -- 翻訳エンジンを追加 -- 入力と出力の翻訳言語を選択できるように変更 -[2023-06-20: v1.0] -- マイクからの音声の文字起こし機能を追加 -- スピーカーからの音声の文字起こし機能を追加 -[2023-06-28: v1.1] -- いくつかのバクを修正 -- 翻訳/文字起こし言語の表記を略称からわかりやすい文字に変更 -- 文字起こしの処理の軽量化 -[2023-07-05: v1.2] -- 文字起こし精度の向上 -[2023-07-21: v1.3] -- UIの表示言語を日本語/英語を選択できる機能を追加 -- Energy Thresholdの視覚化機能を追加 -- 文字起こしの誤認識対策のため、Word Filterを追加 -- WASAPI以外のHostAPIでもマイクとして使用できるようにHostAPIを選択できる機能を追加 -- メッセージ送信後に書き込んだメッセージを空にするか選択できる機能を追加 -- バグ対策のため、translation/voice2chatbox/speaker2log/foregroundは起動時はOFFになるように変更 -- バグ対策のため、スピーカーについて既定デバイス以外を選択した時にERRORが出るように変更 -- 半角入力時に一部の文字が書き込めないバグを修正 -[2023-07-22: v1.3.1] -- UIの表示言語選択に韓国語を追加 -[2023-07-30: v1.3.2] -- 試験的にXSOverlayへの通知機能を追加 -- checkbox ONの状態でもConfigを開けるように変更 -- 文字起こし言語の表示を修正 -- いくつかのバグを修正 - -# 注意事項 -再配布とかはやめてね -""" - - self.textbox_information.insert("end", textbox_information_message) - self.textbox_information.configure(state='disabled') - self.protocol("WM_DELETE_WINDOW", self.delete_window) - - def delete_window(self): - self.withdraw() \ No newline at end of file From aabfa535bf1cb39a8b533d8e9d5be8a18d029c8e Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 4 Sep 2023 12:26:27 +0900 Subject: [PATCH 084/355] =?UTF-8?q?[update]=20auth=20key=E3=81=8C=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=82=8B=E5=A0=B4?= =?UTF-8?q?=E5=90=88=E3=80=81=E5=84=AA=E5=85=88=E7=9A=84=E3=81=ABDeepL(aut?= =?UTF-8?q?h)=E3=82=92=E4=BD=BF=E7=94=A8=E3=81=99=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ※view.pyにprintToTextbox_AuthenticationSuccessが実装される前提で実装 --- main.py | 12 +++++------- model.py | 8 ++++++-- models/translation/translation_languages.py | 3 +-- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/main.py b/main.py index 1ed44b42..93cb2a71 100644 --- a/main.py +++ b/main.py @@ -237,13 +237,11 @@ def callbackSetUiLanguage(value): # Translation Tab def callbackSetDeeplAuthkey(value): print("callbackSetDeeplAuthkey", str(value)) - # config.AUTH_KEYS["DeepL(auth)"] = str(value) - # if len(value) > 0: - # if model.authenticationTranslator(callbackSetAuthKeys, choice_translator="DeepL(auth)", auth_key=value) is True: - # print_textbox(self.parent.textbox_message_log, "Auth key update completed", "INFO") - # print_textbox(self.parent.textbox_message_system_log, "Auth key update completed", "INFO") - # else: - # pass + if len(value) > 0 and model.authenticationTranslator(callbackSetAuthKeys, choice_translator="DeepL(auth)", auth_key=value) is True: + config.CHOICE_TRANSLATOR = "DeepL(auth)" + view.printToTextbox_AuthenticationSuccess() + else: + view.printToTextbox_AuthenticationError() # Transcription Tab (Mic) def callbackSetMicHost(value): diff --git a/model.py b/model.py index 48735c3d..20524129 100644 --- a/model.py +++ b/model.py @@ -78,7 +78,7 @@ class Model: if result: auth_keys = config.AUTH_KEYS auth_keys[choice_translator] = auth_key - fnc(auth_key) + fnc(auth_keys) return result def startLogger(self): @@ -119,7 +119,11 @@ class Model: target_languages = translation_lang.get(engine, {}).get("target", {}) if source_lang in source_languages and target_lang in target_languages: compatible_engines.append(engine) - return compatible_engines[0] + engine_name = compatible_engines[0] + + if engine_name == "DeepL(web)" and config.AUTH_KEYS["DeepL(auth)"] != None: + engine_name = "DeepL(auth)" + return engine_name def getTranslatorStatus(self): return self.translator.translator_status[config.CHOICE_TRANSLATOR] diff --git a/models/translation/translation_languages.py b/models/translation/translation_languages.py index a18eb5a1..1b68bf81 100644 --- a/models/translation/translation_languages.py +++ b/models/translation/translation_languages.py @@ -1,5 +1,4 @@ -# translatorEngine = ["DeepL(web)", "DeepL(auth)", "Google(web)", "Bing(web)"] -translatorEngine = ["DeepL(web)", "Google(web)", "Bing(web)"] +translatorEngine = ["DeepL(web)", "DeepL(auth)", "Google(web)", "Bing(web)"] translation_lang = {} dict_deepl_web_languages = { "Japanese":"JA", From a6137149a0f58e461ca79424ed0673de60fa3c13 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 4 Sep 2023 12:33:00 +0900 Subject: [PATCH 085/355] =?UTF-8?q?[Add]=20view.py=E3=81=ABprintToTextbox?= =?UTF-8?q?=5FAuthenticationSuccess=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/view.py b/view.py index 778f159a..c7e29804 100644 --- a/view.py +++ b/view.py @@ -360,6 +360,8 @@ class View(): def printToTextbox_disableForeground(self): self._printToTextbox_Info("Stop foreground") + def printToTextbox_AuthenticationSuccess(self): + self._printToTextbox_Info("Auth key update completed") def printToTextbox_AuthenticationError(self): self._printToTextbox_Info("Auth Key or language setting is incorrect") From 26403461b4fb77b94eac0e39e8088a42390b1ae6 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 4 Sep 2023 12:40:34 +0900 Subject: [PATCH 086/355] =?UTF-8?q?[bugfix]=20model.findTranslationEngine?= =?UTF-8?q?=E3=81=A7=E7=BF=BB=E8=A8=B3=E3=82=A8=E3=83=B3=E3=82=B8=E3=83=B3?= =?UTF-8?q?=E3=82=92=E8=A8=AD=E5=AE=9A=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 93cb2a71..a5e5351b 100644 --- a/main.py +++ b/main.py @@ -238,7 +238,7 @@ def callbackSetUiLanguage(value): def callbackSetDeeplAuthkey(value): print("callbackSetDeeplAuthkey", str(value)) if len(value) > 0 and model.authenticationTranslator(callbackSetAuthKeys, choice_translator="DeepL(auth)", auth_key=value) is True: - config.CHOICE_TRANSLATOR = "DeepL(auth)" + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) view.printToTextbox_AuthenticationSuccess() else: view.printToTextbox_AuthenticationError() From c819b956b4aa75b32c894025ca208f358cb9bd01 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 4 Sep 2023 14:48:25 +0900 Subject: [PATCH 087/355] index on view: 6b4d782 Merge branch 'remove_modules' into UI_2.0 From 90668b1e7aa8beef6a8cd4ee236d35800082e0c3 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:17:01 +0900 Subject: [PATCH 088/355] =?UTF-8?q?[Add]=20view.py=E3=81=AB=E3=80=81?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E9=A0=85=E7=9B=AE=20mic=5Fhost=20mic=5Fdevic?= =?UTF-8?q?e=20speaker=5Fdevice=E3=81=AE=E6=9B=B4=E6=96=B0=E3=80=81?= =?UTF-8?q?=E3=82=BB=E3=83=83=E3=83=88=E9=96=A2=E6=95=B0=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=E3=80=82=20model.py=E3=81=A7=E5=AE=9F=E9=9A=9B?= =?UTF-8?q?=E3=81=ABdevice=E3=81=AA=E3=81=A9=E3=82=92=E5=8F=96=E5=BE=97?= =?UTF-8?q?=E3=81=99=E3=82=8B=E9=96=A2=E6=95=B0=E3=82=82=E5=85=A5=E3=82=8C?= =?UTF-8?q?=E8=BE=BC=E3=81=BF(=E5=85=83=E3=80=85=E3=81=82=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E3=82=82=E3=81=AE=E3=82=92=E7=A7=BB=E6=A4=8D)?= =?UTF-8?q?=E3=81=97=E3=81=9F=E3=81=AE=E3=81=A7=E3=80=81=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E7=94=BB=E9=9D=A2=E4=B8=8A=E3=81=A7=E8=A6=8B=E3=81=9F=E7=9B=AE?= =?UTF-8?q?=E3=81=A0=E3=81=91=E3=81=AF=E5=AE=9F=E9=9A=9B=E3=81=AE=E5=80=A4?= =?UTF-8?q?=E3=81=A7=E5=8B=95=E3=81=8F=E3=82=88=E3=81=86=E3=81=AB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 9 +++++++-- view.py | 34 ++++++++++++++++++++++++++-------- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/main.py b/main.py index 149ed4e0..4a36015e 100644 --- a/main.py +++ b/main.py @@ -247,6 +247,10 @@ def callbackSetDeeplAuthkey(value): def callbackSetMicHost(value): print("callbackSetMicHost", value) config.CHOICE_MIC_HOST = value + config.CHOICE_MIC_DEVICE = model.getInputDefaultDevice() + + view.updateSelected_MicDevice(config.CHOICE_MIC_DEVICE) + view.updateList_MicDevice(model.getListInputDevice()) def callbackSetMicDevice(value): print("callbackSetMicDevice", value) @@ -415,9 +419,9 @@ view.register( # Transcription Tab (Mic) "callback_set_mic_host": callbackSetMicHost, - "list_mic_host": ["list_mic_host"], + "list_mic_host": model.getListInputHost(), "callback_set_mic_device": callbackSetMicDevice, - "list_mic_device": ["list_mic_device"], + "list_mic_device": model.getListInputDevice(), "callback_set_mic_energy_threshold": callbackSetMicEnergyThreshold, "callback_set_mic_dynamic_energy_threshold": callbackSetMicDynamicEnergyThreshold, "callback_check_mic_threshold": callbackCheckMicThreshold, @@ -428,6 +432,7 @@ view.register( # Transcription Tab (Speaker) "callback_set_speaker_device": callbackSetSpeakerDevice, + "list_speaker_device": model.getListOutputDevice(), "callback_set_speaker_energy_threshold": callbackSetSpeakerEnergyThreshold, "callback_set_speaker_dynamic_energy_threshold": callbackSetSpeakerDynamicEnergyThreshold, "callback_check_speaker_threshold": callbackCheckSpeakerThreshold, diff --git a/view.py b/view.py index 26d7adbe..6d625a2e 100644 --- a/view.py +++ b/view.py @@ -105,13 +105,13 @@ class View(): # Transcription Tab (Mic) VAR_LABEL_MIC_HOST=StringVar(value="Mic Host"), VAR_DESC_MIC_HOST=StringVar(value="Select the mic host. (Default: ?)"), - LIST_MIC_HOST=[], # model.getListInputHost(), + LIST_MIC_HOST=[], CALLBACK_SET_MIC_HOST=None, VAR_MIC_HOST=StringVar(value=config.CHOICE_MIC_HOST), VAR_LABEL_MIC_DEVICE=StringVar(value="Mic Device"), VAR_DESC_MIC_DEVICE=StringVar(value="Select the mic devise. (Default: ?)"), - LIST_MIC_DEVICE=[], # model.getListInputDevice(), + LIST_MIC_DEVICE=[], CALLBACK_SET_MIC_DEVICE=None, VAR_MIC_DEVICE=StringVar(value=config.CHOICE_MIC_DEVICE), @@ -150,7 +150,7 @@ class View(): # Transcription Tab (Speaker) VAR_LABEL_SPEAKER_DEVICE=StringVar(value="Speaker Device"), VAR_DESC_SPEAKER_DEVICE=StringVar(value="Select the speaker devise. (Default: ?)"), - LIST_SPEAKER_DEVICE=[], # model.getListOutputDevice(), + LIST_SPEAKER_DEVICE=[], CALLBACK_SET_SPEAKER_DEVICE=None, VAR_SPEAKER_DEVICE=StringVar(value=config.CHOICE_SPEAKER_DEVICE), @@ -276,11 +276,11 @@ class View(): # Transcription Tab (Mic) vrct_gui.config_window.CALLBACK_SET_MIC_HOST = config_window["callback_set_mic_host"] - # vrct_gui.config_window.LIST_MIC_HOST = config_window["list_mic_host"] - self.updateList_MicHost(vrct_gui.config_window.LIST_MIC_HOST) + self.updateList_MicHost(config_window["list_mic_host"]) vrct_gui.config_window.CALLBACK_SET_MIC_DEVICE = config_window["callback_set_mic_device"] - vrct_gui.config_window.LIST_MIC_DEVISE = config_window["list_mic_device"] + self.updateList_MicDevice(config_window["list_mic_device"]) + vrct_gui.config_window.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window["callback_set_mic_energy_threshold"] vrct_gui.config_window.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_mic_dynamic_energy_threshold"] @@ -292,6 +292,8 @@ class View(): # Transcription Tab (Speaker) vrct_gui.config_window.CALLBACK_SET_SPEAKER_DEVICE = config_window["callback_set_speaker_device"] + self.updateList_SpeakerDevice(config_window["list_speaker_device"]) + vrct_gui.config_window.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window["callback_set_speaker_energy_threshold"] vrct_gui.config_window.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_speaker_dynamic_energy_threshold"] vrct_gui.config_window.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window["callback_check_speaker_threshold"] @@ -422,8 +424,24 @@ class View(): vrct_gui.config_window.reloadConfigWindowSettingBoxContainer() - def updateList_MicHost(self, list__mic_host): - vrct_gui.config_window.LIST_MIC_HOST = list__mic_host + def updateList_MicHost(self, new_mic_host_list:list): + vrct_gui.config_window.LIST_MIC_HOST = new_mic_host_list + vrct_gui.config_window.sb__optionmenu_mic_host.configure(values=new_mic_host_list) + def updateSelected_MicHost(self, selected_mic_host_name:str): + vrct_gui.config_window.VAR_MIC_HOST.set(selected_mic_host_name) + + def updateList_MicDevice(self, new_mic_device_list): + vrct_gui.config_window.LIST_MIC_DEVICE = new_mic_device_list + vrct_gui.config_window.sb__optionmenu_mic_device.configure(values=new_mic_device_list) + + def updateSelected_MicDevice(self, default_selected_mic_device_name:str): + self.view_variable.VAR_MIC_DEVICE.set(default_selected_mic_device_name) + + + + def updateList_SpeakerDevice(self, new_speaker_device_list): + vrct_gui.config_window.LIST_SPEAKER_DEVICE = new_speaker_device_list + vrct_gui.config_window.sb__optionmenu_speaker_device.configure(values=new_speaker_device_list) view = View() \ No newline at end of file From fb22ffa31ea70c9fea4b3dd042dccb6b0e483811 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 4 Sep 2023 18:29:04 +0900 Subject: [PATCH 089/355] =?UTF-8?q?[bugfix]=20Config=20Window=20Font=20Fam?= =?UTF-8?q?ily=20CALLBACK=E9=96=A2=E6=95=B0=E7=99=BB=E9=8C=B2=E3=81=AE?= =?UTF-8?q?=E3=81=97=E5=BF=98=E3=82=8C(=E3=83=86=E3=82=B9=E3=83=88?= =?UTF-8?q?=E7=94=A8=E3=81=AB=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=A2?= =?UTF-8?q?=E3=82=A6=E3=83=88=E3=81=97=E3=81=9F=E3=81=BE=E3=81=BE=E3=81=A0?= =?UTF-8?q?=E3=81=A3=E3=81=9F)=E3=81=A8=E3=80=81=E5=AE=9F=E9=9A=9B?= =?UTF-8?q?=E3=81=AB=E3=83=95=E3=82=A9=E3=83=B3=E3=83=88=E9=81=B8=E6=8A=9E?= =?UTF-8?q?=E6=99=82=E3=81=AB=E6=AD=A3=E3=81=97=E3=81=8F=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E3=81=8C=E5=8B=95=E3=81=84=E3=81=A6=E3=81=84=E3=81=AA=E3=81=8B?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 18 +----------------- .../createSettingBox_Appearance.py | 2 +- 2 files changed, 2 insertions(+), 18 deletions(-) diff --git a/view.py b/view.py index 6d625a2e..25d75690 100644 --- a/view.py +++ b/view.py @@ -249,25 +249,10 @@ class View(): # Appearance Tab vrct_gui.config_window.CALLBACK_SET_TRANSPARENCY = config_window["callback_set_transparency"] - # vrct_gui.config_window.sb__transparency_slider.configure(variable=IntVar(value=config.TRANSPARENCY)) vrct_gui.config_window.CALLBACK_SET_APPEARANCE = config_window["callback_set_appearance"] vrct_gui.config_window.CALLBACK_SET_UI_SCALING = config_window["callback_set_ui_scaling"] - - self.view_variable.CALLBACK_SET_FONT_FAMILY = config_window["callback_set_font_family"] - - - - # vrct_gui.config_window.sb__optionmenu_font_family.configure(values=self.view_variable.LIST_FONT_FAMILY) - - # self.view_variable.VAR_FONT_FAMILY = StringVar(value=config.FONT_FAMILY) - # vrct_gui.config_window.sb__optionmenu_font_family.configure(variable=self.view_variable.VAR_FONT_FAMILY) - - - - # vrct_gui.config_window.sb__optionmenu_font_family.configure(variable=StringVar(value=config.FONT_FAMILY)) - # vrct_gui.config_window.sb__optionmenu_font_family.configure(values=["test", "from", "view.py"]) - + vrct_gui.config_window.CALLBACK_SET_FONT_FAMILY = config_window["callback_set_font_family"] vrct_gui.config_window.CALLBACK_SET_UI_LANGUAGE = config_window["callback_set_ui_language"] @@ -281,7 +266,6 @@ class View(): vrct_gui.config_window.CALLBACK_SET_MIC_DEVICE = config_window["callback_set_mic_device"] self.updateList_MicDevice(config_window["list_mic_device"]) - vrct_gui.config_window.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window["callback_set_mic_energy_threshold"] vrct_gui.config_window.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_mic_dynamic_energy_threshold"] vrct_gui.config_window.CALLBACK_CHECK_MIC_THRESHOLD = config_window["callback_check_mic_threshold"] diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index 800288a2..211f7bf3 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -19,7 +19,7 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): callFunctionIfCallable(config_window.CALLBACK_SET_UI_SCALING, value) def optionmenu_font_family_callback(value): - callFunctionIfCallable(config_window.view_variable.CALLBACK_SET_FONT_FAMILY, value) + callFunctionIfCallable(config_window.CALLBACK_SET_FONT_FAMILY, value) def optionmenu_ui_language_callback(value): callFunctionIfCallable(config_window.CALLBACK_SET_UI_LANGUAGE, value) From 69fae94707e1d8e42c1dc0f614a5058349164f45 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 4 Sep 2023 22:48:24 +0900 Subject: [PATCH 090/355] =?UTF-8?q?[bugifx]=20Config=20Window:=20view=5Fva?= =?UTF-8?q?riable(view.py=E5=A4=89=E6=95=B0)=E3=81=AE=E5=8F=82=E7=85=A7?= =?UTF-8?q?=E3=81=AE=E4=BB=95=E6=96=B9=E3=81=8C=E3=81=8A=E3=81=8B=E3=81=97?= =?UTF-8?q?=E3=81=8F=E3=80=81=E6=84=8F=E5=9B=B3=E3=81=97=E3=81=9F=E5=80=A4?= =?UTF-8?q?=E3=81=AE=E4=BF=9D=E5=AD=98=E3=81=A8=E5=8F=82=E7=85=A7=E3=81=8C?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=8B=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 76 +++++++++---------- vrct_gui/config_window/ConfigWindow.py | 15 ++-- .../_createSettingBoxCompactModeButton.py | 10 +-- .../createSettingBoxTopBar.py | 4 +- .../_createSettingBoxContainer.py | 9 ++- .../createSideMenuAndSettingsBoxContainers.py | 4 +- .../createSettingBox_AdvancedSettings.py | 18 ++--- .../createSettingBox_Appearance.py | 52 ++++++------- .../createSettingBox_Others.py | 26 +++---- .../createSettingBox_Mic.py | 73 +++++++++--------- .../createSettingBox_Speaker.py | 54 ++++++------- .../createSettingBox_Translation.py | 10 +-- 12 files changed, 179 insertions(+), 172 deletions(-) diff --git a/view.py b/view.py index 25d75690..37b362ad 100644 --- a/view.py +++ b/view.py @@ -216,10 +216,10 @@ class View(): self.view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = self._toggleMainWindowSidebarCompactMode - vrct_gui.CALLBACK_TOGGLE_TRANSLATION = sidebar_features["callback_toggle_translation"] - vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features["callback_toggle_transcription_send"] - vrct_gui.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features["callback_toggle_transcription_receive"] - vrct_gui.CALLBACK_TOGGLE_FOREGROUND = sidebar_features["callback_toggle_foreground"] + self.view_variable.CALLBACK_TOGGLE_TRANSLATION = sidebar_features["callback_toggle_translation"] + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features["callback_toggle_transcription_send"] + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features["callback_toggle_transcription_receive"] + self.view_variable.CALLBACK_TOGGLE_FOREGROUND = sidebar_features["callback_toggle_foreground"] vrct_gui.sls__optionmenu_your_language.configure(values=language_presets["values"]) @@ -229,7 +229,7 @@ class View(): vrct_gui.sls__optionmenu_target_language.configure(command=language_presets["callback_target_language"]) vrct_gui.sls__optionmenu_target_language.configure(variable=StringVar(value=config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO])) - vrct_gui.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets["callback_selected_language_preset_tab"] + self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets["callback_selected_language_preset_tab"] vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) @@ -243,56 +243,56 @@ class View(): # Config Window # Compact Mode Switch - vrct_gui.config_window.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window["callback_disable_config_window_compact_mode"] - vrct_gui.config_window.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window["callback_enable_config_window_compact_mode"] + self.view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window["callback_disable_config_window_compact_mode"] + self.view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window["callback_enable_config_window_compact_mode"] # Appearance Tab - vrct_gui.config_window.CALLBACK_SET_TRANSPARENCY = config_window["callback_set_transparency"] + self.view_variable.CALLBACK_SET_TRANSPARENCY = config_window["callback_set_transparency"] - vrct_gui.config_window.CALLBACK_SET_APPEARANCE = config_window["callback_set_appearance"] - vrct_gui.config_window.CALLBACK_SET_UI_SCALING = config_window["callback_set_ui_scaling"] - vrct_gui.config_window.CALLBACK_SET_FONT_FAMILY = config_window["callback_set_font_family"] - vrct_gui.config_window.CALLBACK_SET_UI_LANGUAGE = config_window["callback_set_ui_language"] + self.view_variable.CALLBACK_SET_APPEARANCE = config_window["callback_set_appearance"] + self.view_variable.CALLBACK_SET_UI_SCALING = config_window["callback_set_ui_scaling"] + self.view_variable.CALLBACK_SET_FONT_FAMILY = config_window["callback_set_font_family"] + self.view_variable.CALLBACK_SET_UI_LANGUAGE = config_window["callback_set_ui_language"] # Translation Tab - vrct_gui.config_window.CALLBACK_SET_DEEPL_AUTHKEY = config_window["callback_set_deepl_authkey"] + self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window["callback_set_deepl_authkey"] # Transcription Tab (Mic) - vrct_gui.config_window.CALLBACK_SET_MIC_HOST = config_window["callback_set_mic_host"] + self.view_variable.CALLBACK_SET_MIC_HOST = config_window["callback_set_mic_host"] self.updateList_MicHost(config_window["list_mic_host"]) - vrct_gui.config_window.CALLBACK_SET_MIC_DEVICE = config_window["callback_set_mic_device"] + self.view_variable.CALLBACK_SET_MIC_DEVICE = config_window["callback_set_mic_device"] self.updateList_MicDevice(config_window["list_mic_device"]) - vrct_gui.config_window.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window["callback_set_mic_energy_threshold"] - vrct_gui.config_window.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_mic_dynamic_energy_threshold"] - vrct_gui.config_window.CALLBACK_CHECK_MIC_THRESHOLD = config_window["callback_check_mic_threshold"] - vrct_gui.config_window.CALLBACK_SET_MIC_RECORD_TIMEOUT = config_window["callback_set_mic_record_timeout"] - vrct_gui.config_window.CALLBACK_SET_MIC_PHRASE_TIMEOUT = config_window["callback_set_mic_phrase_timeout"] - vrct_gui.config_window.CALLBACK_SET_MIC_MAX_PHRASES = config_window["callback_set_mic_max_phrases"] - vrct_gui.config_window.CALLBACK_SET_MIC_WORD_FILTER = config_window["callback_set_mic_word_filter"] + self.view_variable.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window["callback_set_mic_energy_threshold"] + self.view_variable.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_mic_dynamic_energy_threshold"] + self.view_variable.CALLBACK_CHECK_MIC_THRESHOLD = config_window["callback_check_mic_threshold"] + self.view_variable.CALLBACK_SET_MIC_RECORD_TIMEOUT = config_window["callback_set_mic_record_timeout"] + self.view_variable.CALLBACK_SET_MIC_PHRASE_TIMEOUT = config_window["callback_set_mic_phrase_timeout"] + self.view_variable.CALLBACK_SET_MIC_MAX_PHRASES = config_window["callback_set_mic_max_phrases"] + self.view_variable.CALLBACK_SET_MIC_WORD_FILTER = config_window["callback_set_mic_word_filter"] # Transcription Tab (Speaker) - vrct_gui.config_window.CALLBACK_SET_SPEAKER_DEVICE = config_window["callback_set_speaker_device"] + self.view_variable.CALLBACK_SET_SPEAKER_DEVICE = config_window["callback_set_speaker_device"] self.updateList_SpeakerDevice(config_window["list_speaker_device"]) - vrct_gui.config_window.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window["callback_set_speaker_energy_threshold"] - vrct_gui.config_window.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_speaker_dynamic_energy_threshold"] - vrct_gui.config_window.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window["callback_check_speaker_threshold"] - vrct_gui.config_window.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = config_window["callback_set_speaker_record_timeout"] - vrct_gui.config_window.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = config_window["callback_set_speaker_phrase_timeout"] - vrct_gui.config_window.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window["callback_set_speaker_max_phrases"] + self.view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window["callback_set_speaker_energy_threshold"] + self.view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_speaker_dynamic_energy_threshold"] + self.view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window["callback_check_speaker_threshold"] + self.view_variable.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = config_window["callback_set_speaker_record_timeout"] + self.view_variable.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = config_window["callback_set_speaker_phrase_timeout"] + self.view_variable.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window["callback_set_speaker_max_phrases"] # Others Tab - vrct_gui.config_window.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window["callback_set_enable_auto_clear_chatbox"] - vrct_gui.config_window.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window["callback_set_enable_notice_xsoverlay"] - vrct_gui.config_window.CALLBACK_SET_MESSAGE_FORMAT = config_window["callback_set_message_format"] + self.view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window["callback_set_enable_auto_clear_chatbox"] + self.view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window["callback_set_enable_notice_xsoverlay"] + self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window["callback_set_message_format"] # Advanced Settings Tab - vrct_gui.config_window.CALLBACK_SET_OSC_IP_ADDRESS = config_window["callback_set_osc_ip_address"] - vrct_gui.config_window.CALLBACK_SET_OSC_PORT = config_window["callback_set_osc_port"] + self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window["callback_set_osc_ip_address"] + self.view_variable.CALLBACK_SET_OSC_PORT = config_window["callback_set_osc_port"] @@ -409,14 +409,14 @@ class View(): def updateList_MicHost(self, new_mic_host_list:list): - vrct_gui.config_window.LIST_MIC_HOST = new_mic_host_list + self.view_variable.LIST_MIC_HOST = new_mic_host_list vrct_gui.config_window.sb__optionmenu_mic_host.configure(values=new_mic_host_list) def updateSelected_MicHost(self, selected_mic_host_name:str): - vrct_gui.config_window.VAR_MIC_HOST.set(selected_mic_host_name) + self.view_variable.VAR_MIC_HOST.set(selected_mic_host_name) def updateList_MicDevice(self, new_mic_device_list): - vrct_gui.config_window.LIST_MIC_DEVICE = new_mic_device_list + self.view_variable.LIST_MIC_DEVICE = new_mic_device_list vrct_gui.config_window.sb__optionmenu_mic_device.configure(values=new_mic_device_list) def updateSelected_MicDevice(self, default_selected_mic_device_name:str): @@ -425,7 +425,7 @@ class View(): def updateList_SpeakerDevice(self, new_speaker_device_list): - vrct_gui.config_window.LIST_SPEAKER_DEVICE = new_speaker_device_list + self.view_variable.LIST_SPEAKER_DEVICE = new_speaker_device_list vrct_gui.config_window.sb__optionmenu_speaker_device.configure(values=new_speaker_device_list) view = View() \ No newline at end of file diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index c2c87c1c..8ca90445 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -18,19 +18,18 @@ class ConfigWindow(CTkToplevel): self.protocol("WM_DELETE_WINDOW", vrct_gui.closeConfigWindow) self.settings = settings - self.view_variable = view_variable + self._view_variable = view_variable + + createConfigWindowTitle(config_window=self, settings=self.settings) + + createSettingBoxTopBar(config_window=self, settings=self.settings, view_variable=self._view_variable) - createConfigWindowTitle(config_window=self, settings=settings) - - createSettingBoxTopBar(config_window=self, settings=settings) - - - createSideMenuAndSettingsBoxContainers(config_window=self, settings=settings) + createSideMenuAndSettingsBoxContainers(config_window=self, settings=self.settings, view_variable=self._view_variable) def reloadConfigWindowSettingBoxContainer(self): self.main_bg_container.destroy() - createSideMenuAndSettingsBoxContainers(config_window=self, settings=self.settings) \ No newline at end of file + createSideMenuAndSettingsBoxContainers(config_window=self, settings=self.settings, view_variable=self._view_variable) \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py index 0d9e21ed..7ffc533f 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py @@ -1,15 +1,15 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkSwitch -def _createSettingBoxCompactModeButton(parent_widget, config_window, settings): +def _createSettingBoxCompactModeButton(parent_widget, config_window, settings, view_variable): def switchConfigWindowCompactMode(): print(config_window.setting_box_compact_mode_switch_box.get()) if config_window.setting_box_compact_mode_switch_box.get() is True: - if callable(config_window.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE) is True: - config_window.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE() + if callable(view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE) is True: + view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE() else: - if callable(config_window.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE) is True: - config_window.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE() + if callable(view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE) is True: + view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE() diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py index a42d43f9..8ec955d6 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py @@ -3,7 +3,7 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel from ._createSettingBoxTitle import _createSettingBoxTitle from ._createSettingBoxCompactModeButton import _createSettingBoxCompactModeButton -def createSettingBoxTopBar(config_window, settings): +def createSettingBoxTopBar(config_window, settings, view_variable): config_window.grid_columnconfigure(1, weight=1) config_window.setting_box_top_bar = CTkFrame(config_window, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) @@ -12,4 +12,4 @@ def createSettingBoxTopBar(config_window, settings): _createSettingBoxTitle(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings) - _createSettingBoxCompactModeButton(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings) \ No newline at end of file + _createSettingBoxCompactModeButton(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings, view_variable=view_variable) \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py index dd01d49c..62e8088d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py @@ -1,7 +1,7 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel -def _createSettingBoxContainer(config_window, settings, setting_box_container_settings): +def _createSettingBoxContainer(config_window, settings, view_variable, setting_box_container_settings): def createSectionTitle(container_widget, section_title): @@ -56,5 +56,10 @@ def _createSettingBoxContainer(config_window, settings, setting_box_container_se setting_box_and_section_title_wrapper.grid(row=setting_box_row, column=0, sticky="ew", padx=0, pady=(SB__TOP_PADY, SB__BOTTOM_PADY)) if setting_box_setting["setting_box"] is not None: - setting_box_setting["setting_box"](setting_box_wrapper=setting_box_wrapper, config_window=config_window, settings=settings) + setting_box_setting["setting_box"]( + setting_box_wrapper=setting_box_wrapper, + config_window=config_window, + settings=settings, + view_variable=view_variable, + ) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index 2c47507f..32c1156e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -13,7 +13,7 @@ from .setting_box_containers.setting_box_advanced_settings import createSettingB from .setting_box_containers.setting_box_translation import createSettingBox_Translation -def createSideMenuAndSettingsBoxContainers(config_window, settings): +def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variable): # Main container config_window.main_bg_container = CTkFrame(config_window, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) @@ -119,6 +119,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): _addConfigSideMenuItem( config_window=config_window, settings=settings, + # view_variable=view_variable, side_menu_settings=sm_and_sbc_setting, side_menu_row=side_menu_row, all_side_menu_tab_attr_name=all_side_menu_tab_attr_name, @@ -129,6 +130,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings): _createSettingBoxContainer( config_window=config_window, settings=settings, + view_variable=view_variable, setting_box_container_settings=sm_and_sbc_setting["setting_box_container_settings"], ) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py index 41c57907..c66d1e86 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py @@ -2,26 +2,26 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator -def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settings): +def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settings, view_variable): sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxEntry = sbg.createSettingBoxEntry def entry_ip_address_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_OSC_IP_ADDRESS, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_OSC_IP_ADDRESS, value) def entry_port_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_OSC_PORT, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_OSC_PORT, value) row=0 config_window.sb__ip_address = createSettingBoxEntry( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_OSC_IP_ADDRESS, - for_var_desc_text=config_window.view_variable.VAR_DESC_OSC_IP_ADDRESS, + for_var_label_text=view_variable.VAR_LABEL_OSC_IP_ADDRESS, + for_var_desc_text=view_variable.VAR_DESC_OSC_IP_ADDRESS, entry_attr_name="sb__entry_ip_address", entry_width=settings.uism.SB__ENTRY_WIDTH_150, entry_bind__Any_KeyRelease=lambda value: entry_ip_address_callback(value), - entry_textvariable=config_window.view_variable.VAR_OSC_IP_ADDRESS, + entry_textvariable=view_variable.VAR_OSC_IP_ADDRESS, ) config_window.sb__ip_address.grid(row=row) row+=1 @@ -29,12 +29,12 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin config_window.sb__port = createSettingBoxEntry( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_OSC_PORT, - for_var_desc_text=config_window.view_variable.VAR_DESC_OSC_PORT, + for_var_label_text=view_variable.VAR_LABEL_OSC_PORT, + for_var_desc_text=view_variable.VAR_DESC_OSC_PORT, entry_attr_name="sb__entry_port", entry_width=settings.uism.SB__ENTRY_WIDTH_150, entry_bind__Any_KeyRelease=lambda value: entry_port_callback(value), - entry_textvariable=config_window.view_variable.VAR_OSC_PORT, + entry_textvariable=view_variable.VAR_OSC_PORT, ) config_window.sb__port.grid(row=row) row+=1 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index 211f7bf3..80ace904 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -2,7 +2,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator -def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): +def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, view_variable): sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxSlider = sbg.createSettingBoxSlider @@ -10,30 +10,30 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): # 関数名は変えるかもしれない。 # テーマ変更、フォント変更時、 Widget再生成か再起動かは検討中 def slider_transparency_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_TRANSPARENCY, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_TRANSPARENCY, value) def optionmenu_appearance_theme_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_APPEARANCE, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_APPEARANCE, value) def optionmenu_ui_scaling_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_UI_SCALING, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_UI_SCALING, value) def optionmenu_font_family_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_FONT_FAMILY, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_FONT_FAMILY, value) def optionmenu_ui_language_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_UI_LANGUAGE, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_UI_LANGUAGE, value) row=0 config_window.sb__transparency = createSettingBoxSlider( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_TRANSPARENCY, - for_var_desc_text=config_window.view_variable.VAR_DESC_TRANSPARENCY, + for_var_label_text=view_variable.VAR_LABEL_TRANSPARENCY, + for_var_desc_text=view_variable.VAR_DESC_TRANSPARENCY, slider_attr_name="sb__transparency_slider", - slider_range=config_window.view_variable.SLIDER_RANGE_TRANSPARENCY, + slider_range=view_variable.SLIDER_RANGE_TRANSPARENCY, command=lambda value: slider_transparency_callback(value), - variable=config_window.view_variable.VAR_TRANSPARENCY, + variable=view_variable.VAR_TRANSPARENCY, ) config_window.sb__transparency.grid(row=row) row+=1 @@ -41,13 +41,13 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): config_window.sb__appearance_theme = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_APPEARANCE_THEME, - for_var_desc_text=config_window.view_variable.VAR_DESC_APPEARANCE_THEME, + for_var_label_text=view_variable.VAR_LABEL_APPEARANCE_THEME, + for_var_desc_text=view_variable.VAR_DESC_APPEARANCE_THEME, optionmenu_attr_name="sb__optionmenu_appearance_theme", dropdown_menu_attr_name="sb__dropdown_appearance_theme", - dropdown_menu_values=config_window.view_variable.LIST_APPEARANCE_THEME, + dropdown_menu_values=view_variable.LIST_APPEARANCE_THEME, command=lambda value: optionmenu_appearance_theme_callback(value), - variable=config_window.view_variable.VAR_APPEARANCE_THEME, + variable=view_variable.VAR_APPEARANCE_THEME, ) config_window.sb__appearance_theme.grid(row=row) row+=1 @@ -56,13 +56,13 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): config_window.sb__ui_scaling = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_UI_SCALING, - for_var_desc_text=config_window.view_variable.VAR_DESC_UI_SCALING, + for_var_label_text=view_variable.VAR_LABEL_UI_SCALING, + for_var_desc_text=view_variable.VAR_DESC_UI_SCALING, optionmenu_attr_name="sb__optionmenu_ui_scaling", dropdown_menu_attr_name="sb__dropdown_ui_scaling", - dropdown_menu_values=config_window.view_variable.LIST_UI_SCALING, + dropdown_menu_values=view_variable.LIST_UI_SCALING, command=lambda value: optionmenu_ui_scaling_callback(value), - variable=config_window.view_variable.VAR_UI_SCALING, + variable=view_variable.VAR_UI_SCALING, ) config_window.sb__ui_scaling.grid(row=row) row+=1 @@ -70,12 +70,12 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): config_window.sb__font_family = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_FONT_FAMILY, - for_var_desc_text=config_window.view_variable.VAR_DESC_FONT_FAMILY, + for_var_label_text=view_variable.VAR_LABEL_FONT_FAMILY, + for_var_desc_text=view_variable.VAR_DESC_FONT_FAMILY, optionmenu_attr_name="sb__optionmenu_font_family", - dropdown_menu_values=config_window.view_variable.LIST_FONT_FAMILY, + dropdown_menu_values=view_variable.LIST_FONT_FAMILY, command=lambda value: optionmenu_font_family_callback(value), - variable=config_window.view_variable.VAR_FONT_FAMILY, + variable=view_variable.VAR_FONT_FAMILY, ) config_window.sb__font_family.grid(row=row) row+=1 @@ -83,13 +83,13 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings): config_window.sb__ui_language = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_UI_LANGUAGE, - for_var_desc_text=config_window.view_variable.VAR_DESC_UI_LANGUAGE, + for_var_label_text=view_variable.VAR_LABEL_UI_LANGUAGE, + for_var_desc_text=view_variable.VAR_DESC_UI_LANGUAGE, optionmenu_attr_name="sb__optionmenu_ui_language", dropdown_menu_attr_name="sb__dropdown_ui_language", - dropdown_menu_values=config_window.view_variable.LIST_UI_LANGUAGE, + dropdown_menu_values=view_variable.LIST_UI_LANGUAGE, command=lambda value: optionmenu_ui_language_callback(value), - variable=config_window.view_variable.VAR_UI_LANGUAGE, + variable=view_variable.VAR_UI_LANGUAGE, ) config_window.sb__ui_language.grid(row=row) row+=1 \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index b3c2b81e..dd730e92 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -2,7 +2,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator -def createSettingBox_Others(setting_box_wrapper, config_window, settings): +def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_variable): sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxCheckbox = sbg.createSettingBoxCheckbox createSettingBoxEntry = sbg.createSettingBoxEntry @@ -10,23 +10,23 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings): # 元 checkbox_auto_clear_chatbox_callback def checkbox_auto_clear_message_box_callback(checkbox_box_widget): - callFunctionIfCallable(config_window.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX, checkbox_box_widget.get()) + callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX, checkbox_box_widget.get()) def checkbox_notice_xsoverlay_callback(checkbox_box_widget): - callFunctionIfCallable(config_window.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, checkbox_box_widget.get()) + callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, checkbox_box_widget.get()) def entry_message_format_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, value) row=0 config_window.sb__auto_clear_message_box = createSettingBoxCheckbox( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_ENABLE_AUTO_CLEAR_MESSAGE_BOX, - for_var_desc_text=config_window.view_variable.VAR_DESC_ENABLE_AUTO_CLEAR_MESSAGE_BOX, + for_var_label_text=view_variable.VAR_LABEL_ENABLE_AUTO_CLEAR_MESSAGE_BOX, + for_var_desc_text=view_variable.VAR_DESC_ENABLE_AUTO_CLEAR_MESSAGE_BOX, checkbox_attr_name="sb__checkbox_auto_clear_message_box", command=lambda: checkbox_auto_clear_message_box_callback(config_window.sb__checkbox_auto_clear_message_box), - variable=config_window.view_variable.VAR_ENABLE_AUTO_CLEAR_MESSAGE_BOX, + variable=view_variable.VAR_ENABLE_AUTO_CLEAR_MESSAGE_BOX, ) config_window.sb__auto_clear_message_box.grid(row=row) row+=1 @@ -34,11 +34,11 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings): config_window.sb__notice_xsoverlay = createSettingBoxCheckbox( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_ENABLE_NOTICE_XSOVERLAY, - for_var_desc_text=config_window.view_variable.VAR_DESC_ENABLE_NOTICE_XSOVERLAY, + for_var_label_text=view_variable.VAR_LABEL_ENABLE_NOTICE_XSOVERLAY, + for_var_desc_text=view_variable.VAR_DESC_ENABLE_NOTICE_XSOVERLAY, checkbox_attr_name="sb__checkbox_notice_xsoverlay", command=lambda: checkbox_notice_xsoverlay_callback(config_window.sb__checkbox_notice_xsoverlay), - variable=config_window.view_variable.VAR_ENABLE_NOTICE_XSOVERLAY, + variable=view_variable.VAR_ENABLE_NOTICE_XSOVERLAY, ) config_window.sb__notice_xsoverlay.grid(row=row) row+=1 @@ -46,12 +46,12 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings): config_window.sb__message_format = createSettingBoxEntry( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_MESSAGE_FORMAT, - for_var_desc_text=config_window.view_variable.VAR_DESC_MESSAGE_FORMAT, + for_var_label_text=view_variable.VAR_LABEL_MESSAGE_FORMAT, + for_var_desc_text=view_variable.VAR_DESC_MESSAGE_FORMAT, entry_attr_name="sb__entry_message_format", entry_width=settings.uism.SB__ENTRY_WIDTH_250, entry_bind__Any_KeyRelease=lambda value: entry_message_format_callback(value), - entry_textvariable=config_window.view_variable.VAR_MESSAGE_FORMAT, + entry_textvariable=view_variable.VAR_MESSAGE_FORMAT, ) config_window.sb__message_format.grid(row=row) row+=1 \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 583d192f..3a53b3c0 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -4,7 +4,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator -def createSettingBox_Mic(setting_box_wrapper, config_window, settings): +def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_variable): sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxCheckbox = sbg.createSettingBoxCheckbox @@ -36,54 +36,55 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): passive_button_wrapper_widget.grid() def optionmenu_mic_host_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_MIC_HOST, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_HOST, value) def optionmenu_input_mic_device_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_MIC_DEVICE, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_DEVICE, value) def slider_input_mic_energy_threshold_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_MIC_ENERGY_THRESHOLD, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_ENERGY_THRESHOLD, value) def checkbox_input_mic_dynamic_energy_threshold_callback(checkbox_box_widget): - callFunctionIfCallable(config_window.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD, checkbox_box_widget.get()) + callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD, checkbox_box_widget.get()) def entry_input_mic_record_timeout_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_MIC_RECORD_TIMEOUT, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_RECORD_TIMEOUT, value) def entry_input_mic_phrase_timeout_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_MIC_PHRASE_TIMEOUT, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_PHRASE_TIMEOUT, value) def entry_input_mic_max_phrases_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_MIC_MAX_PHRASES, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_MAX_PHRASES, value) def entry_input_mic_word_filters_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_MIC_WORD_FILTER, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_WORD_FILTER, value) row=0 # Mic Host と Mic Device は一つの項目として引っ付ける予定 config_window.sb__mic_host = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_HOST, - for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_HOST, + for_var_label_text=view_variable.VAR_LABEL_MIC_HOST, + for_var_desc_text=view_variable.VAR_DESC_MIC_HOST, optionmenu_attr_name="sb__optionmenu_mic_host", - dropdown_menu_values=config_window.view_variable.LIST_MIC_HOST, + dropdown_menu_attr_name="sb__dropdown_mic_host", + dropdown_menu_values=view_variable.LIST_MIC_HOST, command=lambda value: optionmenu_mic_host_callback(value), - variable=config_window.view_variable.VAR_MIC_HOST, + variable=view_variable.VAR_MIC_HOST, ) config_window.sb__mic_host.grid(row=row) row+=1 config_window.sb__mic_device = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_DEVICE, - for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_DEVICE, + for_var_label_text=view_variable.VAR_LABEL_MIC_DEVICE, + for_var_desc_text=view_variable.VAR_DESC_MIC_DEVICE, optionmenu_attr_name="sb__optionmenu_mic_device", dropdown_menu_attr_name="sb__dropdown_mic_device", - dropdown_menu_values=config_window.view_variable.LIST_MIC_DEVICE, + dropdown_menu_values=view_variable.LIST_MIC_DEVICE, command=lambda value: optionmenu_input_mic_device_callback(value), - variable=config_window.view_variable.VAR_MIC_DEVICE, + variable=view_variable.VAR_MIC_DEVICE, ) config_window.sb__mic_device.grid(row=row) row+=1 @@ -91,15 +92,15 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): config_window.sb__mic_energy_threshold = createSettingBoxProgressbarXSlider( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_ENERGY_THRESHOLD, - for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_ENERGY_THRESHOLD, + for_var_label_text=view_variable.VAR_LABEL_MIC_ENERGY_THRESHOLD, + for_var_desc_text=view_variable.VAR_DESC_MIC_ENERGY_THRESHOLD, command=slider_input_mic_energy_threshold_callback, - variable=config_window.view_variable.VAR_MIC_ENERGY_THRESHOLD, + variable=view_variable.VAR_MIC_ENERGY_THRESHOLD, entry_attr_name="sb__progressbar_x_slider__entry_mic_energy_threshold", slider_attr_name="progressbar_x_slider__slider_mic_energy_threshold", - slider_range=config_window.view_variable.SLIDER_RANGE_MIC_ENERGY_THRESHOLD, + slider_range=view_variable.SLIDER_RANGE_MIC_ENERGY_THRESHOLD, progressbar_attr_name="sb__progressbar_x_slider__progressbar_mic_energy_threshold", @@ -125,11 +126,11 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): # Mic Dynamic Energy Thresholdも上に引っ付ける予定 config_window.sb__mic_dynamic_energy_threshold = createSettingBoxCheckbox( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD, - for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD, + for_var_label_text=view_variable.VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD, + for_var_desc_text=view_variable.VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD, checkbox_attr_name="sb__checkbox_mic_dynamic_energy_threshold", command=lambda: checkbox_input_mic_dynamic_energy_threshold_callback(config_window.sb__checkbox_mic_dynamic_energy_threshold), - variable=config_window.view_variable.VAR_MIC_DYNAMIC_ENERGY_THRESHOLD + variable=view_variable.VAR_MIC_DYNAMIC_ENERGY_THRESHOLD ) config_window.sb__mic_dynamic_energy_threshold.grid(row=row) row+=1 @@ -138,36 +139,36 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): # 以下3つも一つの項目にまとめるかもしれない config_window.sb__mic_record_timeout = createSettingBoxEntry( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_RECORD_TIMEOUT, - for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_RECORD_TIMEOUT, + for_var_label_text=view_variable.VAR_LABEL_MIC_RECORD_TIMEOUT, + for_var_desc_text=view_variable.VAR_DESC_MIC_RECORD_TIMEOUT, entry_attr_name="sb__entry_mic_record_timeout", entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_record_timeout_callback(value), - entry_textvariable=config_window.view_variable.VAR_MIC_RECORD_TIMEOUT, + entry_textvariable=view_variable.VAR_MIC_RECORD_TIMEOUT, ) config_window.sb__mic_record_timeout.grid(row=row) row+=1 config_window.sb__mic_phrase_timeout = createSettingBoxEntry( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_PHRASE_TIMEOUT, - for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_PHRASE_TIMEOUT, + for_var_label_text=view_variable.VAR_LABEL_MIC_PHRASE_TIMEOUT, + for_var_desc_text=view_variable.VAR_DESC_MIC_PHRASE_TIMEOUT, entry_attr_name="sb__entry_mic_phrase_timeout", entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_phrase_timeout_callback(value), - entry_textvariable=config_window.view_variable.VAR_MIC_PHRASE_TIMEOUT, + entry_textvariable=view_variable.VAR_MIC_PHRASE_TIMEOUT, ) config_window.sb__mic_phrase_timeout.grid(row=row) row+=1 config_window.sb__mic_max_phrases = createSettingBoxEntry( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_MAX_PHRASES, - for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_MAX_PHRASES, + for_var_label_text=view_variable.VAR_LABEL_MIC_MAX_PHRASES, + for_var_desc_text=view_variable.VAR_DESC_MIC_MAX_PHRASES, entry_attr_name="sb__entry_mic_max_phrases", entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_max_phrases_callback(value), - entry_textvariable=config_window.view_variable.VAR_MIC_MAX_PHRASES, + entry_textvariable=view_variable.VAR_MIC_MAX_PHRASES, ) config_window.sb__mic_max_phrases.grid(row=row) row+=1 @@ -176,12 +177,12 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings): config_window.sb__mic_word_filter = createSettingBoxEntry( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_MIC_WORD_FILTER, - for_var_desc_text=config_window.view_variable.VAR_DESC_MIC_WORD_FILTER, + for_var_label_text=view_variable.VAR_LABEL_MIC_WORD_FILTER, + for_var_desc_text=view_variable.VAR_DESC_MIC_WORD_FILTER, entry_attr_name="sb__entry_mic_word_filter", entry_width=settings.uism.SB__ENTRY_WIDTH_300, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_word_filters_callback(value), - entry_textvariable=config_window.view_variable.VAR_MIC_WORD_FILTER, + entry_textvariable=view_variable.VAR_MIC_WORD_FILTER, ) config_window.sb__mic_word_filter.grid(row=row) row+=1 \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 236e4c58..a27a7f5c 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -4,7 +4,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator -def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): +def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_variable): sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxCheckbox = sbg.createSettingBoxCheckbox @@ -36,36 +36,36 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): passive_button_wrapper_widget.grid() def optionmenu_input_speaker_device_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_SPEAKER_DEVICE, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_DEVICE, value) def slider_input_speaker_energy_threshold_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD, value) def checkbox_input_speaker_dynamic_energy_threshold_callback(checkbox_box_widget): - callFunctionIfCallable(config_window.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, checkbox_box_widget.get()) + callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, checkbox_box_widget.get()) def entry_input_speaker_record_timeout_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT, value) def entry_input_speaker_phrase_timeout_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT, value) def entry_input_speaker_max_phrases_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_SPEAKER_MAX_PHRASES, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_MAX_PHRASES, value) row=0 config_window.sb__speaker_device = createSettingBoxDropdownMenu( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_SPEAKER_DEVICE, - for_var_desc_text=config_window.view_variable.VAR_DESC_SPEAKER_DEVICE, + for_var_label_text=view_variable.VAR_LABEL_SPEAKER_DEVICE, + for_var_desc_text=view_variable.VAR_DESC_SPEAKER_DEVICE, optionmenu_attr_name="sb__optionmenu_speaker_device", dropdown_menu_attr_name="sb__dropdown_speaker_device", - dropdown_menu_values=config_window.view_variable.LIST_SPEAKER_DEVICE, + dropdown_menu_values=view_variable.LIST_SPEAKER_DEVICE, command=lambda value: optionmenu_input_speaker_device_callback(value), - variable=config_window.view_variable.VAR_SPEAKER_DEVICE, + variable=view_variable.VAR_SPEAKER_DEVICE, ) config_window.sb__speaker_device.grid(row=row) row+=1 @@ -73,15 +73,15 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): config_window.sb__speaker_energy_threshold = createSettingBoxProgressbarXSlider( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_SPEAKER_ENERGY_THRESHOLD, - for_var_desc_text=config_window.view_variable.VAR_DESC_SPEAKER_ENERGY_THRESHOLD, + for_var_label_text=view_variable.VAR_LABEL_SPEAKER_ENERGY_THRESHOLD, + for_var_desc_text=view_variable.VAR_DESC_SPEAKER_ENERGY_THRESHOLD, command=slider_input_speaker_energy_threshold_callback, - variable=config_window.view_variable.VAR_SPEAKER_ENERGY_THRESHOLD, + variable=view_variable.VAR_SPEAKER_ENERGY_THRESHOLD, entry_attr_name="sb__progressbar_x_slider__entry_speaker_energy_threshold", slider_attr_name="progressbar_x_slider__slider_speaker_energy_threshold", - slider_range=config_window.view_variable.SLIDER_RANGE_SPEAKER_ENERGY_THRESHOLD, + slider_range=view_variable.SLIDER_RANGE_SPEAKER_ENERGY_THRESHOLD, progressbar_attr_name="sb__progressbar_x_slider__progressbar_speaker_energy_threshold", @@ -107,11 +107,11 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): # Speaker Dynamic Energy Thresholdも上に引っ付ける予定 config_window.sb__speaker_dynamic_energy_threshold = createSettingBoxCheckbox( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, - for_var_desc_text=config_window.view_variable.VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, + for_var_label_text=view_variable.VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, + for_var_desc_text=view_variable.VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, checkbox_attr_name="sb__checkbox_speaker_dynamic_energy_threshold", command=lambda: checkbox_input_speaker_dynamic_energy_threshold_callback(config_window.sb__checkbox_speaker_dynamic_energy_threshold), - variable=config_window.view_variable.VAR_MIC_DYNAMIC_ENERGY_THRESHOLD, + variable=view_variable.VAR_MIC_DYNAMIC_ENERGY_THRESHOLD, ) config_window.sb__speaker_dynamic_energy_threshold.grid(row=row) row+=1 @@ -120,36 +120,36 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings): # 以下3つも一つの項目にまとめるかもしれない config_window.sb__speaker_record_timeout = createSettingBoxEntry( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_SPEAKER_RECORD_TIMEOUT, - for_var_desc_text=config_window.view_variable.VAR_DESC_SPEAKER_RECORD_TIMEOUT, + for_var_label_text=view_variable.VAR_LABEL_SPEAKER_RECORD_TIMEOUT, + for_var_desc_text=view_variable.VAR_DESC_SPEAKER_RECORD_TIMEOUT, entry_attr_name="sb__entry_speaker_record_timeout", entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_record_timeout_callback(value), - entry_textvariable=config_window.view_variable.VAR_SPEAKER_RECORD_TIMEOUT, + entry_textvariable=view_variable.VAR_SPEAKER_RECORD_TIMEOUT, ) config_window.sb__speaker_record_timeout.grid(row=row) row+=1 config_window.sb__speaker_phrase_timeout = createSettingBoxEntry( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_SPEAKER_PHRASE_TIMEOUT, - for_var_desc_text=config_window.view_variable.VAR_DESC_SPEAKER_PHRASE_TIMEOUT, + for_var_label_text=view_variable.VAR_LABEL_SPEAKER_PHRASE_TIMEOUT, + for_var_desc_text=view_variable.VAR_DESC_SPEAKER_PHRASE_TIMEOUT, entry_attr_name="sb__entry_speaker_phrase_timeout", entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_phrase_timeout_callback(value), - entry_textvariable=config_window.view_variable.VAR_SPEAKER_PHRASE_TIMEOUT, + entry_textvariable=view_variable.VAR_SPEAKER_PHRASE_TIMEOUT, ) config_window.sb__speaker_phrase_timeout.grid(row=row) row+=1 config_window.sb__speaker_max_phrases = createSettingBoxEntry( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_SPEAKER_MAX_PHRASES, - for_var_desc_text=config_window.view_variable.VAR_DESC_SPEAKER_MAX_PHRASES, + for_var_label_text=view_variable.VAR_LABEL_SPEAKER_MAX_PHRASES, + for_var_desc_text=view_variable.VAR_DESC_SPEAKER_MAX_PHRASES, entry_attr_name="sb__entry_speaker_max_phrases", entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_max_phrases_callback(value), - entry_textvariable=config_window.view_variable.VAR_SPEAKER_MAX_PHRASES, + entry_textvariable=view_variable.VAR_SPEAKER_MAX_PHRASES, ) config_window.sb__speaker_max_phrases.grid(row=row) row+=1 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index 1bdaa8b0..7096bbc2 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -2,24 +2,24 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator -def createSettingBox_Translation(setting_box_wrapper, config_window, settings): +def createSettingBox_Translation(setting_box_wrapper, config_window, settings, view_variable): sbg = _SettingBoxGenerator(config_window, settings) createSettingBoxEntry = sbg.createSettingBoxEntry def deepl_authkey_callback(value): - callFunctionIfCallable(config_window.CALLBACK_SET_DEEPL_AUTHKEY, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_DEEPL_AUTHKEY, value) row=0 config_window.sb__deepl_authkey = createSettingBoxEntry( parent_widget=setting_box_wrapper, - for_var_label_text=config_window.view_variable.VAR_LABEL_DEEPL_AUTH_KEY, - for_var_desc_text=config_window.view_variable.VAR_DESC_DEEPL_AUTH_KEY, + for_var_label_text=view_variable.VAR_LABEL_DEEPL_AUTH_KEY, + for_var_desc_text=view_variable.VAR_DESC_DEEPL_AUTH_KEY, entry_attr_name="sb__deepl_authkey", entry_width=settings.uism.SB__ENTRY_WIDTH_300, entry_bind__Any_KeyRelease=lambda value: deepl_authkey_callback(value), - entry_textvariable=config_window.view_variable.VAR_DEEPL_AUTH_KEY, + entry_textvariable=view_variable.VAR_DEEPL_AUTH_KEY, ) config_window.sb__deepl_authkey.grid(row=row) row+=1 \ No newline at end of file From ca669f9fec280e60db6e5a6b9ce50e0ac63801e3 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 4 Sep 2023 23:08:28 +0900 Subject: [PATCH 091/355] =?UTF-8?q?[bugfix]=20view.py=20OSC=5FIP=5FADDRESS?= =?UTF-8?q?=E3=81=AB=E5=85=A5=E3=82=8C=E3=82=8BTkinter=E5=A4=89=E6=95=B0?= =?UTF-8?q?=E3=81=AE=E5=9E=8B=E6=8C=87=E5=AE=9A=E3=83=9F=E3=82=B9=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=20int=E5=9E=8B=E3=81=8B=E3=82=89str=E5=9E=8B=E3=81=AB?= =?UTF-8?q?=E3=80=82=20=E4=BB=8A=E3=81=BE=E3=81=A7=E3=81=AFint=E5=9E=8B?= =?UTF-8?q?=E3=81=A7=E6=8C=87=E5=AE=9A=E3=81=97=E3=81=A6=E3=81=84=E3=81=9F?= =?UTF-8?q?=E3=81=AE=E3=81=A7=E3=80=81127.0.0.1=E3=81=AA=E3=81=A9=E3=81=AF?= =?UTF-8?q?float=E3=81=A8=E3=81=97=E3=81=A6=E8=AA=8D=E8=AD=98=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=A6=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=8C=E3=81=A7?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 4 ++-- view.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index 4a36015e..a83154f3 100644 --- a/main.py +++ b/main.py @@ -350,8 +350,8 @@ def callbackSetMessageFormat(value): # Advanced Settings Tab def callbackSetOscIpAddress(value): - print("callbackSetOscIpAddress", value) - config.OSC_IP_ADDRESS = value + print("callbackSetOscIpAddress", str(value)) + config.OSC_IP_ADDRESS = str(value) def callbackSetOscPort(value): print("callbackSetOscPort", int(value)) diff --git a/view.py b/view.py index 37b362ad..ccd98abd 100644 --- a/view.py +++ b/view.py @@ -202,7 +202,7 @@ class View(): VAR_LABEL_OSC_IP_ADDRESS=StringVar(value="OSC IP Address"), VAR_DESC_OSC_IP_ADDRESS=StringVar(value="(Default: 127.0.0.1)"), CALLBACK_SET_OSC_IP_ADDRESS=None, - VAR_OSC_IP_ADDRESS=IntVar(value=config.OSC_IP_ADDRESS), + VAR_OSC_IP_ADDRESS=StringVar(value=config.OSC_IP_ADDRESS), VAR_LABEL_OSC_PORT=StringVar(value="OSC Port"), VAR_DESC_OSC_PORT=StringVar(value="(Default: 9000)"), From 8a91cffcf46d832ea7615bf11d3e04d3fad2d729 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 4 Sep 2023 23:43:22 +0900 Subject: [PATCH 092/355] =?UTF-8?q?[bugifx]=20Main=20Window:=20view=5Fvari?= =?UTF-8?q?able(view.py=E5=A4=89=E6=95=B0)=E3=81=AE=E5=8F=82=E7=85=A7?= =?UTF-8?q?=E3=81=AE=E4=BB=95=E6=96=B9=E3=81=8C=E3=81=8A=E3=81=8B=E3=81=97?= =?UTF-8?q?=E3=81=8F=E3=80=81=E6=84=8F=E5=9B=B3=E3=81=97=E3=81=9F=E5=80=A4?= =?UTF-8?q?=E3=81=AE=E4=BF=9D=E5=AD=98=E3=81=A8=E5=8F=82=E7=85=A7=E3=81=8C?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=8B=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_changeMainWindowWidgetsStatus.py | 10 +++++----- .../main_window/createMainWindowWidgets.py | 6 +++--- .../_create_sidebar/createSidebarFeatures.py | 18 +++++++++--------- .../createSidebarLanguagesSettings.py | 16 ++++++++-------- .../widgets/create_minimize_sidebar_button.py | 8 ++++---- vrct_gui/main_window/widgets/create_sidebar.py | 8 ++++---- vrct_gui/vrct_gui.py | 11 ++++++----- 7 files changed, 39 insertions(+), 38 deletions(-) diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index af8b51d8..4fed6db0 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -3,7 +3,7 @@ from customtkinter import CTkImage from .ui_utils import getImageFileFromUiUtils -def _changeMainWindowWidgetsStatus(vrct_gui, settings, status, target_names): +def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, target_names): COMPACT_MODE_ICON_SIZE_TUPLES = (settings.COMPACT_MODE_ICON_SIZE, settings.COMPACT_MODE_ICON_SIZE) if target_names == "All": @@ -85,7 +85,7 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, status, target_names): vrct_gui.sls__container_title.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) vrct_gui.sls__title_text_your_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) vrct_gui.sls__title_text_target_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) - if vrct_gui.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: + if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE) vrct_gui.sls__optionmenu_your_language.configure(state="disabled") vrct_gui.sls__optionmenu_target_language.configure(state="disabled") @@ -94,7 +94,7 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, status, target_names): vrct_gui.sls__container_title.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) vrct_gui.sls__title_text_your_language.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) vrct_gui.sls__title_text_target_language.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) - if vrct_gui.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: + if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) vrct_gui.sls__optionmenu_your_language.configure(state="normal") @@ -120,7 +120,7 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, status, target_names): vrct_gui.minimize_sidebar_button_container.configure(cursor="") - if vrct_gui.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: + if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT_DISABLED).rotate(180), size=LOGO_SIZE) else: image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT_DISABLED), size=LOGO_SIZE) @@ -128,7 +128,7 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, status, target_names): elif status == "normal": vrct_gui.minimize_sidebar_button_container.configure(cursor="hand2") - if vrct_gui.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: + if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT).rotate(180), size=LOGO_SIZE) else: image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT), size=LOGO_SIZE) diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 08687539..9a33ef0c 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -5,7 +5,7 @@ from customtkinter import CTkFrame from ..ui_utils import createButtonWithImage, getImagePath -def createMainWindowWidgets(vrct_gui, settings): +def createMainWindowWidgets(vrct_gui, settings, view_variable): vrct_gui.protocol("WM_DELETE_WINDOW", vrct_gui.quitVRCT) @@ -56,9 +56,9 @@ def createMainWindowWidgets(vrct_gui, settings): ) vrct_gui.help_and_info_button_container.grid(row=0, column=3, padx=settings.uism.HELP_AND_INFO_BUTTON_PADX, pady=settings.uism.HELP_AND_INFO_BUTTON_PADY, sticky="e") - createSidebar(settings, vrct_gui) + createSidebar(settings, vrct_gui, view_variable) - createMinimizeSidebarButton(settings, vrct_gui) + createMinimizeSidebarButton(settings, vrct_gui, view_variable) createTextbox(settings, vrct_gui) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py index 7547746e..aec073a7 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py @@ -5,7 +5,7 @@ from ....ui_utils import getImageFileFromUiUtils, openImageKeepAspectRatio, reta from utils import callFunctionIfCallable -def createSidebarFeatures(settings, main_window): +def createSidebarFeatures(settings, main_window, view_variable): def toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, mark): mark.place(relx=0.85) if is_turned_on else mark.place(relx=-1) @@ -13,22 +13,22 @@ def createSidebarFeatures(settings, main_window): def toggleTranslationFeature(): is_turned_on = main_window.translation_switch_box.get() - callFunctionIfCallable(main_window.CALLBACK_TOGGLE_TRANSLATION, is_turned_on) + callFunctionIfCallable(view_variable.CALLBACK_TOGGLE_TRANSLATION, is_turned_on) toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.translation_selected_mark) def toggleTranscriptionSendFeature(): is_turned_on = main_window.transcription_send_switch_box.get() - callFunctionIfCallable(main_window.CALLBACK_TOGGLE_TRANSCRIPTION_SEND, is_turned_on) + callFunctionIfCallable(view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND, is_turned_on) toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_send_selected_mark) def toggleTranscriptionReceiveFeature(): is_turned_on = main_window.transcription_receive_switch_box.get() - callFunctionIfCallable(main_window.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE, is_turned_on) + callFunctionIfCallable(view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE, is_turned_on) toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_receive_selected_mark) def toggleForegroundFeature(): is_turned_on = main_window.foreground_switch_box.get() - callFunctionIfCallable(main_window.CALLBACK_TOGGLE_FOREGROUND, is_turned_on) + callFunctionIfCallable(view_variable.CALLBACK_TOGGLE_FOREGROUND, is_turned_on) toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.foreground_selected_mark) @@ -118,7 +118,7 @@ def createSidebarFeatures(settings, main_window): "compact_mode_icon_attr_name": "translation_compact_mode_icon", "compact_mode_frame_attr_name": "compact_mode_translation_frame", "selected_mark_attr_name": "translation_selected_mark", - "var_label_text": main_window.view_variable.VAR_LABEL_TRANSLATION, + "var_label_text": view_variable.VAR_LABEL_TRANSLATION, "icon_file_name": settings.image_filename.TRANSLATION_ICON, }, { @@ -130,7 +130,7 @@ def createSidebarFeatures(settings, main_window): "compact_mode_icon_attr_name": "transcription_send_compact_mode_icon", "compact_mode_frame_attr_name": "compact_mode_transcription_send_frame", "selected_mark_attr_name": "transcription_send_selected_mark", - "var_label_text": main_window.view_variable.VAR_LABEL_TRANSCRIPTION_SEND, + "var_label_text": view_variable.VAR_LABEL_TRANSCRIPTION_SEND, "icon_file_name": settings.image_filename.MIC_ICON, }, { @@ -142,7 +142,7 @@ def createSidebarFeatures(settings, main_window): "compact_mode_icon_attr_name": "transcription_receive_compact_mode_icon", "compact_mode_frame_attr_name": "compact_mode_transcription_receive_frame", "selected_mark_attr_name": "transcription_receive_selected_mark", - "var_label_text": main_window.view_variable.VAR_LABEL_TRANSCRIPTION_RECEIVE, + "var_label_text": view_variable.VAR_LABEL_TRANSCRIPTION_RECEIVE, "icon_file_name": settings.image_filename.HEADPHONES_ICON, }, { @@ -154,7 +154,7 @@ def createSidebarFeatures(settings, main_window): "compact_mode_icon_attr_name": "foreground_compact_mode_icon", "compact_mode_frame_attr_name": "compact_mode_foreground_frame", "selected_mark_attr_name": "foreground_selected_mark", - "var_label_text": main_window.view_variable.VAR_LABEL_FOREGROUND, + "var_label_text": view_variable.VAR_LABEL_FOREGROUND, "icon_file_name": settings.image_filename.FOREGROUND_ICON, }, ] diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index a84e8384..bdd57585 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -5,7 +5,7 @@ from ....ui_utils import getImageFileFromUiUtils, bindEnterAndLeaveColor, bindBu from utils import callFunctionIfCallable -def createSidebarLanguagesSettings(settings, main_window): +def createSidebarLanguagesSettings(settings, main_window, view_variable): def switchActiveAndPassivePresetsTabsColor(target_active_widget): @@ -28,27 +28,27 @@ def createSidebarLanguagesSettings(settings, main_window): switchActiveAndPassivePresetsTabsColor(target_active_widget) switchActiveTabAndPassiveTab(target_active_widget, main_window.current_active_preset_tab, main_window.current_active_preset_tab.passive_function, settings.ctm.SLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR) - main_window.sls__optionmenu_your_language.set(main_window.view_variable.VAR_YOUR_LANGUAGE.get()) - main_window.sls__optionmenu_target_language.set(main_window.view_variable.VAR_TARGET_LANGUAGE.get()) + main_window.sls__optionmenu_your_language.set(view_variable.VAR_YOUR_LANGUAGE.get()) + main_window.sls__optionmenu_target_language.set(view_variable.VAR_TARGET_LANGUAGE.get()) main_window.current_active_preset_tab = target_active_widget def switchToPreset1(e): print("1") - callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "1") + callFunctionIfCallable(view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "1") target_active_widget = getattr(main_window, "sls__presets_button_1") switchPresetTabFunction(target_active_widget) def switchToPreset2(e): print("2") - callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "2") + callFunctionIfCallable(view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "2") target_active_widget = getattr(main_window, "sls__presets_button_2") switchPresetTabFunction(target_active_widget) def switchToPreset3(e): print("3") - callFunctionIfCallable(main_window.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "3") + callFunctionIfCallable(view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "3") target_active_widget = getattr(main_window, "sls__presets_button_3") switchPresetTabFunction(target_active_widget) @@ -219,7 +219,7 @@ def createSidebarLanguagesSettings(settings, main_window): optionmenu_attr_name="sls__optionmenu_your_language", dropdown_menu_attr_name="sls__dropdown_menu_your_language", dropdown_menu_values=["1""2","pppp\npppp"], - variable=main_window.view_variable.VAR_YOUR_LANGUAGE + variable=view_variable.VAR_YOUR_LANGUAGE ) main_window.sls__box_your_language.grid(row=2, column=0, padx=0, pady=(settings.uism.SLS__BOX_TOP_PADY,0),sticky="ew") @@ -269,7 +269,7 @@ def createSidebarLanguagesSettings(settings, main_window): optionmenu_attr_name="sls__optionmenu_target_language", dropdown_menu_attr_name="sls__dropdown_menu_target_language", dropdown_menu_values=["1""2","pppp\npppp2"], - variable=main_window.view_variable.VAR_TARGET_LANGUAGE + variable=view_variable.VAR_TARGET_LANGUAGE ) main_window.sls__box_target_language.grid(row=4, column=0, padx=0, pady=(0,0),sticky="ew") diff --git a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py index 6cfd0ce0..da06788b 100644 --- a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py +++ b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py @@ -5,13 +5,13 @@ from ...ui_utils import getImageFileFromUiUtils, bindEnterAndLeaveColor, bindBut from utils import callFunctionIfCallable -def createMinimizeSidebarButton(settings, main_window): +def createMinimizeSidebarButton(settings, main_window, view_variable): def enableCompactMode(e): - callFunctionIfCallable(main_window.view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE, True) + callFunctionIfCallable(view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE, True) def disableCompactMode(e): - callFunctionIfCallable(main_window.view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE, False) + callFunctionIfCallable(view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE, False) @@ -28,7 +28,7 @@ def createMinimizeSidebarButton(settings, main_window): ) - if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: + if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT).rotate(180),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) bindButtonReleaseFunction([main_window.minimize_sidebar_button_container, main_window.minimize_sidebar_button], disableCompactMode) diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 967d3268..12323aad 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -2,7 +2,7 @@ from customtkinter import CTkFrame from ._create_sidebar import createSidebarFeatures, createSidebarLanguagesSettings -def createSidebar(settings, main_window): +def createSidebar(settings, main_window, view_variable): # Side Bar Container main_window.grid_rowconfigure(0, weight=1) @@ -14,14 +14,14 @@ def createSidebar(settings, main_window): main_window.sidebar_compact_mode_bg_container.grid_columnconfigure(0, weight=0, minsize=settings.uism.COMPACT_MODE_SIDEBAR_WIDTH) - createSidebarFeatures(settings, main_window) - createSidebarLanguagesSettings(settings, main_window) + createSidebarFeatures(settings, main_window, view_variable) + createSidebarLanguagesSettings(settings, main_window, view_variable) main_window.sidebar_bg_container.grid(row=0, column=0, sticky="nsew") main_window.sidebar_compact_mode_bg_container.grid(row=0, column=0, sticky="nsew") - if main_window.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE: + if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE: main_window.sidebar_bg_container.grid_remove() else: main_window.sidebar_compact_mode_bg_container.grid_remove() \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index b33d3728..8275f04a 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -21,10 +21,10 @@ class VRCT_GUI(CTk): def createGUI(self, settings, view_variable): self.settings = settings - self.view_variable = view_variable + self._view_variable = view_variable - createMainWindowWidgets(vrct_gui=self, settings=self.settings.main) - self.config_window = ConfigWindow(vrct_gui=self, settings=self.settings.config_window, view_variable=self.view_variable) + createMainWindowWidgets(vrct_gui=self, settings=self.settings.main, view_variable=self._view_variable) + self.config_window = ConfigWindow(vrct_gui=self, settings=self.settings.config_window, view_variable=self._view_variable) # self.information_window = ToplevelWindowInformation(self) def startMainLoop(self): @@ -57,6 +57,7 @@ class VRCT_GUI(CTk): _changeMainWindowWidgetsStatus( vrct_gui=self, settings=self.settings.main, + view_variable=self._view_variable, status=status, target_names=target_names, ) @@ -80,9 +81,9 @@ class VRCT_GUI(CTk): def recreateMainWindowSidebar(self): self.minimize_sidebar_button_container.destroy() - createMinimizeSidebarButton(self.settings.main, self) + createMinimizeSidebarButton(self.settings.main, self, view_variable=self._view_variable) - if self.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE: + if self._view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE: self.sidebar_bg_container.grid_remove() self.sidebar_compact_mode_bg_container.grid() else: From e1466efc51d9ead91ec8f639c5af0ae320de35ae Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 5 Sep 2023 00:54:15 +0900 Subject: [PATCH 093/355] =?UTF-8?q?[Chore]=20ui=5Fmanagers:=20=E4=B8=8D?= =?UTF-8?q?=E8=A6=81=E3=81=AAreturn=E6=96=87=E3=81=AE=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/ui_managers/ImageFilenameManager.py | 4 ++-- vrct_gui/ui_managers/UiScalingManager.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vrct_gui/ui_managers/ImageFilenameManager.py b/vrct_gui/ui_managers/ImageFilenameManager.py index 82573f37..f6a1705a 100644 --- a/vrct_gui/ui_managers/ImageFilenameManager.py +++ b/vrct_gui/ui_managers/ImageFilenameManager.py @@ -1,9 +1,9 @@ class ImageFilenameManager(): def __init__(self, theme:str ="Dark"): if theme == "Dark": - return self._createDarkModeImages() + self._createDarkModeImages() elif theme == "Light": - return self._createLightModeImages() + self._createLightModeImages() def _createDarkModeImages(self): diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index e7d244b2..0eb5c5d4 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -8,7 +8,7 @@ class UiScalingManager(): self.main = SimpleNamespace() self.config_window = SimpleNamespace() - return self._calculatedUiSizes() + self._calculatedUiSizes() From 670a651e57badef653238fed02759a3b98eae88c Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 5 Sep 2023 01:53:49 +0900 Subject: [PATCH 094/355] =?UTF-8?q?[bugfix]=20getImageFileFromUiUtils?= =?UTF-8?q?=E3=81=AA=E3=81=A9image=20file=E5=8F=96=E5=BE=97=E3=82=92imageF?= =?UTF-8?q?ileManager(=E5=85=83imageFilenameManager)=E3=81=AB=E3=81=A6?= =?UTF-8?q?=E4=B8=80=E6=8B=AC=E3=81=A7=E8=A1=8C=E3=81=86=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=80=82=20UI=E3=81=AB=E5=85=A5=E3=82=8C=E3=82=8B?= =?UTF-8?q?=E6=99=82=E3=81=AF=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E5=90=8D?= =?UTF-8?q?=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=81=9D=E3=81=AE=E3=82=82=E3=81=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 6 +- vrct_gui/_changeMainWindowWidgetsStatus.py | 34 +++++------ .../widgets/createConfigWindowTitle.py | 2 +- .../_SettingBoxGenerator.py | 14 ++--- .../createSettingBox_Mic.py | 2 +- .../createSettingBox_Speaker.py | 2 +- .../main_window/createMainWindowWidgets.py | 2 +- .../_create_sidebar/createSidebarFeatures.py | 16 +++--- .../createSidebarLanguagesSettings.py | 6 +- .../widgets/create_minimize_sidebar_button.py | 6 +- vrct_gui/ui_managers/ImageFileManager.py | 57 +++++++++++++++++++ vrct_gui/ui_managers/ImageFilenameManager.py | 54 ------------------ vrct_gui/ui_managers/__init__.py | 2 +- vrct_gui/ui_utils/ui_utils.py | 13 ++--- vrct_gui/vrct_gui.py | 2 +- 15 files changed, 110 insertions(+), 108 deletions(-) create mode 100644 vrct_gui/ui_managers/ImageFileManager.py delete mode 100644 vrct_gui/ui_managers/ImageFilenameManager.py diff --git a/view.py b/view.py index ccd98abd..12f10cdf 100644 --- a/view.py +++ b/view.py @@ -3,7 +3,7 @@ from tkinter import font as tk_font from languages import selectable_languages from customtkinter import StringVar, IntVar, BooleanVar, END as CTK_END, get_appearance_mode -from vrct_gui.ui_managers import ColorThemeManager, ImageFilenameManager, UiScalingManager +from vrct_gui.ui_managers import ColorThemeManager, ImageFileManager, UiScalingManager from vrct_gui import vrct_gui from config import config @@ -14,10 +14,10 @@ class View(): theme = get_appearance_mode() if config.APPEARANCE_THEME == "System" else config.APPEARANCE_THEME all_ctm = ColorThemeManager(theme) all_uism = UiScalingManager(config.UI_SCALING) - image_filename = ImageFilenameManager(theme) + image_file = ImageFileManager(theme) common_args = { - "image_filename": image_filename, + "image_file": image_file, "FONT_FAMILY": config.FONT_FAMILY, } diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index 4fed6db0..5e02a67c 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -18,15 +18,15 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta widget_label.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) widget_switch_box.configure(state="disabled", progress_color=settings.ctm.SF__SWITCH_BOX_DISABLE_BG_COLOR) widget_selected_mark.configure(fg_color=settings.ctm.SF__SELECTED_MARK_DISABLE_BG_COLOR) - icon_filename = disabled_icon_name + icon_file = disabled_icon_name elif status == "normal": widget_frame.configure(cursor="hand2") widget_label.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) widget_switch_box.configure(state="normal", progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR) widget_selected_mark.configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) - icon_filename = icon_name + icon_file = icon_name - image = CTkImage(getImageFileFromUiUtils(icon_filename), size=COMPACT_MODE_ICON_SIZE_TUPLES) + image = CTkImage(icon_file, size=COMPACT_MODE_ICON_SIZE_TUPLES) widget_compact_mode_icon.configure(image=image) @@ -41,8 +41,8 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta widget_switch_box=vrct_gui.translation_switch_box, widget_selected_mark=vrct_gui.translation_selected_mark, widget_compact_mode_icon=vrct_gui.translation_compact_mode_icon, - icon_name=settings.image_filename.TRANSLATION_ICON, - disabled_icon_name=settings.image_filename.TRANSLATION_ICON_DISABLED + icon_name=settings.image_file.TRANSLATION_ICON, + disabled_icon_name=settings.image_file.TRANSLATION_ICON_DISABLED ) case "transcription_send_switch": update_switch_status( @@ -51,8 +51,8 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta widget_switch_box=vrct_gui.transcription_send_switch_box, widget_selected_mark=vrct_gui.transcription_send_selected_mark, widget_compact_mode_icon=vrct_gui.transcription_send_compact_mode_icon, - icon_name=settings.image_filename.MIC_ICON, - disabled_icon_name=settings.image_filename.MIC_ICON_DISABLED + icon_name=settings.image_file.MIC_ICON, + disabled_icon_name=settings.image_file.MIC_ICON_DISABLED ) case "transcription_receive_switch": update_switch_status( @@ -61,8 +61,8 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta widget_switch_box=vrct_gui.transcription_receive_switch_box, widget_selected_mark=vrct_gui.transcription_receive_selected_mark, widget_compact_mode_icon=vrct_gui.transcription_receive_compact_mode_icon, - icon_name=settings.image_filename.HEADPHONES_ICON, - disabled_icon_name=settings.image_filename.HEADPHONES_ICON_DISABLED + icon_name=settings.image_file.HEADPHONES_ICON, + disabled_icon_name=settings.image_file.HEADPHONES_ICON_DISABLED ) case "foreground_switch": update_switch_status( @@ -71,8 +71,8 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta widget_switch_box=vrct_gui.foreground_switch_box, widget_selected_mark=vrct_gui.foreground_selected_mark, widget_compact_mode_icon=vrct_gui.foreground_compact_mode_icon, - icon_name=settings.image_filename.FOREGROUND_ICON, - disabled_icon_name=settings.image_filename.FOREGROUND_ICON_DISABLED + icon_name=settings.image_file.FOREGROUND_ICON, + disabled_icon_name=settings.image_file.FOREGROUND_ICON_DISABLED ) @@ -105,12 +105,12 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta if status == "disabled": vrct_gui.sidebar_config_button_wrapper.configure(cursor="") vrct_gui.sidebar_config_button.configure( - image=CTkImage(getImageFileFromUiUtils(settings.image_filename.CONFIGURATION_ICON_DISABLED)), + image=CTkImage((settings.image_file.CONFIGURATION_ICON_DISABLED)), ) elif status == "normal": vrct_gui.sidebar_config_button_wrapper.configure(cursor="hand2") vrct_gui.sidebar_config_button.configure( - image=CTkImage(getImageFileFromUiUtils(settings.image_filename.CONFIGURATION_ICON)), + image=CTkImage((settings.image_file.CONFIGURATION_ICON)), ) @@ -121,17 +121,17 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT_DISABLED).rotate(180), size=LOGO_SIZE) + image_file = CTkImage((settings.image_file.ARROW_LEFT_DISABLED).rotate(180), size=LOGO_SIZE) else: - image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT_DISABLED), size=LOGO_SIZE) + image_file = CTkImage((settings.image_file.ARROW_LEFT_DISABLED), size=LOGO_SIZE) vrct_gui.minimize_sidebar_button.configure(image=image_file) elif status == "normal": vrct_gui.minimize_sidebar_button_container.configure(cursor="hand2") if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT).rotate(180), size=LOGO_SIZE) + image_file = CTkImage((settings.image_file.ARROW_LEFT).rotate(180), size=LOGO_SIZE) else: - image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT), size=LOGO_SIZE) + image_file = CTkImage((settings.image_file.ARROW_LEFT), size=LOGO_SIZE) vrct_gui.minimize_sidebar_button.configure(image=image_file) diff --git a/vrct_gui/config_window/widgets/createConfigWindowTitle.py b/vrct_gui/config_window/widgets/createConfigWindowTitle.py index bbbd9606..e638f61c 100644 --- a/vrct_gui/config_window/widgets/createConfigWindowTitle.py +++ b/vrct_gui/config_window/widgets/createConfigWindowTitle.py @@ -34,6 +34,6 @@ def createConfigWindowTitle(config_window, settings): text=None, height=0, anchor="w", - image=CTkImage(getImageFileFromUiUtils(settings.image_filename.VRCT_LOGO_MARK),size=settings.uism.TOP_BAR_SIDE__CONFIG_LOGO_MARK_SIZE), + image=CTkImage(settings.image_file.VRCT_LOGO_MARK, size=settings.uism.TOP_BAR_SIDE__CONFIG_LOGO_MARK_SIZE), ) config_window.side_menu_config_window_title_logo.place(relx=0.08, rely=0.59, anchor="w") diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 3f91fa48..20b43474 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -185,7 +185,7 @@ class _SettingBoxGenerator(): progressbar_attr_name, passive_button_attr_name, passive_button_command, active_button_attr_name, active_button_command, - button_image_filename, + button_image_file, variable, slider_number_of_steps: Union[int, None] = None, @@ -276,10 +276,10 @@ class _SettingBoxGenerator(): - passive_button_wrapper = self._createPassiveButtonForProgressbarXSlider(setting_box_progressbar_x_slider_frame, BUTTON_PADDING, passive_button_command, button_image_filename) + passive_button_wrapper = self._createPassiveButtonForProgressbarXSlider(setting_box_progressbar_x_slider_frame, BUTTON_PADDING, passive_button_command, button_image_file) setattr(self.config_window, passive_button_attr_name, passive_button_wrapper) - active_button_wrapper = self._createActiveButtonForProgressbarXSlider(setting_box_progressbar_x_slider_frame, BUTTON_PADDING, active_button_command, button_image_filename) + active_button_wrapper = self._createActiveButtonForProgressbarXSlider(setting_box_progressbar_x_slider_frame, BUTTON_PADDING, active_button_command, button_image_file) setattr(self.config_window, active_button_attr_name, active_button_wrapper) passive_button_wrapper.grid() @@ -467,13 +467,13 @@ class _SettingBoxGenerator(): - def _createPassiveButtonForProgressbarXSlider(self, setting_box_progressbar_x_slider_frame, BUTTON_PADDING, button_command, button_image_filename): + def _createPassiveButtonForProgressbarXSlider(self, setting_box_progressbar_x_slider_frame, BUTTON_PADDING, button_command, button_image_file): button_wrapper = createButtonWithImage( parent_widget=setting_box_progressbar_x_slider_frame, button_fg_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR, button_enter_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_HOVERED_COLOR, button_clicked_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_CLICKED_COLOR, - button_image_filename=button_image_filename, + button_image_file=button_image_file, button_image_size=self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE, button_ipadxy=self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY, button_command=button_command, @@ -485,13 +485,13 @@ class _SettingBoxGenerator(): - def _createActiveButtonForProgressbarXSlider(self, setting_box_progressbar_x_slider_frame, BUTTON_PADDING, button_command, button_image_filename): + def _createActiveButtonForProgressbarXSlider(self, setting_box_progressbar_x_slider_frame, BUTTON_PADDING, button_command, button_image_file): button_wrapper = createButtonWithImage( parent_widget=setting_box_progressbar_x_slider_frame, button_fg_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_COLOR, button_enter_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_HOVERED_COLOR, button_clicked_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_CLICKED_COLOR, - button_image_filename=button_image_filename, + button_image_file=button_image_file, button_image_size=self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE, button_ipadxy=self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY, button_command=button_command, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 3a53b3c0..78086993 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -118,7 +118,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold, is_turned_on=False, ), - button_image_filename="mic_icon_white.png" + button_image_file=settings.image_file.MIC_ICON ) config_window.sb__mic_energy_threshold.grid(row=row) row+=1 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index a27a7f5c..27711b22 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -99,7 +99,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold, is_turned_on=False, ), - button_image_filename="headphones_icon_white.png" + button_image_file=settings.image_file.HEADPHONES_ICON ) config_window.sb__speaker_energy_threshold.grid(row=row) row+=1 diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 9a33ef0c..d55a66ed 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -48,7 +48,7 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): button_fg_color=settings.ctm.HELP_AND_INFO_BUTTON_BG_COLOR, button_enter_color=settings.ctm.HELP_AND_INFO_BUTTON_HOVERED_BG_COLOR, button_clicked_color=settings.ctm.HELP_AND_INFO_BUTTON_CLICKED_BG_COLOR, - button_image_filename=settings.image_filename.HELP_ICON, + button_image_file=settings.image_file.HELP_ICON, button_image_size=settings.uism.HELP_AND_INFO_BUTTON_SIZE, button_ipadxy=settings.uism.HELP_AND_INFO_BUTTON_IPADXY, button_command=vrct_gui.openHelpAndInfoWindow, diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py index aec073a7..eeb373d9 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py @@ -69,7 +69,7 @@ def createSidebarFeatures(settings, main_window, view_variable): - (img, width, height) = openImageKeepAspectRatio(settings.image_filename.VRCT_LOGO, settings.uism.SF__LOGO_MAX_SIZE) + (img, width, height) = openImageKeepAspectRatio(settings.image_file.VRCT_LOGO, settings.uism.SF__LOGO_MAX_SIZE) main_window.sidebar_logo = CTkLabel( main_window.sidebar_bg_container, fg_color=settings.ctm.SIDEBAR_BG_COLOR, @@ -81,7 +81,7 @@ def createSidebarFeatures(settings, main_window, view_variable): # "height-a_s" represents the adjustment in size, and the "pady+a_s/2" indicates a padding increase of 4 pixels to compensate for the reduction. a_s = settings.uism.SF__LOGO_HEIGHT_FOR_ADJUSTMENT - (img, width, height) = openImageKeepAspectRatio(settings.image_filename.VRCT_LOGO_MARK, height-a_s) + (img, width, height) = openImageKeepAspectRatio(settings.image_file.VRCT_LOGO_MARK, height-a_s) main_window.sidebar_compact_mode_logo = CTkLabel( main_window.sidebar_compact_mode_bg_container, fg_color=settings.ctm.SIDEBAR_BG_COLOR, @@ -119,7 +119,7 @@ def createSidebarFeatures(settings, main_window, view_variable): "compact_mode_frame_attr_name": "compact_mode_translation_frame", "selected_mark_attr_name": "translation_selected_mark", "var_label_text": view_variable.VAR_LABEL_TRANSLATION, - "icon_file_name": settings.image_filename.TRANSLATION_ICON, + "icon_file": settings.image_file.TRANSLATION_ICON, }, { "frame_attr_name": "transcription_send_frame", @@ -131,7 +131,7 @@ def createSidebarFeatures(settings, main_window, view_variable): "compact_mode_frame_attr_name": "compact_mode_transcription_send_frame", "selected_mark_attr_name": "transcription_send_selected_mark", "var_label_text": view_variable.VAR_LABEL_TRANSCRIPTION_SEND, - "icon_file_name": settings.image_filename.MIC_ICON, + "icon_file": settings.image_file.MIC_ICON, }, { "frame_attr_name": "transcription_receive_frame", @@ -143,7 +143,7 @@ def createSidebarFeatures(settings, main_window, view_variable): "compact_mode_frame_attr_name": "compact_mode_transcription_receive_frame", "selected_mark_attr_name": "transcription_receive_selected_mark", "var_label_text": view_variable.VAR_LABEL_TRANSCRIPTION_RECEIVE, - "icon_file_name": settings.image_filename.HEADPHONES_ICON, + "icon_file": settings.image_file.HEADPHONES_ICON, }, { "frame_attr_name": "foreground_frame", @@ -155,7 +155,7 @@ def createSidebarFeatures(settings, main_window, view_variable): "compact_mode_frame_attr_name": "compact_mode_foreground_frame", "selected_mark_attr_name": "foreground_selected_mark", "var_label_text": view_variable.VAR_LABEL_FOREGROUND, - "icon_file_name": settings.image_filename.FOREGROUND_ICON, + "icon_file": settings.image_file.FOREGROUND_ICON, }, ] @@ -172,7 +172,7 @@ def createSidebarFeatures(settings, main_window, view_variable): compact_mode_frame_attr_name = sfs["compact_mode_frame_attr_name"] selected_mark_attr_name = sfs["selected_mark_attr_name"] var_label_text = sfs["var_label_text"] - icon_file_name = sfs["icon_file_name"] + icon_file = sfs["icon_file"] frame_widget = CTkFrame(main_window.sidebar_features_container, corner_radius=0, fg_color=settings.ctm.SF__BG_COLOR, cursor="hand2", width=0, height=0) setattr(main_window, frame_attr_name, frame_widget) @@ -234,7 +234,7 @@ def createSidebarFeatures(settings, main_window, view_variable): text=None, height=0, corner_radius=0, - image=CTkImage(getImageFileFromUiUtils(icon_file_name),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)), + image=CTkImage((icon_file),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)), ) setattr(main_window, compact_mode_icon_attr_name, compact_mode_icon_widget) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index bdd57585..df698570 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -234,7 +234,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): main_window.sls__arrow_direction_box, text=None, height=0, - image=CTkImage(getImageFileFromUiUtils(settings.image_filename.NARROW_ARROW_DOWN).rotate(180),size=settings.uism.SLS__BOX_ARROWS_IMAGE_SIZE) + image=CTkImage((settings.image_file.NARROW_ARROW_DOWN).rotate(180),size=settings.uism.SLS__BOX_ARROWS_IMAGE_SIZE) ) main_window.sls__both_direction_up.grid(row=0, column=1, pady=0) @@ -253,7 +253,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): main_window.sls__arrow_direction_box, text=None, height=0, - image=CTkImage(getImageFileFromUiUtils(settings.image_filename.NARROW_ARROW_DOWN).rotate(0),size=settings.uism.SLS__BOX_ARROWS_IMAGE_SIZE) + image=CTkImage((settings.image_file.NARROW_ARROW_DOWN).rotate(0),size=settings.uism.SLS__BOX_ARROWS_IMAGE_SIZE) ) main_window.sls__both_direction_label_down.grid(row=0, column=3, pady=0) @@ -296,7 +296,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): main_window.sidebar_config_button_wrapper, text=None, height=0, - image=CTkImage(getImageFileFromUiUtils(settings.image_filename.CONFIGURATION_ICON),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)) + image=CTkImage((settings.image_file.CONFIGURATION_ICON),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)) ) main_window.sidebar_config_button.grid(row=0, column=0, padx=0, pady=settings.uism.SIDEBAR_CONFIG_BUTTON_IPADY) diff --git a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py index da06788b..250e2ac4 100644 --- a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py +++ b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py @@ -24,16 +24,16 @@ def createMinimizeSidebarButton(settings, main_window, view_variable): text=None, corner_radius=0, height=0, - image=CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) + image=CTkImage((settings.image_file.ARROW_LEFT),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) ) if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT).rotate(180),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) + image_file = CTkImage((settings.image_file.ARROW_LEFT).rotate(180),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) bindButtonReleaseFunction([main_window.minimize_sidebar_button_container, main_window.minimize_sidebar_button], disableCompactMode) else: - image_file = CTkImage(getImageFileFromUiUtils(settings.image_filename.ARROW_LEFT),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) + image_file = CTkImage((settings.image_file.ARROW_LEFT),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) bindButtonReleaseFunction([main_window.minimize_sidebar_button_container, main_window.minimize_sidebar_button], enableCompactMode) main_window.minimize_sidebar_button_container.grid_rowconfigure((0,2), weight=1) diff --git a/vrct_gui/ui_managers/ImageFileManager.py b/vrct_gui/ui_managers/ImageFileManager.py new file mode 100644 index 00000000..62dcc122 --- /dev/null +++ b/vrct_gui/ui_managers/ImageFileManager.py @@ -0,0 +1,57 @@ +from ..ui_utils import getImageFileFromUiUtils + + +class ImageFileManager(): + def __init__(self, theme:str ="Dark"): + if theme == "Dark": + self._createDarkModeImages() + elif theme == "Light": + self._createLightModeImages() + + + def _createDarkModeImages(self): + self.VRCT_LOGO = getImageFileFromUiUtils("vrct_logo_for_dark_mode.png") + self.VRCT_LOGO_MARK = getImageFileFromUiUtils("vrct_logo_mark_white.png") + + self.TRANSLATION_ICON = getImageFileFromUiUtils("translation_icon_white.png") + self.TRANSLATION_ICON_DISABLED = getImageFileFromUiUtils("translation_icon_disabled.png") + self.MIC_ICON = getImageFileFromUiUtils("mic_icon_white.png") + self.MIC_ICON_DISABLED = getImageFileFromUiUtils("mic_icon_disabled.png") + self.HEADPHONES_ICON = getImageFileFromUiUtils("headphones_icon_white.png") + self.HEADPHONES_ICON_DISABLED = getImageFileFromUiUtils("headphones_icon_disabled.png") + self.FOREGROUND_ICON = getImageFileFromUiUtils("foreground_icon_white.png") + self.FOREGROUND_ICON_DISABLED = getImageFileFromUiUtils("foreground_icon_disabled.png") + + self.NARROW_ARROW_DOWN = getImageFileFromUiUtils("narrow_arrow_down.png") + + self.CONFIGURATION_ICON = getImageFileFromUiUtils("configuration_icon_white.png") + self.CONFIGURATION_ICON_DISABLED = getImageFileFromUiUtils("configuration_icon_disabled.png") + + self.ARROW_LEFT = getImageFileFromUiUtils("arrow_left_white.png") + self.ARROW_LEFT_DISABLED = getImageFileFromUiUtils("arrow_left_disabled.png") + + self.HELP_ICON = getImageFileFromUiUtils("help_icon_white.png") + + def _createLightModeImages(self): + self.VRCT_LOGO = getImageFileFromUiUtils("vrct_logo_for_light_mode.png") + self.VRCT_LOGO_MARK = getImageFileFromUiUtils("vrct_logo_mark_black.png") + + + self.TRANSLATION_ICON = getImageFileFromUiUtils("translation_icon_white.png") + self.TRANSLATION_ICON_DISABLED = getImageFileFromUiUtils("translation_icon_disabled.png") + self.MIC_ICON = getImageFileFromUiUtils("mic_icon_white.png") + self.MIC_ICON_DISABLED = getImageFileFromUiUtils("mic_icon_disabled.png") + self.HEADPHONES_ICON = getImageFileFromUiUtils("headphones_icon_white.png") + self.HEADPHONES_ICON_DISABLED = getImageFileFromUiUtils("headphones_icon_disabled.png") + self.FOREGROUND_ICON = getImageFileFromUiUtils("foreground_icon_white.png") + self.FOREGROUND_ICON_DISABLED = getImageFileFromUiUtils("foreground_icon_disabled.png") + + self.NARROW_ARROW_DOWN = getImageFileFromUiUtils("narrow_arrow_down.png") + + self.CONFIGURATION_ICON = getImageFileFromUiUtils("configuration_icon_white.png") + self.CONFIGURATION_ICON_DISABLED = getImageFileFromUiUtils("configuration_icon_disabled.png") + + self.ARROW_LEFT = getImageFileFromUiUtils("arrow_left_white.png") + self.ARROW_LEFT_DISABLED = getImageFileFromUiUtils("arrow_left_disabled.png") + + self.HELP_ICON = getImageFileFromUiUtils("help_icon_white.png") \ No newline at end of file diff --git a/vrct_gui/ui_managers/ImageFilenameManager.py b/vrct_gui/ui_managers/ImageFilenameManager.py deleted file mode 100644 index f6a1705a..00000000 --- a/vrct_gui/ui_managers/ImageFilenameManager.py +++ /dev/null @@ -1,54 +0,0 @@ -class ImageFilenameManager(): - def __init__(self, theme:str ="Dark"): - if theme == "Dark": - self._createDarkModeImages() - elif theme == "Light": - self._createLightModeImages() - - - def _createDarkModeImages(self): - self.VRCT_LOGO = "vrct_logo_for_dark_mode.png" - self.VRCT_LOGO_MARK = "vrct_logo_mark_white.png" - - self.TRANSLATION_ICON = "translation_icon_white.png" - self.TRANSLATION_ICON_DISABLED = "translation_icon_disabled.png" - self.MIC_ICON = "mic_icon_white.png" - self.MIC_ICON_DISABLED = "mic_icon_disabled.png" - self.HEADPHONES_ICON = "headphones_icon_white.png" - self.HEADPHONES_ICON_DISABLED = "headphones_icon_disabled.png" - self.FOREGROUND_ICON = "foreground_icon_white.png" - self.FOREGROUND_ICON_DISABLED = "foreground_icon_disabled.png" - - self.NARROW_ARROW_DOWN = "narrow_arrow_down.png" - - self.CONFIGURATION_ICON = "configuration_icon_white.png" - self.CONFIGURATION_ICON_DISABLED = "configuration_icon_disabled.png" - - self.ARROW_LEFT = "arrow_left_white.png" - self.ARROW_LEFT_DISABLED = "arrow_left_disabled.png" - - self.HELP_ICON = "help_icon_white.png" - - def _createLightModeImages(self): - self.VRCT_LOGO = "vrct_logo_for_light_mode.png" - self.VRCT_LOGO_MARK = "vrct_logo_mark_black.png" - - - self.TRANSLATION_ICON = "translation_icon_white.png" - self.TRANSLATION_ICON_DISABLED = "translation_icon_disabled.png" - self.MIC_ICON = "mic_icon_white.png" - self.MIC_ICON_DISABLED = "mic_icon_disabled.png" - self.HEADPHONES_ICON = "headphones_icon_white.png" - self.HEADPHONES_ICON_DISABLED = "headphones_icon_disabled.png" - self.FOREGROUND_ICON = "foreground_icon_white.png" - self.FOREGROUND_ICON_DISABLED = "foreground_icon_disabled.png" - - self.NARROW_ARROW_DOWN = "narrow_arrow_down.png" - - self.CONFIGURATION_ICON = "configuration_icon_white.png" - self.CONFIGURATION_ICON_DISABLED = "configuration_icon_disabled.png" - - self.ARROW_LEFT = "arrow_left_white.png" - self.ARROW_LEFT_DISABLED = "arrow_left_disabled.png" - - self.HELP_ICON = "help_icon_white.png" \ No newline at end of file diff --git a/vrct_gui/ui_managers/__init__.py b/vrct_gui/ui_managers/__init__.py index f6a5abc0..f0dd1edb 100644 --- a/vrct_gui/ui_managers/__init__.py +++ b/vrct_gui/ui_managers/__init__.py @@ -1,3 +1,3 @@ from .ColorThemeManager import ColorThemeManager -from .ImageFilenameManager import ImageFilenameManager +from .ImageFileManager import ImageFileManager from .UiScalingManager import UiScalingManager \ No newline at end of file diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index d714fc3c..272fbef1 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -12,11 +12,10 @@ def getImageFileFromUiUtils(file_name): img = Image_open(os_path.join(os_path.dirname(os_path.dirname(os_path.dirname(__file__))), "img", file_name)) return img -def openImageKeepAspectRatio(file_name, desired_width): - img = getImageFileFromUiUtils(file_name) - wpercent = (desired_width/float(img.size[0])) - hsize = int((float(img.size[1])*float(wpercent))) - img = img.resize((desired_width,hsize), LANCZOS) +def openImageKeepAspectRatio(image_file, desired_width): + wpercent = (desired_width/float(image_file.size[0])) + hsize = int((float(image_file.size[1])*float(wpercent))) + img = image_file.resize((desired_width,hsize), LANCZOS) return (img, desired_width, hsize) def retag(tag, *args): @@ -121,14 +120,14 @@ def switchTabsColor(target_widget, tab_buttons, active_bg_color, active_text_col -def createButtonWithImage(parent_widget, button_fg_color, button_enter_color, button_clicked_color, button_image_filename, button_image_size, button_ipadxy, button_command, corner_radius: int = 0 ,shape: str = "normal"): +def createButtonWithImage(parent_widget, button_fg_color, button_enter_color, button_clicked_color, button_image_file, button_image_size, button_ipadxy, button_command, corner_radius: int = 0 ,shape: str = "normal"): button_wrapper = CTkFrame(parent_widget, corner_radius=corner_radius, fg_color=button_fg_color, height=0, width=0, cursor="hand2") button_widget = CTkLabel( button_wrapper, text=None, height=0, - image=CTkImage(getImageFileFromUiUtils(button_image_filename),size=(button_image_size,button_image_size)), + image=CTkImage((button_image_file),size=(button_image_size,button_image_size)), ) button_widget.grid(row=0, column=0, padx=button_ipadxy, pady=button_ipadxy) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 8275f04a..c3e6afde 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -4,7 +4,7 @@ from customtkinter import CTk # from window_help_and_info import ToplevelWindowInformation -# from .ui_managers import ColorThemeManager, ImageFilenameManager, UiScalingManager +# from .ui_managers import ColorThemeManager, ImageFileManager, UiScalingManager from ._changeMainWindowWidgetsStatus import _changeMainWindowWidgetsStatus from ._printToTextbox import _printToTextbox From 08fbbd1b593f1d3941baf50b4246d6404a20a435 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 5 Sep 2023 02:02:14 +0900 Subject: [PATCH 095/355] =?UTF-8?q?[bugfix]=20Config=20Window:=20=E3=83=9E?= =?UTF-8?q?=E3=82=A4=E3=82=AF=E3=80=81=E3=82=B9=E3=83=94=E3=83=BC=E3=82=AB?= =?UTF-8?q?=E3=83=BC=E3=81=AEThreshold=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=92=E6=8A=BC=E3=81=97=E3=81=9F?= =?UTF-8?q?=E6=99=82=E3=81=AB=E3=80=81=E5=A4=89=E6=95=B0=E3=82=A2=E3=82=AF?= =?UTF-8?q?=E3=82=BB=E3=82=B9=E3=82=A8=E3=83=A9=E3=83=BC=E3=81=8C=E3=81=A7?= =?UTF-8?q?=E3=82=8B=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82=20?= =?UTF-8?q?=E3=83=86=E3=82=B9=E3=83=88=E3=81=A8=E3=81=97=E3=81=A6Sleep?= =?UTF-8?q?=E9=96=A2=E6=95=B0=E3=82=92=E5=85=A5=E3=82=8C=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=82=92=E5=89=8A=E9=99=A4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting_box_transcription/createSettingBox_Mic.py | 5 +---- .../setting_box_transcription/createSettingBox_Speaker.py | 4 +--- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 78086993..1ca96a8c 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -1,5 +1,3 @@ -from time import sleep - from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator @@ -13,14 +11,13 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari def checkbox_input_mic_threshold_check_callback(e, passive_button_wrapper_widget, active_button_wrapper_widget, is_turned_on): - callFunctionIfCallable(config_window.CALLBACK_CHECK_MIC_THRESHOLD, is_turned_on) + callFunctionIfCallable(view_variable.CALLBACK_CHECK_MIC_THRESHOLD, is_turned_on) if is_turned_on is True: passive_button_widget = passive_button_wrapper_widget.children["!ctklabel"] passive_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) passive_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) passive_button_wrapper_widget.update_idletasks() - sleep(1) passive_button_wrapper_widget.grid_remove() active_button_wrapper_widget.grid() diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 27711b22..911c10f1 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -1,4 +1,3 @@ -from time import sleep from utils import callFunctionIfCallable @@ -13,14 +12,13 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ def checkbox_input_speaker_threshold_check_callback(e, passive_button_wrapper_widget, active_button_wrapper_widget, is_turned_on): - callFunctionIfCallable(config_window.CALLBACK_CHECK_SPEAKER_THRESHOLD, is_turned_on) + callFunctionIfCallable(view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD, is_turned_on) if is_turned_on is True: passive_button_widget = passive_button_wrapper_widget.children["!ctklabel"] passive_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) passive_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) passive_button_wrapper_widget.update_idletasks() - sleep(1) passive_button_wrapper_widget.grid_remove() active_button_wrapper_widget.grid() From fab05ae888a2e87e9e7631f975ada869cdaf2651 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 5 Sep 2023 02:43:22 +0900 Subject: [PATCH 096/355] =?UTF-8?q?[Add]=20Config=20Window=E3=81=AE?= =?UTF-8?q?=E3=83=9E=E3=82=A4=E3=82=AF/=E3=82=B9=E3=83=94=E3=83=BC?= =?UTF-8?q?=E3=82=AB=E3=83=BC=E3=81=AE=E5=8B=95=E4=BD=9C=E7=A2=BA=E8=AA=8D?= =?UTF-8?q?=E3=81=AEenergy=E3=82=92=E8=A1=A8=E7=A4=BA=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 18 ++++++++++-------- view.py | 6 +++++- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/main.py b/main.py index a83154f3..2c6629f9 100644 --- a/main.py +++ b/main.py @@ -264,14 +264,15 @@ def callbackSetMicDynamicEnergyThreshold(value): print("callbackSetMicDynamicEnergyThreshold", value) config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value +def setProgressBarMicEnergy(energy): + view.updateSetProgressBar_MicEnergy(energy) + def callbackCheckMicThreshold(is_turned_on): print("callbackCheckMicThreshold", is_turned_on) if is_turned_on is True: - # UIの処理あり - pass + model.startCheckMicEnergy(setProgressBarMicEnergy) else: - # UIの処理あり - pass + model.stopCheckMicEnergy() def callbackSetMicRecordTimeout(value): print("callbackSetMicRecordTimeout", int(value)) @@ -311,14 +312,15 @@ def callbackSetSpeakerDynamicEnergyThreshold(value): print("callbackSetSpeakerDynamicEnergyThreshold", value) config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value +def setProgressBarSpeakerEnergy(energy): + view.updateSetProgressBar_SpeakerEnergy(energy) + def callbackCheckSpeakerThreshold(is_turned_on): print("callbackCheckSpeakerThreshold", is_turned_on) if is_turned_on is True: - # UIの処理あり - pass + model.startCheckSpeakerEnergy(setProgressBarSpeakerEnergy) else: - # UIの処理あり - pass + model.stopCheckSpeakerEnergy() def callbackSetSpeakerRecordTimeout(value): print("callbackSetSpeakerRecordTimeout", int(value)) diff --git a/view.py b/view.py index 12f10cdf..f550180c 100644 --- a/view.py +++ b/view.py @@ -422,10 +422,14 @@ class View(): def updateSelected_MicDevice(self, default_selected_mic_device_name:str): self.view_variable.VAR_MIC_DEVICE.set(default_selected_mic_device_name) - + def updateSetProgressBar_MicEnergy(self, new_mic_energy): + vrct_gui.config_window.sb__progressbar_x_slider__progressbar_mic_energy_threshold.set(new_mic_energy/config.MAX_MIC_ENERGY_THRESHOLD) def updateList_SpeakerDevice(self, new_speaker_device_list): self.view_variable.LIST_SPEAKER_DEVICE = new_speaker_device_list vrct_gui.config_window.sb__optionmenu_speaker_device.configure(values=new_speaker_device_list) + def updateSetProgressBar_SpeakerEnergy(self, new_speaker_energy): + vrct_gui.config_window.sb__progressbar_x_slider__progressbar_speaker_energy_threshold.set(new_speaker_energy/config.MAX_SPEAKER_ENERGY_THRESHOLD) + view = View() \ No newline at end of file From 84ebe47b8958a6a8def0773d1090f1c12562722f Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 5 Sep 2023 06:57:56 +0900 Subject: [PATCH 097/355] =?UTF-8?q?[Add]=20Config=20Window=20=E3=82=B9?= =?UTF-8?q?=E3=83=AC=E3=83=83=E3=82=B7=E3=83=A7=E3=83=AB=E3=83=89=E3=83=81?= =?UTF-8?q?=E3=82=A7=E3=83=83=E3=82=AF=E6=99=82=E3=81=AE=E3=83=9C=E3=82=BF?= =?UTF-8?q?=E3=83=B3=E3=81=AE=E3=82=B9=E3=83=86=E3=83=BC=E3=82=BF=E3=82=B9?= =?UTF-8?q?=E5=A4=89=E6=9B=B4(disabled,=20normal)=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E3=81=A8=E5=87=A6=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 21 +++++++++ view.py | 44 +++++++++++++++++++ vrct_gui/_changeConfigWindowWidgetsStatus.py | 37 ++++++++++++++++ .../createSettingBox_Mic.py | 34 ++------------ .../createSettingBox_Speaker.py | 35 ++------------- vrct_gui/vrct_gui.py | 10 +++++ 6 files changed, 118 insertions(+), 63 deletions(-) create mode 100644 vrct_gui/_changeConfigWindowWidgetsStatus.py diff --git a/main.py b/main.py index 2c6629f9..b9ae536a 100644 --- a/main.py +++ b/main.py @@ -270,9 +270,19 @@ def setProgressBarMicEnergy(energy): def callbackCheckMicThreshold(is_turned_on): print("callbackCheckMicThreshold", is_turned_on) if is_turned_on is True: + view.setConfigWindowCompactModeSwitchStatusToDisabled() + + view.setConfigWindowThresholdCheckWidgetsStatusToDisabled() model.startCheckMicEnergy(setProgressBarMicEnergy) + view.replaceConfigWindowMicThresholdCheckButtonToActive() + view.setConfigWindowThresholdCheckWidgetsStatusToNormal() else: + view.setConfigWindowThresholdCheckWidgetsStatusToDisabled() model.stopCheckMicEnergy() + view.replaceConfigWindowMicThresholdCheckButtonToPassive() + view.setConfigWindowThresholdCheckWidgetsStatusToNormal() + + view.setConfigWindowCompactModeSwitchStatusToNormal() def callbackSetMicRecordTimeout(value): print("callbackSetMicRecordTimeout", int(value)) @@ -318,9 +328,20 @@ def setProgressBarSpeakerEnergy(energy): def callbackCheckSpeakerThreshold(is_turned_on): print("callbackCheckSpeakerThreshold", is_turned_on) if is_turned_on is True: + view.setConfigWindowCompactModeSwitchStatusToDisabled() + + view.setConfigWindowThresholdCheckWidgetsStatusToDisabled() model.startCheckSpeakerEnergy(setProgressBarSpeakerEnergy) + view.replaceConfigWindowSpeakerThresholdCheckButtonToActive() + view.setConfigWindowThresholdCheckWidgetsStatusToNormal() + else: + view.setConfigWindowThresholdCheckWidgetsStatusToDisabled() model.stopCheckSpeakerEnergy() + view.replaceConfigWindowSpeakerThresholdCheckButtonToPassive() + view.setConfigWindowThresholdCheckWidgetsStatusToNormal() + + view.setConfigWindowCompactModeSwitchStatusToNormal() def callbackSetSpeakerRecordTimeout(value): print("callbackSetSpeakerRecordTimeout", int(value)) diff --git a/view.py b/view.py index f550180c..8202ce67 100644 --- a/view.py +++ b/view.py @@ -403,6 +403,50 @@ class View(): # Config Window + def setConfigWindowCompactModeSwitchStatusToDisabled(self): + vrct_gui.config_window.setting_box_compact_mode_switch_box.configure(state="disabled") + + def setConfigWindowCompactModeSwitchStatusToNormal(self): + vrct_gui.config_window.setting_box_compact_mode_switch_box.configure(state="normal") + + def setConfigWindowThresholdCheckWidgetsStatusToDisabled(self): + vrct_gui.changeConfigWindowWidgetsStatus( + status="disabled", + target_names=[ + "mic_energy_threshold_check_button", + "speaker_energy_threshold_check_button", + ] + ) + + def setConfigWindowThresholdCheckWidgetsStatusToNormal(self): + vrct_gui.changeConfigWindowWidgetsStatus( + status="normal", + target_names=[ + "mic_energy_threshold_check_button", + "speaker_energy_threshold_check_button", + ] + ) + + def replaceConfigWindowMicThresholdCheckButtonToActive(self): + vrct_gui.config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.grid_remove() + vrct_gui.config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold.grid() + + def replaceConfigWindowMicThresholdCheckButtonToPassive(self): + vrct_gui.config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold.grid_remove() + vrct_gui.config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.grid() + + + + def replaceConfigWindowSpeakerThresholdCheckButtonToActive(self): + vrct_gui.config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.grid_remove() + vrct_gui.config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold.grid() + + def replaceConfigWindowSpeakerThresholdCheckButtonToPassive(self): + vrct_gui.config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold.grid_remove() + vrct_gui.config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.grid() + + + def reloadConfigWindowSettingBoxContainer(self): vrct_gui.config_window.settings.IS_CONFIG_WINDOW_COMPACT_MODE = config.IS_CONFIG_WINDOW_COMPACT_MODE vrct_gui.config_window.reloadConfigWindowSettingBoxContainer() diff --git a/vrct_gui/_changeConfigWindowWidgetsStatus.py b/vrct_gui/_changeConfigWindowWidgetsStatus.py new file mode 100644 index 00000000..6eb2bd57 --- /dev/null +++ b/vrct_gui/_changeConfigWindowWidgetsStatus.py @@ -0,0 +1,37 @@ +from customtkinter import CTkImage + +from .ui_utils import getImageFileFromUiUtils + + +def _changeConfigWindowWidgetsStatus(config_window, settings, view_variable, status, target_names): + if target_names == "All": + target_names = ["mic_energy_threshold_check_button", "speaker_energy_threshold_check_button"] + + + for target_name in target_names: + match target_name: + case "mic_energy_threshold_check_button": + if status == "disabled": + config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.children["!ctklabel"].configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + + elif status == "normal": + config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR) + config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.children["!ctklabel"].configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR) + + case "speaker_energy_threshold_check_button": + if status == "disabled": + config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.children["!ctklabel"].configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) + + elif status == "normal": + config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR) + config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.children["!ctklabel"].configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR) + + + case _: + raise ValueError(f"No matching case for target_name: {target_name}") + + + + config_window.update() \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 1ca96a8c..689452ca 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -10,27 +10,9 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari createSettingBoxEntry = sbg.createSettingBoxEntry - def checkbox_input_mic_threshold_check_callback(e, passive_button_wrapper_widget, active_button_wrapper_widget, is_turned_on): + def checkbox_input_mic_threshold_check_callback(is_turned_on): callFunctionIfCallable(view_variable.CALLBACK_CHECK_MIC_THRESHOLD, is_turned_on) - if is_turned_on is True: - passive_button_widget = passive_button_wrapper_widget.children["!ctklabel"] - passive_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - passive_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - passive_button_wrapper_widget.update_idletasks() - - passive_button_wrapper_widget.grid_remove() - active_button_wrapper_widget.grid() - - elif is_turned_on is False: - # active_button_widget = active_button_wrapper_widget.children["!ctklabel"] - # active_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - # active_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - # active_button_wrapper_widget.update_idletasks() - # sleep(3) - - active_button_wrapper_widget.grid_remove() - passive_button_wrapper_widget.grid() def optionmenu_mic_host_callback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_MIC_HOST, value) @@ -102,19 +84,9 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari progressbar_attr_name="sb__progressbar_x_slider__progressbar_mic_energy_threshold", passive_button_attr_name="sb__progressbar_x_slider__passive_button_mic_energy_threshold", - passive_button_command=lambda e: checkbox_input_mic_threshold_check_callback( - e, - config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold, - config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold, - is_turned_on=True, - ), + passive_button_command=lambda _e: checkbox_input_mic_threshold_check_callback(True), active_button_attr_name="sb__progressbar_x_slider__active_button_mic_energy_threshold", - active_button_command=lambda e: checkbox_input_mic_threshold_check_callback( - e, - config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold, - config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold, - is_turned_on=False, - ), + active_button_command=lambda _e: checkbox_input_mic_threshold_check_callback(False), button_image_file=settings.image_file.MIC_ICON ) config_window.sb__mic_energy_threshold.grid(row=row) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 911c10f1..5742292a 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -1,4 +1,3 @@ - from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator @@ -11,27 +10,9 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ createSettingBoxEntry = sbg.createSettingBoxEntry - def checkbox_input_speaker_threshold_check_callback(e, passive_button_wrapper_widget, active_button_wrapper_widget, is_turned_on): + def checkbox_input_speaker_threshold_check_callback(is_turned_on): callFunctionIfCallable(view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD, is_turned_on) - if is_turned_on is True: - passive_button_widget = passive_button_wrapper_widget.children["!ctklabel"] - passive_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - passive_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - passive_button_wrapper_widget.update_idletasks() - - passive_button_wrapper_widget.grid_remove() - active_button_wrapper_widget.grid() - - elif is_turned_on is False: - # active_button_widget = active_button_wrapper_widget.children["!ctklabel"] - # active_button_wrapper_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - # active_button_widget.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - # active_button_wrapper_widget.update_idletasks() - # sleep(3) - - active_button_wrapper_widget.grid_remove() - passive_button_wrapper_widget.grid() def optionmenu_input_speaker_device_callback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_DEVICE, value) @@ -84,19 +65,9 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ progressbar_attr_name="sb__progressbar_x_slider__progressbar_speaker_energy_threshold", passive_button_attr_name="sb__progressbar_x_slider__passive_button_speaker_energy_threshold", - passive_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - e, - config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold, - config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold, - is_turned_on=True, - ), + passive_button_command=lambda _e: checkbox_input_speaker_threshold_check_callback(True), active_button_attr_name="sb__progressbar_x_slider__active_button_speaker_energy_threshold", - active_button_command=lambda e: checkbox_input_speaker_threshold_check_callback( - e, - config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold, - config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold, - is_turned_on=False, - ), + active_button_command=lambda _e: checkbox_input_speaker_threshold_check_callback(False), button_image_file=settings.image_file.HEADPHONES_ICON ) config_window.sb__speaker_energy_threshold.grid(row=row) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index c3e6afde..61631efb 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -6,6 +6,7 @@ from customtkinter import CTk # from .ui_managers import ColorThemeManager, ImageFileManager, UiScalingManager from ._changeMainWindowWidgetsStatus import _changeMainWindowWidgetsStatus +from ._changeConfigWindowWidgetsStatus import _changeConfigWindowWidgetsStatus from ._printToTextbox import _printToTextbox from .main_window import createMainWindowWidgets @@ -62,6 +63,15 @@ class VRCT_GUI(CTk): target_names=target_names, ) + def changeConfigWindowWidgetsStatus(self, status, target_names): + _changeConfigWindowWidgetsStatus( + config_window=self.config_window, + settings=self.settings.config_window, + view_variable=self._view_variable, + status=status, + target_names=target_names, + ) + def printToTextbox(self, target_textbox, original_message, translated_message, tags=None): _printToTextbox( settings=self.settings.main, From 74b759ad6b785e5f6af45a9279e0e815c9b1ff9c Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 5 Sep 2023 09:38:24 +0900 Subject: [PATCH 098/355] =?UTF-8?q?[Add]=20Main=20Window.=20language=20set?= =?UTF-8?q?tings=20label=E7=B3=BB=E3=82=92=E5=A4=89=E6=95=B0=E3=81=AB?= =?UTF-8?q?=E3=81=97=E3=81=A6view.py=E3=81=AB=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 7 +++++++ .../createSidebarLanguagesSettings.py | 16 ++++++---------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/view.py b/view.py index 8202ce67..25bd3b15 100644 --- a/view.py +++ b/view.py @@ -55,8 +55,15 @@ class View(): CALLBACK_TOGGLE_FOREGROUND=None, # Language Settings + VAR_LABEL_LANGUAGE_SETTINGS=StringVar(value="Language Settings"), # JA: 言語設定 + CALLBACK_SELECTED_LANGUAGE_PRESET_TAB=None, + VAR_LABEL_YOUR_LANGUAGE=StringVar(value="Your Language"), # JA: あなたの言語 VAR_YOUR_LANGUAGE = StringVar(value="Japanese\n(Japan)"), + + VAR_LABEL_BOTH_DIRECTION_DESC=StringVar(value="Translate Each Other"), # JA: 双方向に翻訳 + + VAR_LABEL_TARGET_LANGUAGE=StringVar(value="Target Language"), # JA: 相手の言語 VAR_TARGET_LANGUAGE = StringVar(value="English\n(United States)"), diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index df698570..27175885 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -75,7 +75,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - def createQuickLanguageSettingBox(parent_widget, title_text, title_text_attr_name, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values, variable): + def createQuickLanguageSettingBox(parent_widget, var_title_text, title_text_attr_name, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values, variable): sls__box = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) sls__box.columnconfigure((0,2), weight=1) @@ -85,7 +85,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): sls__label = CTkLabel( sls__box_wrapper, - text=title_text, + textvariable=var_title_text, height=0, font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_SECTION_TITLE_FONT_SIZE, weight="normal"), text_color=settings.ctm.SLS__BOX_SECTION_TITLE_TEXT_COLOR @@ -126,8 +126,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): main_window.sls__container_title = CTkLabel(main_window.sls__container, - # text="言語設定", - text="Language Settings", + textvariable=view_variable.VAR_LABEL_LANGUAGE_SETTINGS, height=0, font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__TITLE_FONT_SIZE, weight="normal"), text_color=settings.ctm.SLS__TITLE_TEXT_COLOR @@ -213,8 +212,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): # Your language main_window.sls__box_your_language = createQuickLanguageSettingBox( parent_widget=main_window.sls__box_frame, - # title_text="あなたの言語", - title_text="Your Language", + var_title_text=view_variable.VAR_LABEL_YOUR_LANGUAGE, title_text_attr_name="sls__title_text_your_language", optionmenu_attr_name="sls__optionmenu_your_language", dropdown_menu_attr_name="sls__dropdown_menu_your_language", @@ -241,8 +239,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): main_window.sls__both_direction_desc = CTkLabel( main_window.sls__arrow_direction_box, - # text="双方向に翻訳", - text="Translate Each Other", + textvariable=view_variable.VAR_LABEL_BOTH_DIRECTION_DESC, height=0, font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_ARROWS_DESC_FONT_SIZE, weight="normal"), text_color=settings.ctm.SLS__BOX_ARROWS_TEXT_COLOR, @@ -263,8 +260,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): # Target language main_window.sls__box_target_language = createQuickLanguageSettingBox( parent_widget=main_window.sls__box_frame, - # title_text="相手の言語", - title_text="Target Language", + var_title_text=view_variable.VAR_LABEL_TARGET_LANGUAGE, title_text_attr_name="sls__title_text_target_language", optionmenu_attr_name="sls__optionmenu_target_language", dropdown_menu_attr_name="sls__dropdown_menu_target_language", From e7350038a0e526b0110f7212cd3d91c3a0e55188 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 5 Sep 2023 15:17:58 +0900 Subject: [PATCH 099/355] =?UTF-8?q?[Chore]=20Main=20Window:=20Language=20S?= =?UTF-8?q?ettings=20=E4=BD=BF=E3=81=A3=E3=81=A6=E3=81=AA=E3=81=84?= =?UTF-8?q?=E5=A4=89=E6=95=B0=E3=82=84=E5=BC=95=E6=95=B0=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E3=81=AE=E5=89=8A=E9=99=A4=E3=80=82=20=E5=A4=89=E6=95=B0?= =?UTF-8?q?=E5=90=8D=E3=81=AE=E5=A4=89=E6=9B=B4=E3=82=82=E5=B0=91=E3=81=97?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../createSidebarLanguagesSettings.py | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 27175885..15a2becb 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -56,7 +56,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - def createOption_DropdownMenu_for_quickSettings(setattr_obj, parent_widget, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values=None, width:int = 200, font_size:int = 10, text_color="white", command=None, variable=""): + def createOption_DropdownMenu_for_languageSettings(setattr_obj, parent_widget, optionmenu_attr_name, dropdown_menu_values, width:int = 200, font_size:int = 10, text_color="white", command=None, variable=""): setattr(setattr_obj, optionmenu_attr_name, CTkOptionMenu( parent_widget, height=30, @@ -75,7 +75,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - def createQuickLanguageSettingBox(parent_widget, var_title_text, title_text_attr_name, optionmenu_attr_name, dropdown_menu_attr_name, dropdown_menu_values, variable): + def createLanguageSettingBox(parent_widget, var_title_text, title_text_attr_name, optionmenu_attr_name, dropdown_menu_values, variable): sls__box = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) sls__box.columnconfigure((0,2), weight=1) @@ -96,11 +96,10 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - createOption_DropdownMenu_for_quickSettings( + createOption_DropdownMenu_for_languageSettings( main_window, sls__box_wrapper, optionmenu_attr_name, - dropdown_menu_attr_name, dropdown_menu_values=dropdown_menu_values, # command=self.fakeCommand, width=settings.uism.SLS__BOX_DROPDOWN_MENU_WIDTH, @@ -210,12 +209,11 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): main_window.sls__box_frame.grid_columnconfigure(0, weight=1) # Your language - main_window.sls__box_your_language = createQuickLanguageSettingBox( + main_window.sls__box_your_language = createLanguageSettingBox( parent_widget=main_window.sls__box_frame, var_title_text=view_variable.VAR_LABEL_YOUR_LANGUAGE, title_text_attr_name="sls__title_text_your_language", optionmenu_attr_name="sls__optionmenu_your_language", - dropdown_menu_attr_name="sls__dropdown_menu_your_language", dropdown_menu_values=["1""2","pppp\npppp"], variable=view_variable.VAR_YOUR_LANGUAGE ) @@ -258,12 +256,11 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): # Target language - main_window.sls__box_target_language = createQuickLanguageSettingBox( + main_window.sls__box_target_language = createLanguageSettingBox( parent_widget=main_window.sls__box_frame, var_title_text=view_variable.VAR_LABEL_TARGET_LANGUAGE, title_text_attr_name="sls__title_text_target_language", optionmenu_attr_name="sls__optionmenu_target_language", - dropdown_menu_attr_name="sls__dropdown_menu_target_language", dropdown_menu_values=["1""2","pppp\npppp2"], variable=view_variable.VAR_TARGET_LANGUAGE ) From bfed9a2d26277a463a3d847d6a6c73fbf5437c58 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 5 Sep 2023 22:38:26 +0900 Subject: [PATCH 100/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Config=20Window?= =?UTF-8?q?=E3=81=AE=E3=83=9E=E3=82=A4=E3=82=AF/=E3=82=B9=E3=83=94?= =?UTF-8?q?=E3=83=BC=E3=82=AB=E3=83=BC=E3=81=AEenergy=E6=A4=9C=E5=87=BA?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=81=A7=E3=82=B9=E3=83=94=E3=83=BC=E3=82=AB?= =?UTF-8?q?=E3=83=BC->=E3=83=9E=E3=82=A4=E3=82=AF=E3=81=AE=E9=A0=86?= =?UTF-8?q?=E3=81=A7ON=E3=81=AB=E3=81=97=E3=81=9F=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AB=E3=83=97=E3=83=AD=E3=82=B0=E3=83=A9=E3=83=A0=E3=81=8C?= =?UTF-8?q?=E8=90=BD=E3=81=A1=E3=82=8B=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 14 ++++---------- models/transcription/transcription_recorder.py | 2 -- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/model.py b/model.py index 20524129..e6440941 100644 --- a/model.py +++ b/model.py @@ -250,6 +250,7 @@ class Model: energy = mic_energy_queue.get() fnc(energy) sleep(0.01) + mic_energy_queue = Queue() mic_device = [device for device in getInputDevices()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] self.mic_energy_recorder = SelectedMicEnergyRecorder(mic_device) @@ -303,24 +304,17 @@ class Model: fnc(energy) sleep(0.01) - def getSpeakerEnergy(): - with self.speaker_energy_recorder.source as source: - energy = self.speaker_energy_recorder.recorder.listen_energy(source) - speaker_energy_queue.put(energy) - speaker_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] speaker_energy_queue = Queue() self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(speaker_device) - self.speaker_energy_get_progressbar = threadFnc(getSpeakerEnergy) - self.speaker_energy_get_progressbar.daemon = True - self.speaker_energy_get_progressbar.start() + self.speaker_energy_recorder.recordIntoQueue(speaker_energy_queue) self.speaker_energy_plot_progressbar = threadFnc(sendSpeakerEnergy) self.speaker_energy_plot_progressbar.daemon = True self.speaker_energy_plot_progressbar.start() def stopCheckSpeakerEnergy(self): - if self.speaker_energy_get_progressbar != None: - self.speaker_energy_get_progressbar.stop() + if self.speaker_energy_recorder != None: + self.speaker_energy_recorder.stop() if self.speaker_energy_plot_progressbar != None: self.speaker_energy_plot_progressbar.stop() diff --git a/models/transcription/transcription_recorder.py b/models/transcription/transcription_recorder.py index cf2cec04..4e46cfc7 100644 --- a/models/transcription/transcription_recorder.py +++ b/models/transcription/transcription_recorder.py @@ -84,8 +84,6 @@ class SelectedSpeakeEnergyRecorder(BaseEnergyRecorder): source = Microphone(speaker=True, device_index= device["index"], sample_rate=int(device["defaultSampleRate"]), - chunk_size=get_sample_size(paInt16), - channels=device["maxInputChannels"] ) super().__init__(source=source) # self.adjustForNoise() \ No newline at end of file From 408a4d7996fc971cde8b378480a6f0de1e7f98aa Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 5 Sep 2023 23:12:42 +0900 Subject: [PATCH 101/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20=E3=83=9E?= =?UTF-8?q?=E3=82=A4=E3=82=AF/=E3=82=B9=E3=83=94=E3=83=BC=E3=82=AB?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E6=99=82=E3=81=ABenergy=E3=81=AEProgressBar=E3=81=AE?= =?UTF-8?q?=E5=8B=95=E4=BD=9C=E3=82=92=E5=81=9C=E6=AD=A2=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/main.py b/main.py index b9ae536a..bd8e3535 100644 --- a/main.py +++ b/main.py @@ -252,10 +252,16 @@ def callbackSetMicHost(value): view.updateSelected_MicDevice(config.CHOICE_MIC_DEVICE) view.updateList_MicDevice(model.getListInputDevice()) + model.stopCheckMicEnergy() + view.replaceConfigWindowMicThresholdCheckButtonToPassive() + def callbackSetMicDevice(value): print("callbackSetMicDevice", value) config.CHOICE_MIC_DEVICE = value + model.stopCheckMicEnergy() + view.replaceConfigWindowMicThresholdCheckButtonToPassive() + def callbackSetMicEnergyThreshold(value): print("callbackSetMicEnergyThreshold", int(value)) config.INPUT_MIC_ENERGY_THRESHOLD = int(value) @@ -314,6 +320,9 @@ def callbackSetSpeakerDevice(value): print("callbackSetSpeakerDevice", value) config.CHOICE_SPEAKER_DEVICE = value + model.stopCheckSpeakerEnergy() + view.replaceConfigWindowSpeakerThresholdCheckButtonToPassive() + def callbackSetSpeakerEnergyThreshold(value): print("callbackSetSpeakerEnergyThreshold", int(value)) config.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) From ca3333d717314b7c8c39a86fdc5b4be1a1ba4766 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 5 Sep 2023 23:42:23 +0900 Subject: [PATCH 102/355] =?UTF-8?q?[Add]=20Main=20Window:=20view.py?= =?UTF-8?q?=E3=81=B8=E3=81=AE=E5=A4=89=E6=95=B0=E7=A7=BB=E5=8B=95=20Langua?= =?UTF-8?q?ge=20Settings=E5=9B=9E=E3=82=8A=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 20 +++++++++++------ .../createSidebarLanguagesSettings.py | 22 ++++++++++++++----- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/view.py b/view.py index 25bd3b15..883564f6 100644 --- a/view.py +++ b/view.py @@ -56,15 +56,18 @@ class View(): # Language Settings VAR_LABEL_LANGUAGE_SETTINGS=StringVar(value="Language Settings"), # JA: 言語設定 + LIST_SELECTABLE_LANGUAGES=[], CALLBACK_SELECTED_LANGUAGE_PRESET_TAB=None, VAR_LABEL_YOUR_LANGUAGE=StringVar(value="Your Language"), # JA: あなたの言語 VAR_YOUR_LANGUAGE = StringVar(value="Japanese\n(Japan)"), + CALLBACK_SELECTED_YOUR_LANGUAGE=None, VAR_LABEL_BOTH_DIRECTION_DESC=StringVar(value="Translate Each Other"), # JA: 双方向に翻訳 VAR_LABEL_TARGET_LANGUAGE=StringVar(value="Target Language"), # JA: 相手の言語 VAR_TARGET_LANGUAGE = StringVar(value="English\n(United States)"), + CALLBACK_SELECTED_TARGET_LANGUAGE=None, @@ -228,13 +231,10 @@ class View(): self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features["callback_toggle_transcription_receive"] self.view_variable.CALLBACK_TOGGLE_FOREGROUND = sidebar_features["callback_toggle_foreground"] - - vrct_gui.sls__optionmenu_your_language.configure(values=language_presets["values"]) - vrct_gui.sls__optionmenu_your_language.configure(command=language_presets["callback_your_language"]) - vrct_gui.sls__optionmenu_your_language.configure(variable=StringVar(value=config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO])) - vrct_gui.sls__optionmenu_target_language.configure(values=language_presets["values"]) - vrct_gui.sls__optionmenu_target_language.configure(command=language_presets["callback_target_language"]) - vrct_gui.sls__optionmenu_target_language.configure(variable=StringVar(value=config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO])) + self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE = language_presets["callback_your_language"] + self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE = language_presets["callback_target_language"] + self.updateList_selectableLanguages(language_presets["values"]) + self.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets["callback_selected_language_preset_tab"] vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) @@ -338,6 +338,12 @@ class View(): self.view_variable.VAR_TARGET_LANGUAGE.set(config.SELECTED_TAB_TARGET_LANGUAGES[tab_no]) + def updateList_selectableLanguages(self, new_selectable_language_list:list): + self.view_variable.LIST_SELECTABLE_LANGUAGES = new_selectable_language_list + vrct_gui.sls__optionmenu_your_language.configure(values=new_selectable_language_list) + vrct_gui.sls__optionmenu_target_language.configure(values=new_selectable_language_list) + + def printToTextbox_enableTranslation(self): self._printToTextbox_Info("翻訳機能をONにしました") def printToTextbox_disableTranslation(self): diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 15a2becb..7bf7be09 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -56,7 +56,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - def createOption_DropdownMenu_for_languageSettings(setattr_obj, parent_widget, optionmenu_attr_name, dropdown_menu_values, width:int = 200, font_size:int = 10, text_color="white", command=None, variable=""): + def createOption_DropdownMenu_for_languageSettings(setattr_obj, parent_widget, optionmenu_attr_name, dropdown_menu_values, command, width:int = 200, font_size:int = 10, text_color="white", variable=""): setattr(setattr_obj, optionmenu_attr_name, CTkOptionMenu( parent_widget, height=30, @@ -68,6 +68,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): font=CTkFont(family=settings.FONT_FAMILY, size=font_size, weight="normal"), variable=variable, anchor="center", + command=command, )) target_optionmenu_attr = getattr(setattr_obj, optionmenu_attr_name) target_optionmenu_attr.grid(row=0, column=0, sticky="e") @@ -75,7 +76,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - def createLanguageSettingBox(parent_widget, var_title_text, title_text_attr_name, optionmenu_attr_name, dropdown_menu_values, variable): + def createLanguageSettingBox(parent_widget, var_title_text, title_text_attr_name, optionmenu_attr_name, dropdown_menu_values, command, variable): sls__box = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) sls__box.columnconfigure((0,2), weight=1) @@ -101,7 +102,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): sls__box_wrapper, optionmenu_attr_name, dropdown_menu_values=dropdown_menu_values, - # command=self.fakeCommand, + command=command, width=settings.uism.SLS__BOX_DROPDOWN_MENU_WIDTH, font_size=settings.uism.SLS__BOX_DROPDOWN_MENU_FONT_SIZE, text_color=settings.ctm.LABELS_TEXT_COLOR, @@ -203,7 +204,14 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): column+=1 - # Quick Language settings BOX + def selectYourLanguageCommand(value): + callFunctionIfCallable(view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE, value) + + + def selectTargetLanguageCommand(value): + callFunctionIfCallable(view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE, value) + + # Language Settings BOX main_window.sls__box_frame = CTkFrame(main_window.sls__container, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) main_window.sls__box_frame.grid(row=2, column=0, sticky="ew") main_window.sls__box_frame.grid_columnconfigure(0, weight=1) @@ -214,7 +222,8 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): var_title_text=view_variable.VAR_LABEL_YOUR_LANGUAGE, title_text_attr_name="sls__title_text_your_language", optionmenu_attr_name="sls__optionmenu_your_language", - dropdown_menu_values=["1""2","pppp\npppp"], + dropdown_menu_values=view_variable.LIST_SELECTABLE_LANGUAGES, + command=selectYourLanguageCommand, variable=view_variable.VAR_YOUR_LANGUAGE ) main_window.sls__box_your_language.grid(row=2, column=0, padx=0, pady=(settings.uism.SLS__BOX_TOP_PADY,0),sticky="ew") @@ -261,7 +270,8 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): var_title_text=view_variable.VAR_LABEL_TARGET_LANGUAGE, title_text_attr_name="sls__title_text_target_language", optionmenu_attr_name="sls__optionmenu_target_language", - dropdown_menu_values=["1""2","pppp\npppp2"], + dropdown_menu_values=view_variable.LIST_SELECTABLE_LANGUAGES, + command=selectTargetLanguageCommand, variable=view_variable.VAR_TARGET_LANGUAGE ) main_window.sls__box_target_language.grid(row=4, column=0, padx=0, pady=(0,0),sticky="ew") From 143af06b91b3d88ec0c0c1870f459e3215949012 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 5 Sep 2023 23:49:00 +0900 Subject: [PATCH 103/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20wordfilter?= =?UTF-8?q?=E3=81=AE=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=A2=E3=82=A6?= =?UTF-8?q?=E3=83=88=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index bd8e3535..d7c5e93b 100644 --- a/main.py +++ b/main.py @@ -312,8 +312,8 @@ def callbackSetMicWordFilter(value): config.INPUT_MIC_WORD_FILTER = word_filter.split(",") else: config.INPUT_MIC_WORD_FILTER = [] - # model.resetKeywordProcessor() - # model.addKeywords() + model.resetKeywordProcessor() + model.addKeywords() # Transcription Tab (Speaker) def callbackSetSpeakerDevice(value): From 0ad9652d3e69d7901d29fe2a4131dd4d49725c0a Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 6 Sep 2023 00:25:21 +0900 Subject: [PATCH 104/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20speaker=5Fenergy?= =?UTF-8?q?=5Fget=5Fprogressbar=20->=20speaker=5Fenergy=5Frecorder?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model.py b/model.py index e6440941..81d9a9d7 100644 --- a/model.py +++ b/model.py @@ -55,7 +55,7 @@ class Model: self.logger = None self.mic_energy_recorder = None self.mic_energy_plot_progressbar = None - self.speaker_energy_get_progressbar = None + self.speaker_energy_recorder = None self.speaker_energy_plot_progressbar = None self.translator = Translator() self.keyword_processor = KeywordProcessor() From 9ce41dad10121e27f106513c711e0cde4539726b Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 6 Sep 2023 00:44:13 +0900 Subject: [PATCH 105/355] [Test] bugfix_config_window_compact_mode --- main.py | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index d7c5e93b..d9fa60b5 100644 --- a/main.py +++ b/main.py @@ -202,10 +202,20 @@ def callbackToggleForeground(is_turned_on): # Compact Mode Switch def callbackEnableConfigWindowCompactMode(): config.IS_CONFIG_WINDOW_COMPACT_MODE = True + model.stopCheckMicEnergy() + view.replaceConfigWindowMicThresholdCheckButtonToPassive() + model.stopCheckSpeakerEnergy() + view.replaceConfigWindowSpeakerThresholdCheckButtonToPassive() + view.reloadConfigWindowSettingBoxContainer() def callbackDisableConfigWindowCompactMode(): config.IS_CONFIG_WINDOW_COMPACT_MODE = False + model.stopCheckMicEnergy() + view.replaceConfigWindowMicThresholdCheckButtonToPassive() + model.stopCheckSpeakerEnergy() + view.replaceConfigWindowSpeakerThresholdCheckButtonToPassive() + view.reloadConfigWindowSettingBoxContainer() # Appearance Tab @@ -276,7 +286,7 @@ def setProgressBarMicEnergy(energy): def callbackCheckMicThreshold(is_turned_on): print("callbackCheckMicThreshold", is_turned_on) if is_turned_on is True: - view.setConfigWindowCompactModeSwitchStatusToDisabled() + # view.setConfigWindowCompactModeSwitchStatusToDisabled() view.setConfigWindowThresholdCheckWidgetsStatusToDisabled() model.startCheckMicEnergy(setProgressBarMicEnergy) @@ -288,7 +298,7 @@ def callbackCheckMicThreshold(is_turned_on): view.replaceConfigWindowMicThresholdCheckButtonToPassive() view.setConfigWindowThresholdCheckWidgetsStatusToNormal() - view.setConfigWindowCompactModeSwitchStatusToNormal() + # view.setConfigWindowCompactModeSwitchStatusToNormal() def callbackSetMicRecordTimeout(value): print("callbackSetMicRecordTimeout", int(value)) @@ -337,7 +347,7 @@ def setProgressBarSpeakerEnergy(energy): def callbackCheckSpeakerThreshold(is_turned_on): print("callbackCheckSpeakerThreshold", is_turned_on) if is_turned_on is True: - view.setConfigWindowCompactModeSwitchStatusToDisabled() + # view.setConfigWindowCompactModeSwitchStatusToDisabled() view.setConfigWindowThresholdCheckWidgetsStatusToDisabled() model.startCheckSpeakerEnergy(setProgressBarSpeakerEnergy) @@ -350,7 +360,7 @@ def callbackCheckSpeakerThreshold(is_turned_on): view.replaceConfigWindowSpeakerThresholdCheckButtonToPassive() view.setConfigWindowThresholdCheckWidgetsStatusToNormal() - view.setConfigWindowCompactModeSwitchStatusToNormal() + # view.setConfigWindowCompactModeSwitchStatusToNormal() def callbackSetSpeakerRecordTimeout(value): print("callbackSetSpeakerRecordTimeout", int(value)) From 5db665ecf13d01b11372685d7337f3c650eb4410 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 6 Sep 2023 00:55:28 +0900 Subject: [PATCH 106/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20model.py=20?= =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=AB=E3=83=90=E3=83=83=E3=82=AF=E3=81=AE?= =?UTF-8?q?=E9=96=A2=E6=95=B0(fnc)=E3=81=8C=E5=91=BC=E3=81=B9=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=81=93=E3=81=A8=E3=82=92=E8=80=83=E6=85=AE=E3=81=97?= =?UTF-8?q?=E3=81=A6try=20except=E3=81=A7=E5=9B=B2=E3=81=86=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/model.py b/model.py index 81d9a9d7..99923c2c 100644 --- a/model.py +++ b/model.py @@ -231,7 +231,10 @@ class Model: def sendMicTranscript(): mic_transcriber.transcribeAudioQueue(mic_audio_queue, config.SOURCE_LANGUAGE, config.SOURCE_COUNTRY) message = mic_transcriber.getTranscript() - fnc(message) + try: + fnc(message) + except: + pass self.mic_print_transcript = threadFnc(sendMicTranscript) self.mic_print_transcript.daemon = True @@ -248,7 +251,10 @@ class Model: def sendMicEnergy(): if mic_energy_queue.empty() is False: energy = mic_energy_queue.get() - fnc(energy) + try: + fnc(energy) + except: + pass sleep(0.01) mic_energy_queue = Queue() @@ -284,7 +290,10 @@ class Model: def sendSpkTranscript(): spk_transcriber.transcribeAudioQueue(spk_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) message = spk_transcriber.getTranscript() - fnc(message) + try: + fnc(message) + except: + pass self.spk_print_transcript = threadFnc(sendSpkTranscript) self.spk_print_transcript.daemon = True @@ -301,7 +310,10 @@ class Model: def sendSpeakerEnergy(): if speaker_energy_queue.empty() is False: energy = speaker_energy_queue.get() - fnc(energy) + try: + fnc(energy) + except: + pass sleep(0.01) speaker_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] From f12a5d10a557fc2541a3fb88eeabe58a46b5f0fb Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 6 Sep 2023 02:23:24 +0900 Subject: [PATCH 107/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Config=20Window?= =?UTF-8?q?=20transparency=E3=81=8C=E5=A4=89=E5=8C=96=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E5=AE=9F=E8=A3=85=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 2 +- view.py | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index d7c5e93b..ad67818b 100644 --- a/main.py +++ b/main.py @@ -212,7 +212,7 @@ def callbackDisableConfigWindowCompactMode(): def callbackSetTransparency(value): print("callbackSetTransparency", int(value)) config.TRANSPARENCY = int(value) - # self.parent.wm_attributes("-alpha", int(value/100)) + view.updateConfigWindowTransparency() def callbackSetAppearance(value): print("callbackSetAppearance", value) diff --git a/view.py b/view.py index 25bd3b15..3221b6ef 100644 --- a/view.py +++ b/view.py @@ -410,6 +410,10 @@ class View(): # Config Window + @staticmethod + def updateConfigWindowTransparency(): + vrct_gui.wm_attributes("-alpha", config.TRANSPARENCY/100) + def setConfigWindowCompactModeSwitchStatusToDisabled(self): vrct_gui.config_window.setting_box_compact_mode_switch_box.configure(state="disabled") From ec5732ec2e60230706c5c3958f0971976065e679 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 6 Sep 2023 03:56:26 +0900 Subject: [PATCH 108/355] [Update] New VRCT Logo! replace to the new one. so adjust UI position and scaling for it. --- img/vrct_logo_for_dark_mode.png | Bin 7087 -> 201271 bytes img/vrct_logo_for_light_mode.png | Bin 7664 -> 223936 bytes img/vrct_logo_mark_black.png | Bin 1018 -> 8777 bytes img/vrct_logo_mark_white.png | Bin 898 -> 8225 bytes .../widgets/createConfigWindowTitle.py | 4 ++-- vrct_gui/ui_managers/UiScalingManager.py | 4 ++-- 6 files changed, 4 insertions(+), 4 deletions(-) diff --git a/img/vrct_logo_for_dark_mode.png b/img/vrct_logo_for_dark_mode.png index 7eeaabc26f728a7991160400ead86a6a2139948b..53add07604ecaf8c7756ab56ef1fe202cf4f5485 100644 GIT binary patch literal 201271 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

*dMGNi3xCmqY5#z7?D8>u7qw#q+si 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 z%0DAJI~5fb`LvGl#3vth|7`1qGxvmRhPmMn*ca#@$Gu+EzNE#h;}7Vz;JiNlc2GB@ zkGwtsbvEc84*!XN>{~P1f3Rco&9!?&ysjus`)1-wNG9|@|C-6ayy{u8|AM@|_F<*} z$!>48ewu{u?93{cb#>~0h}S*Sqve5!CpD=5#q@Q#HJ{B|BfAWBF>xjgcD0TUk$FT6h3w)g|bnu5pkFr30|+O%<1iOhDW z>x`@Mlxyh13+3(9299tEP|AEsJCf?(m-@zxc8axZ*|IGH-q+*7!jF_F5CGYt2QbgI zyu5r0=Ji7`ymi$(6&$va%L$phFXtkh8SQrO-hCb38;$Es)j0C^yQ=j&3UYD-9uLFd zo)-#*)?iKQ1Pk@i_z%{n+E3*?z&&8W%87V=dqcnFOssW&ABxUA#n{~x$g!f60>671 zUJtB)T<72Vbwj`HI0s>Ys~Y;vhJM@N^?W?77Aas<$TYNq%l3j)Xz_ZCumFy;0qfzR z7A;zYwc-3@*)s^`FpoGB4_tP9T$JrRyvDmj(vppsD56yZNQ+tNk*ER3~BeyN~@H?FU-iX{@ zkMBHzIi#HhO##~n`}XYF^E3SR9rgclU;D$pWVI!JF7{C{vcElSf9HX*p9DK>rea;! z$?W?C_F4Jw(3^xIY8^=XTHEz>LNW|WT?Xen2zK_FA_WDk4#+vl;xRnm$2l2|M#o`& zb54exfk!2ra=Gh|9OI*oI!Y$?ah%E0Mco@~?>b!D3Dl2MAx9fHms3vtcOkanPUC?0d(+Qr~*OA2zI3kjDF+A>&t4Y#<-c_(4nAs5p51f!E{n^ z?r~gq44y#l25z7G>L4akrlRP>2_AoIWd$GNsyC)i>D;pIr29`ebspA#7-P=FYF(VU zKbfWVE`N12e5Z-Vi+$!H=umM-2fbt=ZTX5w=*GA~!4%H@L9sVU#a?jSC;5!A@#|p9 zpBB#U7<)SNQPg>Nfuwc@kk3RW%rH6#iKKpEGXT*~vd2ti$ zFpVe4fXGC;x&-0Z2XEN3BMtL9-Zv79_FcJs>!^!fov_Ck>X#P=?E@~Fa4AI?V>@!XDkxvi#Rcx9;C)g-d|Aam)is;YkT z*KfG@gN;)bwkjx?^290UJ&$-<1U7aU4{fW&_Ok|Realsm&}HFx?1U!%yx3k@v0&rI zjcXN)Y*XN20W}jpux{@V82|>N)waq?lWvkt#RFpR;wje#*RfQ295R+No#vk<2s7+9`;$Kz(gK(3=pVapAx zC0A6_0p);p9Or}wdUUSGt@Fq}u=Bh~m|$k`A$kKnR%98N0Pd^Zzni$*?I_zd5l`F2 z5A6tNQkIjXAn?}rYunBE>htS&heD&{jdV$BDj+M%9v*h?#=1Z4d`|Uzv3rs5@bTv6 zb!}(j+^Ztd;ncT9iohhb>R4pdcYAh?I&Cqn6-^Lz`+Tx{k(>uRYpQ$J?a$#xzZCSwCc%$5)4_24 zgZ)``aM$-rvfWb`<-MBq^@>X?BdIC6C#Wm!h*te|(Z)66)9*J->EF52SQc&9)YOE@ zrVpQ>zCT%EYdQ59<^Ps3#MvuXw41*6%j;4-uL;`1yK1Tj?Ao$PaBjCNDt@?6+m5n; z1~N6GNT+HOD6`4VcLFy5UWFgD`4Lz5-M#3I6TjPCc5zK4GAvy>JLh+(s%pT8RU7U- zd-7Au+ZGm%@805o1^?Oq7pvJxNYwcNWZ|}sZMYo!mTQoCZKif0{ZZH>qKrrGBVS6| zvm`=e(lvZufk!`k|KO#Y*56SbjT+_g?GIkBXZf!WJ!&`n`AtoBuk~kEhNDAFWNgCE zE#4D}d=|GCNM#IHeTes6#IMDYfQi#vAg=*ONEASulBFPB}nCsciH zs^-3wADHX&t5)6n;p{bwS{D{he(dD)C21XVA=SCZbZ($0wH<@$8}~5pY)?%M{QS`- z`}rG0<6B~b*b@$&XZziK9<#b?9p=L4`-(&C2XbDpps+Fw|2Wa~*?qroPv>wGwek)3 zn|X#l=Idd;w}U=-No6EFES01jPS8JI9f^$EShj1_3uU{+%++5Yj!`=4fq}#37Z(@r zRe+%6d^V6JB|4;pMLUzd!6%{%G1kv3BBOozXKIhnN4IX>0?=uE3%6}-`*Irezpa&3 zf8AQSMqK^IhAD@4=rr~}j!x)*y1T6MwYxU{87%-&h_k9xIn7=Zh6Nym&*vMBN%;y} zh1GA@oC9F+l8TP6yuADb(RANfHbEds@d)ol{qKP377|?0+)$$caz{RwHm5)_dH3oORBG z3lqqet?Ii?PUa=kHST${3XDXfQL?i7XaF|D0Gec?Y}>YN0CN9qCMpU$jN_byp-{{I zDt;tz&WBXY4Yjum7x06*O9yw{(bkpofJuLlGu$&+gq#*fqXLbo>ClH3m6esumaRRoSD zCY%M}i?+lSyw9>^O78({ADm>?v_&Gl@K` zq2@H_w%PQCFGPM(WCx_Ik)7E*X2mR+KVH}{&JH}a{sWchAcOaKX5m`k_aD4<;@Vw1 z9yRHh(o{Tr^4W{vU(zeQI(DBECSjnBd<#H2*%o$|iAAiw2t*y+?XTBgqZ2mS_U za98h>xKDu=En4{RUikXp^<}%pgrbqO=_3r>Y^$gmaMQxs7ae^-k6ZqD(&;Oq7t2uR z3opEo%7Vxyp&>sM9)UaO%{tNT1mUJaCXb2esL!{3KkDpBPp)WFSUCRiQ-1k;%a$#r zjpiu)c0Cb9p{}^u__*E%kCO(-`>48p)F$*W+4~P4y)$TI`Oe!iaWBKM=n31aDo?oh z)#t~wFDx4OXvdDvLmr24e!3e&)Ik#P1KSV}zhqrB7)fv3?^8a-rV4yHc3u}M{Id-h zW6HpkCFRF|$ZE?ZvOu~D#~Oo{CG*#B81><|lNPlvYVq{{oqO3#!~-L+6?C#eHjn~P z*Yf(}evD&HlfT3OTtoK1Q?8TU7S~F9Jn)ZyFX_49+cl4-LKYlUSn|Gp{bP*fIicgV z@Pmo2?GM-~Hf=EaK9kR8e6zgG>0Nu*ELpN7Ur>{WtKgjK)~%apCXljD5tZ{=yzrj51zug_Z-bFG97;N0F? zSs@cBmwvwibG!T4|Lp&ZHJWrv75+3<cxQs%tZ;^Vrm+!R^P{uMb&}i>r7bMk9m$_En6xdJR zL`PtQFW?%K@A(J$M0$VJU2f^`(y_{CzFd0n;_ueoMxUxkBWQ|dk%sFdSCNF-XH4fjOVlPcjnr!+Ra|`)%EElO(CfNeYk1E zsHnJs(pGOAm7rxJS7K^O=GH;v~{EEc14zgcW<5E5~0FdXwTVF=te z>G8qb7dGG=cO-FAU+C$v?EV3a!?CE;|L78V(}*~kT5LOjKw(HkYJ z(45)6yksk{tgK{f+0nuRm}~6TCObx9ZP&J^6M47-wU*=5vzR2F%-A~R1LYl^+;F%A z;Akp<0a^&dd?=3piAZ#U$&>*)fbtfPyYadJk01>(!5oshJufSu4N#HTd&#g_P*hY@ zfN{JT=b!0u)aS^g`z2W4Ps54Y2Z0ZcsopcZigCyZ8Qj6=jUth}FVKORh}h)sz!C4k zm^I5iLpi`bjeJP2<+w%nkG&4%eC@Oc@L(qbuJ1$edt}y|F4AB8u@^Weu|7V=WhVt(I?^Ysz{7;ri(xJLOjtKX4Xo zg;=v%hpd&c!UcF_BVJFOUH>pXbk^~IzZZ5ONy2lGE$twiI8R7+kd1Rht~u!3L0s$Q z!h*yE-($y?5wMx)(#)8HwK+lC%r+fB5Ihjs29*_KbS-|hA7Rh^!QrR}(l)0oK=8if zZ{B=iES&$-Q&B(>^m$qQq5YF_Vc}z<>~(Z2@l*EV{EKd!HRU7(?LOIFRryFJia(rS z5OMX+Ez^Jf`jjUhU-rQvxMnG5nFuZtdt7~4EB!b&3D2IofUH~ku?PBs*L1zS_Vuzh zn>TOXE4b(MQBjYF+zxP`yl3GXCtW^k;>?vhwoOZC@rM&kg#U2UswKaC;qUkGoN(?z zeSb1xXE^K|%?>Pew`VYau9!XP)ivcir)8pGpg!0cgbZaDy*lBOE2jP9SI~Eg;YX1E zJ;)cSM~D=mZQHiO95I1CnJ?06Yw%u@7yxt1&$$KrtKiQc&+UKdYZGTuzGnJfQbulp zti1TO=Rdt})|8WZeoRVl$li7at`oAsY#0Lpk4?OX-?3*&%2Ighk~&k1!MK{cxdh+G7CeK_`zHM5ji$CgvYv8**<>-qep z?CjL36N|?S{rM) z*4JO`4_l^yISL|SBWizt+M(kN%3cVrU%$Ta=GjwEn!J3;d#PHJrl^()l&h9pIeW^) zrxF;9YwNGJ|yN}IDK4AhI5T;IQ%O6L>byK`Vu~m?=^Hz`Zzw-88t|M*TiKD zI{pfNQ_it;7k@ZG|43PN^_V4FznyX8+!?pv78Y@D6b{)TQU{NXpFi8diX_#k&RFJB zC&YeiB?qvdXFSKf9Q(?8O_f9kP>4EF@QX7Y~msJX@GOm#bPmIZ4c{Dey4$ebgn@wb*#oRqm&VH97`R} zV&nPhNDh&PYckzGgi7QU`NWe{!jow8YlnjlI;f#84>I7x;0Y7ZAU@~eZyGZhS0u=Q zi@EhK3sR`;z{x3Pvtpmmwu)To$bdX6+_PuTE#YvONxa5MT&aXk;)9{|36nap*Ae{mtq+>AWKg9bU2nNDpC|6zuo=$C5n~ z^Cg0{9nJ3`6dV|4T%XCwZ{UpG|G@v8&vFGvL^mv97=QMCWW3XpDUtL|@ zk&Ib*%QtuKT$v1#CHsa>K$Ls$u3ftj6E5tJF`kKkyH{kv-s=SI3KkDyT(6{_K$Rv?PNCXArW`V(~ zXrwDU?qbia-78eGgVbM2N=ky`|GlISgZCBTaLR1n0$-4=+ z_K_0fDBB&3v|jp7&a|Dq?`( zo152l{PpWEjQw)^mUpu)37_|I9c1Tk-gxmLo+tc4l6XPXBbIR@YA9#pQSQ zxa=>JHVO2dAoP}k`{%rI2#bwa_>rk1yQYHsHVZ*mF!{$hGY_Rr1%IMUGN7%c8PwA# zlk0xRFo)UOu)NVeH))qYxngl|-kV+F>i-rgLcr%=gjj|wz!#SKRx?GMMaVv{ceyY` zdARxnJdr(quMP!yx0Il{a-%&>$B@2M5&;Q}0cTSqTd_~qI$UCtccF;@Td_4CL zAI&|9y1Ymfk@_6_uZBu8whIwpKa!+oA_-`@d%yt);C0NbKr_*~=?C9oEJQdG!%0%W z0weCx@hphvP)Aw;`(gAuFW<#Zr#x4ch@AGAm``D=Fhor> za>k$_OVaRUrudIgft! zF^Rl_YD``6pXl$?4z#h~87~*gr>5<4$})DL<+-v;_vp7?zAM#!!_zwV;64(=*fdoK zx6V0<;TPJpX`{dE`h13OKvH`deW}hKs_Wv4dx1EUR}~CN@R4q;2nyn!w92GZBj!!yrU?rq(xc7CgWpjd zvNHiEfl93o%e)&+w)Nh|HA}P2Cs-epkDDtiWFg3Z%zO1$oc}6bA5?2Nt>f#||4f1P z?992A_lEmF^*>#RM*kUgb?ASu?5e4{H(T^S`f0Cud)nPf|6?JVD?av&7l0_pWC3h4 z(O@u0wo+q;4&2O(5Pa7xR9TxuqTv(_c-=!-B#(H#-f?(}!f0mqkF+Q5D@Dr)>;NK~ z5y)hWYcehX(F7R?Itm*o!@Rb80gh+2kzmf;!osV{%1YTqM&yx|P;91JiIM>yZp2ic zaqsUES(LOk^L1!EyEqlpBsQjPK_&dW@_(fM9KS97$3h)kaw|M>C-p^Rf>dO|L*^Fv%oqbs1m`ixD7; z`mpnwVi&1p=Mvcg?JUwAf25-bHaBJHO*sFhp3HXJGX~`W9&^}<1#;{VCjPMnw`LX* zE`a+N3qvtZ;p*z@5Z6hbzTbII`6e}7i?JdZmNU#9ZE_T zs!qkmtg7`HC)u^(Hk|kS->#XP?Ew@f*RYGjf4x1s4~xWvO0LnQ4I2Nmc&<_BH4RO| zZNkV2UoZZIY*r%6z<~3M|NeC5-#+@NBkT=! zvXJ*CUHglk+e{V{3FiKStKWR#K^c6?BKFUQK$-Eyw(sX$_4?GO{y2BmA@rju9nR}W za!D5Q7&x|ItX*U`#CyRtRj=CkBUfWG|%&?6^na|y6(b1=;)%@ zpAGM6KBS=GW-(H{u6woAKWWn=I9zz`+b`Wcf77ZZ*}gB(IAn}j?L-X!L2rz!MAyAyyztcr?w|ig?+?CRJ0sQirdSn#K!42_f7Brc{ZSUg z6bAyvc4$pBdd9@Xi`zoB@3S~Ak33AF?fe8gvfW;(nUzI`q1Hs4jLkNCT_Ew~B<7Q{ zlQB-I7r!y}p&WxYMv?+2cy8=m zN&SI21?&z_Uni5}>J@<)BQ9i@aQ&xiHi0R;dw;){qyYOQbzP}HUH!(350RuGl?6t& z*i%z;8xz-=q(~-cRRof@b+6_DJ74>m$a?SFvwKfnvGkQ}w{HmA`HMGioPN+q=GY>g@C*mJ5$eE^Ilbjp$ zb(n(rgsR&q;kuuM$4}*#Km? zumA$83q-m6)%)1 zKLpoVmWd~e@V$AJm6c(vJsF(OC!VAZijmz42q5rD2Sdm>D;eBzzsRnek`{(8;x^3D zyCClh+O=z^6Z|r{_jp){!VVg2tA%mw9}b7#6_92@jK6YBX^^O3nQl$!)d475 zKw)k4`&v$98B4C4s7=xp%>>#2hr;WH%(pHY#Bs)Ay^yI|>K^$$!+mIDDj7B{wcqGK zino3H_Dm3$MGN?QT0Dyb5+cF;{z}W1EjuW7*^C8Vb+UzW7R$hn>YnmywigW6!)T1> zCrr4f^GzgTZh9ZK3fgHf=qH*eD;?ecnq3gxF@~P~d`2h1+677j0Wx6Ju5Bh;(z#8? z0Q7<@*!4r?h%Tdwlio#8=1LZ(F)>*&HD$aAM6>%i8S|Os$nHeYy}0)86S=eYe*hM) z6crWCz(QC}C1BB4yp z5l3>DkA*21OBT5suQ`rKG#Y)7G-nBVwn6TVN82@QoLwCr+BY?f7;HueEANsBm_bQ9`$!ILr@0C@b{EJ0= z(4%~A7DDb14!PokA2@)yU2x^>$xl>-LZd|vLAR;DYr$JX9Sp+$xXaz#Y>vf*Y>{Hr zFHj@HC}k^Id$T#|&k3IAf(@(ZTr%z1n^dRBT0NZqUYpcusGE3vUZc+=sM%~2tCL7# zsDGSV-4hQckri-nA!~JcO-;7P0`?0gW}P|l$=^tIgn#*F?8)~`y7Jm}J*%>vjIIwqXAB@|#|$pVmeMJ4lvAu#t!XKq-232bF8 z6Z1tNc_a<(Cn1rf$ZOnkg$qGOVW;zdT|<(=7K)|!dN0X!cj{Q+Y=f!#InXMk~K#yxmH`0t4%W1rD6^^S_TqpJi{G%pOkDk8CX zS4RS%Ohl$F&Sb{>=ggG)Q)MK(ZLi%b2@q={(7%8GwE4*uX{J{fe>}eI!(Jb3k;SZ8 zS17YDkkP-ay6V2mUYqoa>IM>UkWQs~KlkYF2PEQS0;lXY^ZuJdo?o_XZYUbfsxIwK z1w^Gx3}z?Q#}$D96Kcs0ZyQ5!;XY`BG)>SX3H$!_(MKI$`u7JjYtleOQWyxxeS}5F zkTa$oKrFhQrXGe35#jd?cWcw8jZ;R;J)K>F8LPZy?kiX30*Q|X_eOTT9sl|J(vK?? znU6?;40g*`kcCL+b!99z7H3saGT9PU}YV|)BqFwgd^RHyP{>LJaYux{W7svWI+PSzTiy6#0JU5V?60#Y){718j&H!NNnQ52J8Uht*hM1`Atr- zZzKcbIjY&!$mGthH@NqmwpB6#l}TvW0D9x?xgt9m z+#A9etd(=f^h^fz7hZTFBds^JVnzd;Oriw^1x!ktljX_K`vO@EUSTq_P(gQ&Ek%N` zgu`HKk}Oaxxy>2PDaa#b2!ahh$d5B^=DgNBu$HN(+DF+;I zKmof&siK8kK)+BZ^Z=O?L>i4uC*pgzYi7YyLa#Q)E&NY9g@Z0nz_0eqp|C2a=}bPfG(+vJvF;X5cl;$q3>;6~59?v51*^Ws8B18LuLTv&ble=)8y@W*(KE|~iy7~yrr)|)u z!jOv*94oI=WdY!ajH}}IChckI0Bkc0g+F+TQ~r{vNcwc2ZaUXMrF z8f01|o0e>IPiDVdU!$1J3SqVADCd}WUz-lktcL> zfa3uBDu%ffduZ^P3pq~05al)1qXT5$aCAq?7lGvA9t;!f6<`DKDO=XLTn~t?ng9_& z?!J>^dc7XqwH!8*B(;GaB8xxLr56|Z{O_v*5dGLM>UI9m#qYri+pNnItb0UD<^l`@6FZ_Pv^jx`DOai=n!Rtd6iC0SXB5mU+CUYNr#DS+iUD*4B@7B)9mE!jLpsmkt zAh*Bq@-g(qD0@Gp@dlO8EjxW`BEDt4C_?dknt2N$rzgGjK$ zCpD(FiSUXTMapCrf4uhXs`gV?Eqx_jYrI9UPAyhQE+ES z{(%2r|J3@PS^S~rK06t)a41(ud^DtO`u)Z^SI?L*g0`usBPejgUnsvXyJf{waxb$1 zrq>lG*`ej!jwh2pV*>{{2Ok02|4%MkdI*dBa;@vfzM#8-T=df4?^503v>uYQyf;Ev zm;q;(6_cV9inq5oOSo~&`GDaAgj9%SgSp%54a zgRw1?I=$&+D{41I1bznFBx2$_lc``}Q%zg<5B0lpTx|ma1cEuu#wkhWj!-DnTG+#C zES4-;;w>sF@?qUj;2Jwe$UMsKAGjB_-9JPg*$iF$L58{BQ21x4u17foobw}4ntL%& zde~uyrL}!2p#tuDt)mwKS%OiD&@8fl&2#3QS$OZ4_&71v9m}O!L z9#4qu?-sQe$Wo8%xIv_lZKElt!C*dFy%7IBngtX&S}-V~x-Lw^we+U}jJENY~pem;}*{6#=c+K&v#aBbuDQzFOKb^{oLYk884e-5Wo zMv6e{Ir9AM@~~&mo?I>#ZVY4CO_1G9@H#+wmn^#op$b7_I9`OdCf-_#zke?*z;VaE z_Ae#{;7`9|sLn(oc`dsyfTHy(le=fBa1L6lri za@}54{m6wc{QYhg;RuyT2%nD68;MxSFjZ6&cPsd4gH3z(bi>}rKs$>+0^^r|(rfba zCGX`5v!6y~Br^J@Inyu4y$LYcMWiS`qS#bDJ$v?)YqF-MCV1s*lOL#xMlTdOj@{MO zV}3Pz%9D8M)Zvl_)(AKHtcmG3)|rvi1)@fU%nK3k<{B5Ps$hAZ{fy`(;s~IP1z}d zUD%m#g4dpCJZf}Zo_O}iNWDMf;inDGLW<9KY|6P}hPAtQ{qymqACh=RlO3lrm(Rlj zAlf)je!93fi)FYMiW~;d|MP9%&$;;Jf80zWP*GRZ>AF8z#}YJ20OnocuU~)R!9C%s zT#wi61&-;8&ELIy{haAH;ZNPo-7hS`A`Y85t2k1EwZ8g?9y`pij0ckkXU<4TNG;JM(zrSpzn@cpWs>C~*TscQEl)c=%! zwziYJDYA-D1r8MkIb-ZfAndw;l)}MtiBL@FA`6Q^98O#x1O(fpT_g;llK1E1TB9(` z(%St){}!F}SWLnO5Eh~tK+Y|d$Y#ulfN?A{W!raty?_7y9@UQcOkn}9*DJe!&{3T4 z{*hWZ$JQdjk~*{(N})_bgM*C4AFd{#DtRIwBDE;Ryn8Gd3rx~^lfraH*;Fozmf&88)Up)Lzqz)ovk=xKf?%;I0p7}Vx`!9HSq zZ5C{Aab*KEH`x(aPT2ikm+p*U%T9tlo=LsZ$pq&%li{Dht;_l3L4q<0kM}UAIxvAy zt#_}3Z8_y|I`TGe-YoS+c2~jsxn3;X7>2$-c z{y682J_|RlZWc_51cRS9ygTDY?kmu{0?F9wis{e|8`b*zUReL_+O_l1uOkygzFYZ< z%lE1r*SEpz^=$Ci5koZ@JF2VheQo8+b|lgeDfCZqesj!r?%Wx?ZSgxdxYa2I8`7c@ ze{g)qlHywqYv1Lhi+=L6-v2jrL@Acpg-Jbl4$NHpRmZ6-mcCLGjoA2AvJPY`{EtPmA7t_j z=P1bmpci^=x)?Y*ST_pQ0VIy~*X+nepo>_O%IMA?oNw%~#EwBDr;LB1yry|2k=2JT zC}ZM~vooYfom{ZrM?HzTPX9ap3Ov5-!(IzFte&3EMBrjyU{UMVt)o49^hhf)V2&V; zagijG2efGMny|q3Q01+!uS)D|j3;GlF;X5m1?4w1>y}0M{4yCzWE*UV7j{;MTGJMS z9l_j~;5Bc)bZ0s{2r?(*FK7I+wtG@zpLfR8*hPSP(#`MAxMEvHMOy9aR6%yF{C?H6 zd*6Mn53Vi1*fQ>mH$_bIO@fxUkiSAiOusEW^ciAqk~As!&`0kcy{3HUG&hT9rJbrC zB86ZQ^5U)QALTkx9V}9{7s*^T=4;Rv_;~wd78U)z&=;8M_PDahm~sZbCdN^uZs~%*yB5w)pS^qc zRwMzF9bNcU3K4uFZ}H;7FcoK!2b8!%uTt*E%4alMUs$&zBQ7g z{c6^f2h&ML;)g%w%tH>}smWOkzA8V9^G>>mAQgxk7xH;z4g%~|NnA`Dj@`U+r7Lh_ zfbD%72^bt?KCa{@By_>%#i$!^qVL}codbi|Maqktl12TF&Uh~P%^NS?wWVrL&ULvl zK_nXMw|M);=})d$+#7!uD1T_mb2@eBa}&g4aps>&{g1lLm^Y^#y=?P$uKH(Wj3SNt zA7d`b=zrGLnW?~v6)WO!s?hj~)YQ~G6$*unotqf^VPrZkEG#73Bqw6FIg+K(2qEEP zw-TGAJSOjl!{I0#@)>2}R*8v5z_~Z4jg73y-asJG*K~h^a#&7QGs|u$(&<2OzMK9w z6E^Ytv+&zdK1AM8oKk4P%&JA3qN%E?Dj&N!z?f={b#Ik`%6pEvvIx(MaIE!_NMt!4 z>q|;XVmv36C?n6ay1KeG<}`n!o8RvrfbX1u*L{Txz&Q-S<3wy+?}!9bmSnF?Rq~U= zN#|juHW^UyI#tCVG_sfTf!|$0hoaBtTc-aJ_l}9#f*phKek=St840@LvwTj^8@}xF61|AKp7iSOUM%3UlOT9Oq>G)>={5na~KB zpB~HwlkdeH38Bpi`npnjZCKkrthInz+kbYWuuIQDJXT;nl;QZxxz2^-I@nc6&B0R4 zo36N4>V^E<72VK*e(dxBS#l0pW8fDM)DuOT_F_8n*r86b-f`{UDN?4=14O0AzSvRT2@t$VQYAg3c`_W=JgFL+a?kc)5{hCxEU;pCtMQ*N>9zR#)D(ZvSs(NU78U!vDb%f8e;&Ch_mxk#I6ygoR7{V%(9W++>hZ z06wF@8|%I<{b0+QR}l8L8XFcb1ib#`g#rKa14`N~Kcany_2(UOcp2>(&YedMEs zU3p*1YW5sn5s3_NEAgIzm6z9i`oP(T9C980Y_|Sz_UDecIiXh!F$F*y())y>$4f-x)i>i zX^5Bjp7`XW?qBTKG-zklp3#wbJZXInzufP|e}6g?L5j0Z?%b1^95Kr|DnGB1DU8v& zb!#1Xc1@P6q9B8LO7hNCm?v6L+vCur9><;$UD!3xCP_ZY@eSIAv5c z67hT=*>e!Rer4f`ew9*2h4D!4KC%epnm1o4``hUkK7>Exuse(kK;qGu$B_9^es;CYjud7?TRNlDQ^gVzN<|H7hxe}&)cDLb&J&GI%ue|cig zg+!wH2=~Pu-KYw4&eepYU28D^u{XM&XvKoEOJ_~&d*NXN&N)qpEi8IeF|U*nmoc;^ z43W519+GJI$uzli(}vsAU9{SzMauFK3mjpdreihT~$>B!tq$YP}ChADIANQ zaM!}O2LI`}A#;QxL$+|InT{U9rqQk=WFO=a};r*@4iKE9sbR(?W1AypWudmMLBcp z;yGe`s}c9LZQGXie*8~=`jc=2wDm8z-~x6>4U&kDBmt?g6Qz9c`W6-k3s!V$)p}Wr zsK5N0Bl@q`^HF`w^YYhUmwvHrUAI)sM<;fNLSsh1^Rjs6l=C0x)TtBSl5Poh18?lR zdX3wHNpZN3UhE_OJ3o5+;&c}r1bu;pBt^wNH?{YqoV4|IIFFGEn05eBNs1(fkv_X$ z&z^dR8{NDpe<28#`#s(b@QEz<#l7XhfWIv036#kX_?g2NE{%yuX+=2F74y4aEFSCX zhF_~bC_6^K^|Ba$>UsAwR>q>G{{8!#mnJ5lV_A6iG%NoH#-4 z2#&a*hjJdzBwP(*ue@tdxF#hrV;#2$`ske)&{tnV$DA*43hd)(Fd7!XW@B%OrcGi zHoE9X-gmWW$u$idn+YS;;c)nSyk3H!-wYbwP-?nYRaM=_VvuB#PUQt5eh>M46kbN4 zG^I?@buzTz5nwBMIQQfT&-JY>S*@_Ja+S{7ZQHgn zFggc;P8zBV8u@U3C*b23v8YSfDQQd!{4;LXf8kz{Nyk{$Vs6x4S`)t#gV7T!E-r3f z73=XhjksoGgwa`j*=3h`p?7dC3>W4o!@Oj&QdH?3`J~0vLmJGK&!&5{OxB3UgOEwT z!FNsN9Sa3#<1q=}-KqBtolpeld#rxKe`RGQ^`Nit{#S77z6%-jIAnYq$V2M5w^}Cp zIx!4#dobkZJCL8Y4wfcCC-{)f0Qz0vzylBLh3kIS<;2NF7?asJ?qrTJ#x{z3Cf5|l znR^7}s!ZNedouTpuXgO%;e~T^8kx8dT=2DO)#`Bk+X?vYXv;BXGXFH(r_*qKWw>Wn zQAp|zWa?-l;;KDza|@iuF^)~XCXSb77exj_0+<`W6ZVB&gq(vo)+{yygf1ePcRBxX zf8u%$s<{_etQ!o-co>VpxjznlfdRGd`2Ijuh~ic-fyE_H6crWSz)p$EPU5;xA;VEZ zUu4%2GFPciaj7W&sFklc{w}QPShsH7qR&ge`UcO zr$x!btkN0ku0*nhbVO| zCNswk&%^5x9wz70K6~+tPw#Mxz0u?Klxa!Kr1^aX6|hk)R2`kQgLm@5`7^ zvb*M^ix+kOa_5$TJHu6@B9;#QtreB`OA z^pz{x&fTzP_?F7@G0Dys2C46Q@AZj)8gTL)o`Z0)fIuaUNuLypvfkhP-E+bcWSZ>J zrrkaF4;(fhdK<%k(Ty87vV(dYwn0o6%`RE8gdL+}bLYh*+f1Ns=)OT>ty;mQ0 zb)2N{p^{NjM`T9==%>NiYd(Fz&7>X%cv}R5Q`!VuEMV~*Wz_cV+haX@_GAIA z&@xK-6187Z>*ZVz(x>?BSIfJ9v1{`QyKAb4hhlaGQsp%@xBkaF)4zRa;IOIG`Gp}o zJ}bg6>cPz}sT%hv>X0W*g4R1lB0HFc1qD~WCRtQnEB@eqUf>JPE(!Vz{{aMNzOJX*WSb{$9wXaXTmW3-KNoZY5rUXd|7WeFHk0oiy z?~gzAGs=FEkL&#a91qNw7zqtXWYn#s)!5a$cTG1uj3}?JzV?k(tDZioTQ?RqMinbX z-iFJ=3ee94{6BR33L7XHoHb+xBwmPHu+jSB0WWRU#e2SMwR-0^SGs6Wm#RyDPOt#) z%T_J+`xh`ajz^<=G-CviBVKl?ge(c(HUG^aZg&1i6#n`awG0%@8s5F{YiAvL=q~uW zWZa&9LS0m=S@N9)i`;)8kESU%q3^aiuMOH%z5Bv&Bs|cDxPW^&?C?k7+diMC#%P4e z_dRj<_95rI@#r}fXXB>R!q*D&;zN@69B&zdHR8ftL9dkbF z+@3QII;d(m^cLp!U@gpGp~SRrmbaO+ZsqV@HC3<^qpo$ZWcR*n-+sC5$&*Gr200nS zBcgPrrjWo`!yoYYA*+Iu*8aQew#q%X2^YYkcv+RC&Wwd|`}%BYLE(HBe_+0vCgjS` z+7zD?jFXAfz~jgZYBIbYS&QStz3|;K@73QWBR@FTivq!g?TShkK0bWZ0{G5!E}PsB z`58y9m5!CkihLI1Gfw%xOBZxoSGMDb?Ufb7YNC;0ZY9FA2;|zgUn+n4l=B}`Z%G%X zjr@Qv81K}n6PzE-s{;uAx{9zq-qdl?@mR_&k)^2bpMl?`V9ZBvxnBGs;}-CfAKtdh zYd0S|U>*G4QqDsMsm(J~xjD7nF?44@=8P!cAiMbbSdhALclp3_#x7#+6h$R;#MRRu zUm832;vRig@jd7yvfHL`-!_Tcc84MKGpLg!Qj6_R1 zmzD+i9D5dgQ14R(AW{e9{h>eEHtKi$0P%w08=qm$MDzOO8!{`VU8 zcM=Iko3g(;kyQmCnk7;s!V?p5;&~?b^nn|5VPT=^cyr?p;-1}t*T*;I3#usLX(-;~p#*FbiA`8Dr#p=j z=|s|ro9fxafH7a-Ts>H$T84gu4ek2k;^H2dv)79x5)WytW8Mye9D4_jtf)vWW43hC z_2|(fh&|zK$W6BtGbZu9j>kmCXBn`>_%LxYcF;iw$?Ktv$>0cy5NdRxVn!Xz_#BeCBspoy%e;9AY8oCDI(;l4qmlXFZYbng&k z!n0UQEF8KBGHpzfNy@iD=b9%H&A(%*U>Kk=uM>Q*_XXgz?~U`e>bljyvAHjghn#yF z0#4V1gg1qr`ge#p{I<>IR@cqSb1}uf#d=jq*sRBYHyMot- zas3DG(?zxgAWdbhQ8r#2=N`dz{|oYZvSxjwU7%&L-Xk=e`-c8o$#*ZtEP(63Rq04g z0#4zt*t7o!=N+M~xvzanpO14ds}Fo8O6E8g)@=p4NE2-a;BdmqBF@=-yUTF0tTL~59;O#76bUziAl*Q@Y&UKNAf=5e7b zi8{u5=$!X+e$hFa1iMb7P|2BOb@s8XGv=dfo#M1kj_Y_Df9}u>Q5<&$!9=2vR(Cp) zUvYe0u74)Osz4Z>Ytjb9{dt>7uo!!utJV77$N6@}y?hSGIv3}5Wpr|LztEjSCoNl6`stQ+hwqG3_N$4;`c_9HCrG`*>z}WZ zd7CoNC5sl8vmeI}F4_F;vu-7j7W({CI=3pF^xz=bjYR9VQTPVpkZ-X~n>NYMLH_ES zPn}jL&)3@OkhLE85lY7c-F|q1$7Ae^ zryj*U!=Wc81G%p|?EfYs7LT>Lb>6F^_k_b2H1#`724l%ldu9It-=Ev-uzz8$PrT`^X_suP+H+en!Ih1bWwbTsGiiZrz-|?R zLBHUWBqX`%-RYM`Vo@t6c&mbfhyOBQ*h9GXkShL=aT7;sjnV_WPE1Zq;VtQEfV`9nz&|IsMXgzajnVUM6vTU$LiOd8qn?npkvzwAVb| zh46#RL;#Tu!ZHitAeoFlIP~lVm%RGCaTgHkZIf3ndGw6ly)R{w3-{bV{NWGs*!iNH zJgng}cfC6Gb+>fXB;nDdRi^MKavJ=TmMy={ri-BNAlNDxT-3YtfMvh>$>Hm1r#i=o zq$#t$URL_X`ZWV9!qxpMqu~L1{kJY|@t#n2;r_-^=U|Wwgk37x#c%EIZO;l9z&)Tv zu<+?Kd;a8UCYfwVg|WH|h${YYC6_|$o|r>^tf9c-t(&Y8 z>6tixaI5yey?^lO?_myyxL@+2{48;mB(LwQQ-0OP9r(VoyO^V*^HIF?6FoBItYkd#mCCr-;4 zayiKY13icfymOF7EN=j!lEMRPDLdjhH99o8I~Uo6GMz?&LYj z?nz~?k3^wY=H&$zZCpLZW*?y)UJ?v0EcW_Wa38t(*a7RgpK*dJR(Gj`Km6%}zPmy- z-FJtukA&;?kt5q5I8Tujn#Lluy=MiI$tpSPHsbJgBF1a<9pOPd7j|Uu?hoJk<*sUT z8K-^E?TcE@8argzeC~yePqKK-nP;WQd1}v<|KocqUd}Zr3&%$PVLxM_ZZl7Y+v@c!S)Bd>)GeFOGw7MSjH zzjgW`=x=c3UCA5}jDO&&H>OU3?D@H9B983P>Ebc{2Xnk-!4^?JZ>yp6Xvi-@-#^YF z3jZSGU5%1Jr2t8-sm7qg=opv3xE3YqrA{qN5`^2?o@M!T^$$xa|!*3N*o zcepWwlZ^hyLd5Iedg+o!hMYtDLldp# zW&wx;RIsB^*6Fk@7v`0EqcOM-F-Zc8#%o#?lR`*mE}Z#fd+z}OYwQ$df?l_7-6Y$W z$bgei2ab|y%fw`!TrU35K_D8}_`416Ns|)uoxm6@CmR%nhlP?zMp!R`L`Sf_nkjJJ zTvstx(TaQmO1fdHj8H4=j*aWLP55j%<`ol6pTxY_u8a_8@rQI$I~>*eT=tp+j^MCl zyTb;~*HJPzz=^RE$G$@)5k7)vs~|g+a3%xO;TBfc=&JM55j^ukQ3_(Nld(5xh87%m z1D+nnxlW`*LnM+&bSv<^3~hf?TId1hG5t^wp2K~dggx+L z{5{5Soc{~^-;%fmlLC|!VF+GxL^&X+oYMG~3K|RoCaB1B;5e|RQz!27HJ|xDo+WDy zgS~i;DVw9oCxFj}A=9eyGE`MnC6!IGuW+B?Z}2lz%=ntW<#d|r_XwUZ?rEG!D0EW6 zVNvUx63qbIub{YEb&bLp&xUhBCoJZJi)n`G9K`3jmYN!rcpYoy9z3r`06Wa2WKn0V z$Ml}jl=IMXm#w3*ekj`+B#EqFzkUMb+d`~+%3#}M0DiA6^ykg$9QR6eatqk+yiD+6 z5+cU%vA8=)h!10IpT-)b5gEa;qngo+2|gm>43{5NWBdb$xHFFR{`>E9UzTG{R%+}& zVbytM9v~O7<_H&$D>2!fQtGB>_9D$)8o70B9u3{gZZ@W^17g=4Z6r*5Lcj z_qdN>fAqqRqCBHR#HcU;KNZ1#Qv=-~gma1D*z$J|Jn%rdXK_!^dy{kT&`?R2^0O=& z@B>fxP+ueyJ{(51$*yYLh0-&qFG_t-GuoQ0e5g?8heOYE;e{6_!!pNt7FCJWVaS^qIM0VIyYlsepXd%aHSf>?MUgt) zM9vM&?I7g#9l~x88;^T90dqjgJ&|YWO(~d|#Uce$c*S1(pC=30eRUu_ZaTdoF-w{B z@bB$g{->$mk%6~BU`ofL7PB52a@KrRFhRy|%`V8!DlFoZMK-i6&h6E!YV?2sq-C1J zLoi_Y+j*~^;EP6gY}v9UM15^v!{DFR}&*_+Bj&YtN1ABUM(EsMe*!r&e=@H*td*m@UJpI}GV;6n5 z!Ke@f7fXNFt9ARgg#~oLLAV5IkQ-u0b!Fczm3yve%4eZazy9+sJ?-(zR_n8(B;FueZJ)q0kzd7{iwbU<%4jpPU zhK~DhZkIHC9E&gSEu;LS@+HV zT-o=SCav-MAX|04B#nK-Q3S$b`Z#QgDEAkqSaRB7PJ2yv%jEmC53xR&02!?ehr+@D z99-Ik@ORZP!3t}EL0_TILH|Dg){Kj^A1RBS{!YyX4a9IPDc`8;H$~@j+9XUaV4~@7 z-gxnjJ)xRpIzCFUbI?I0ZGJ!Yq%-HyCLwWw!-p+(XiW^JeArU;&~r^)U)&*sYvTnX zk#r!FP%(%7e;IPtvn)WmTygFDUF%D z@d(MX8Cdk~y8jn9U>E25@7kht9CWP(OuBJ)+vS{QLX9TaP{eN@%_@oez#@Imor~V= zyQikIB@3rS^XshY8XO74l4SDWR_*`in_XLsZ8^-tVRtTi-ktMza>vQmlnE&Oq%MRWhs=m6Z-*F~roudjSC_A~vnIy`@T_-saNt1>g z)T`>GZhc;Yel(3qv*SLP*KO6Ft;hfVX9GXM-W7F_u>A1i>h@(8*#_QRv!=ts?>A02 zVH4ipb)41f;HS>%dDJ%As>(;ZS6o@|%k|^RdOyawM;+H1VH}43S?tFw6!-t{(BaQv zKc2+G%x%>bM%@z2s;jSlXvu;QssmK{t*lF;K%oeQRpv8{T~{MRq2i8h`L z%lpx?pkVy)gMW(q@zZVEZ;Sa6Qw6cL$*ek_@5rgxtB9eVp5(zE9idJ)`iTDD1cS#v zFz(}bW-tBz`+wS!U2H7hb{q5p5~I19OCd?nI(F>ndwj_U2iqiw`k-f?*tO>sV+NhR z3h}fsZ5t+S|Lt#oi}&l-?+3PEy>qVb17N2&{u$GA0QxrX2T2Vhj}ASX^Lpa1XHUJu zhJD>9Jj$7XtG594?YwR zNlr*=jF_?ajDvc854rwqRaMo*J3f5plI>M{ZcTQcZ>lJJ9Q1kUd@r5rrF=2$Tw9w@Vj)+h4J_=Dk{l6vVCB!ngZbRS8)r=!va6=* zUXdi~bitoKb#AZ2wyS$ko7}I83-*e2s(G;2{3mptHT>$b7z*n}%6gKCF?R90qmN&V zIr0hi__2$(Zn!PkuFqUUA8q^YxgKlQoO4?D?pxIQbp0Ms|MNx7J0<<@y_vt>9j-aA zvCnD!@4TP<^r_PiO56iQ_7G7q7w1Flt8i8Qo7UB(-lAg_xQ56xr=9=6Gyh&PdCtmz zPq!tkg1vMR_iN@Xef{;Y$1WcxqeG$sk^POr1O{>Xap|NRv6ptM+QqR6q_#n7EET zHH3TN3Ta=1S^%IOkj7#P|e%hGBR|l#Ghi zE5`ic3Dt9mJ7nTs2Ik_$i(_=;(h;thX}`hVd?Nn+QvCC+P2HdI`CBly(_^vJFL9M{ zCg+FhWa2DF_0*Hq&iC|~P#0k^#gDP|+BlZruwo)MAA1Gw>-~<$@m}q@IL@i|d%9ax z6VD}XTSSlJ-;A+qa)&In&TnDCEgczL=j=eC*l1(>NE-aXn%9L1j(MnqjZtM2$@>W1 z#lLOaHqOE8F`vdIp(6_Qd-r19EWjL3#4{3CFb`#AQHvo~N}jAxe&PjX$ChT#}@!XbP=_kI>_ z@Lqe8NQzfBw46K8+q^|ZMGuHXL4}R1y$0vE9`oZd6=06XE!%H51gDc1j{wHCC&tw{ zn8!7UbGi$2L3aLt+>a?X_&i*O!wK$&pDFI5j*%1+_MBN5&MQ3L|9or4sAv-zj~IBn zpyy#@&*?tg?k(Hha@SD<<}sMXLIdiy4CsowKveCy)ZgpspJzQ=SL!V)p@RVx zc8!W$_0#^}Tzlm4H{A5rv_;!%Dw`Zo+*Muqt8aJjsuXt6k)MpsQ5Jv1M0~%B4DUxz z+>hIFKcJt|uOGqO(Ry&qsRuX3)~I)_nx$VRHZYl)_u{&r4Y>Tu*Cs!1%b5S#w_jd+ z&d#6yne&EwY_d9sCJthkz27gMGsq_3d{oEI=iPSP(4`d>6*bT&!rH%=%z0_OLhb3= zZi}k!ypjJKc^O;R&$?>%ro`tM4x&)qiu(?#Rwez)%BcoXL!86L7p~sULfp3^~4Me;aGK$}+EwFOfc|Z6q)d`~^yH&rhpN)KqxCS5eZJAu~+c~dX zjzF&q^Q~aq_|q?ZlKvjbU~Q8?j>MH8d%r$x=fCYEPN0)eb&{DZK)UaQ)9279apRlQ zFWp{~T(V7Bb@iAV-h z%}_E!Kxa9_qTXsZTaSoO*M$tI3+A2_Z<_nU4$=J+*`N#T_% zS4MO}yx5Q4;HeiQZQwJtaTx6HUJ`!NP9+J#|0WXVC;jG)7jHMQwU=$)_)t;6|CKNR zJAkNIv>W~yc}V&qpxPSG^)=-?Zfxu`I?P|h$5%Y+# z*ogj!-yJ=4=~K%;8aaR6N~v&gS4jFmB34nCGh=L5 z>_cz&s$;UFpX_w4yQ|2Ks!0@r;Mk>~?)dJBgAO|9w5AZnY5y&Y?%Bz}6hWWxe_LMG z6vEtuU#d;JTkap2=rFrHb+GzhfVK8EGUUUW`zHmASqz+*zGpOV~*pOE`;3a+M>;gr$wV%l+VDF zm{8_XseCHW*I$2K+qFmKR!KCJ%cT4e8ZuW8u{C%z%=UtCNJJg6aY_E9f6qxc)KDJR zKKviG`;m=~YMX1EN;u^_9)Tx6`KZ_0vK>hiOw0JoPY)Y=T9==0BQb{}-PHt*QXU!3 zf5#~&S95;W{s%gpSwCqYYW9?^mo^$A-w~M-X zy({^2I{#e8q5{Gg-|a4MRR3cq!Q&6?ddu}k4_r+d#CxE~J!^|A_BYPXb9KCS{+ozvqaSMibmmsV1Y}$O{8gkk>#xZ9F*LUB2 z=VN;=ymgT<1m^g{SR%{L$WY^NlhJ`pyUL)pVN+Vfo^?f=(xEY4iaqwlgA9>KGwhhs)kX@ z#K#-(Z?`~fY;5B3PR7$ugdyMnvAPC*?&=Xyk7&K;NJIZ7>blfKl#ylxj8B}78>KTe z^c(woNcPL2Lx<{54q#;m$bVuzu;mW)cm(Aktn^03jfaL-O^D#Y-alF-bW~fEV#z;;HB`WYUXjol-L!qBzQ|+4@!1}~HC+gMMnCSCSa(BV zBj1lPAcJ_d>UJYLKWH68Bnds=)Y!{}HIW$mnmv2=RO39z5POtJZtRIM7?Ts(NkAmV zUL@;#(E9>OKG;bQOZajFIkgw?y?Zgqb^LJT_Kt?2#=|MGAJZ2O+@77O!nW^!2VfwJm8nTZXYyLaQBoi{i3IiJ_N zykCb-C;#7R7u<1Dw?3QcM?k=>igIi5;>BUiSIIai67nn3z}~0-oiZq_^qg8J>~4nj^dnZ>M_@s?`kx7+7N8m%^iAe%wyn7)bEjDPx}MI z(Wu#`8Lv$ik>3{$`xK-5u~8ecBbz+`wf`D>uq-xWa>3TBJ@<(uKp8v`^X-X~&wCWU zfol3<)O?e2gg%61=9`A&7&4KC$LHR-Y15`^*l$&TJ?X4x2X*OjZlT}bqCBiqtvq9U14r_}1un59hrmYRIDwnzTh!_rI;!`HSjsV!XrFZxfEM>?;g&gP~k#O zpZ0q)#s*u{7m7!XiFr=!-2FU`Azsqw8FuO=ZrQ8aMxZ`K)_mxgOc<}e4r>Xv&C#t2 ziW;?TydLlR0iC;FcE-U6Z)IK`2^E|jNNrAwzAV~_(^q}be&L4I#{Oa_=*znD$e$lQ zZt$5e)3=1<>iMWUNV{VHdmM2%C4U&Nl{z~+s$SaXm?0&;z%#-UEei^sX9sc7gte3G z^~_(t{`{sC9TfZhf13T{s7_uo|`&eTCKdC#MXSdj|FqZNur;QPpvlj}Ycf zMD0EFl}6QGlQzUsm(C~e1@z^(X;Dg5Bz$aHsCqr|i8Na)UMzWiS26 zv~^5v=mvkIUc8z6|JPrC9ooJCS3#)0eY@NX|91K>jrPKZzy$8;>ps7uu&|Iefwf)g zm}`yYFE#kUA=s%v#>QiYBNmHCwEwTZzkh#zkNx>SYJFK2_s^4L2N2icAQF(pAB;=W zA4q#pSOVj+p?BN%BN=}g)o;M})X$j5Nx32%v3eKgf306osMrPN7WWnUm4Df_=Qpoi za{W;)0!2y1j7b!9?b|QkKs%c`HNvG@gT2<0QD5H#us-)2t6%}ngtIO^cl43Rf1_m( z?t#=_Jmxtb`vi~0vFAOO{@8xUts2y=+ei#jlkz+3V{6s!;i@5lQ7&!P6@Vyc0Yv8D z&xBpF7z-@gibR)_Y`9L@8W~E44+8BhOaS9b??@!FK(&0!SR$rgfJ|PpVXw_)Xa@k> zuw#Q~TkeB#4~`e+nB}N6H>%ubH7FUA5j5ht7$@@r>?RO1{p~!m6PS~~#p3}QFbu4V zI%h@Pt<6e9J3o}+6UJ|b!r}00y#IS)0h~9Br(7=pQO9DT6!t{_&xCmgsah~8jmgxI zz3>d9K1On$8B+A(e=uF85>p9>?3+-kMX|S-} zHReutVUFcOvDUEdW**rO$dG-meawNWcsd84*<4muR)e5}Yewci&ZX9&z%D&30>L?4 zDeMXdOL4qG&_CHp&&{Ae+hcm!CcV14I_UHHrimngbG(HGgWQ){kfEL6A_-=gMt-k0 zAyGLmr8AsGp`JtnzP0nV3BT0BkSt{&j|2z$`hxjh&R(w>(EATB2P%VJ?VZ6TW;h$qMBCyTzg?&#Qo zYz-?Xo@>sdeL)>xCS5TY#(NG>);<8A!+Ev^7V6lK78e&YE6SDekJoj)ljjRJ^>#e2 z6!v%)LHQ0$BImxqVm=qDI!WKt2>VpMAX}rlj+VcW zRU~T&eza-h9mxbzn7}dN%!@BO>3}|)x%a3-gRq;L}Q5E;pK?%bJjeZp~USqMUz zKp)g!Pa5&B6T0>svA^-`SkmH_`v;vi7i+p^?%cW2Bok`nxXCcKgSqM3fF-!YU*=L#1)#RQ?Uj+9=S!|RLq;0WJi)ya8LdX=KXWSFTSyLQIWMhNZI_!mTw;=$sfC(3YTLDHXT4}B9Wg|Mk2@V z?=vjKx#Ec9hTb>ujCYw_2l*7zgbaH9??=~)JIB?tcafB9$$-rXfB3^6ysG#^7JlHq z7QzQvH2#Z^`&{wrq{kll@VyngLe;+#mcacUb?uuk-GKWTgbyL0$Ol9oc^D&Vo--Pa zM794lTZjtcb;Jv1*iu=s-y(P>iBh*@A@b?H`+ko&Y&8?;g-VEa*qr;cRqG|e)t8_9 z-0PiWAoMXk_W1`rw^i8_74TYE1o*_M=RbxRwT#^}uB;NxTp?}z!EdVTAC8#W4>Wgrr)wqzA-oox81s)IJ*PbbO6~O4k#}9?f;!|!2|S}Qa`4iEF6U&Zs$_y zdqE!s^s>;^{f=AJyLH>EgaxXi;ZfMv{E8SY>3lbKHwXj*-g`e=FwoQ=)4H(e;cJgN zVHG=#i$q*8>hYx3@6Ez3RrDQZyiFH3wJs?7y=NbBMkd`3>e7A0XpB2aLPU~;3Ywr7 zYs$x1!pv1)B-I^=zN~)@zv$AF5A41L^D)GNZR*h;7n0%k2Nl1e-7br2@Vffc9zC}F z@4#VqbZJ@oD^p)e%V6QSCx)K;Jmy1_@)jMj0V`~2giM9^{oqz@MsgoHvs<6dc&moR z*UC4)SHZq#?}B!HPH|CB7gq)JF|EG~J7g>txgTiVLSCv!0XU!fcIY(n$y3gMj6MmH z6KI=Aq$plaXkQ@j-a7wg3A1IYf+7XGNepMK$;44PAi z&?Hr_jB>zw7#)u&Q@y+gpSMsQ=*@I+wP|@+K<_K(y14ZMfL+0v9t|SO9Sr znPAMq3z6ZCm5L!2nHNg69}i>J0F1#DDEMo!Q85WK)52Mfr42K+2JVT-MX2r{Of2_f zp4vD7%7iMy_~MH%j0B5zLO}R2nfGvE5fs4OTPEsSS>`>JX@DN*ty{OcDuZy`TUxbh z^&A=G=*$sKW7|yjH*14BWFdsY62iQEURV_A;W%9}k9!CggEw^etK<3MRHKUUoUq4o zEc0`t(I^9FH8h4D26#G>`82ybl`|HekWrgmS6Bp+5&^n;_3E_mla6t;9#6~eIzJY0 zY`lB+?BTkwDH>cz1_&M}nfADO*u%x3;&?o+r<0q(XR_taoH^4KM*96h?-`I`5jc66 z@XH8$liKt)fD|UnG%EZT}fC3A&kj%Q^kmo#XfIG6!WDD-`t|E;nTGZ~4RK=u+_uqeiFF%4vAt-+t9AnoW$jrbEZ%@Azj_eDC zCFsC^;liu$XAqHl4)z|MRGF6brUQy1QG6TXKJd&L=Rc54qHANIFHpDxIY!tkTpKGU zx<7a3B{#t5SexLa^x%}+8kDm_b)-41OV4dxid+1bu)vO*3TbPwbB9Qx&_>%BOse_s zd2b$JnpD%FsQ6*{p(2>q5&Dfqnn*gFdo=xDENUgQ>lKF{In?Lz{J;m*G8i29*CFTp z3wt?>$RfPo$ug?q5r^LAv02ABkGSJFw!beE|EPC9vGl{9G;AB9A-_ zAZw>j~`er#3hCP#MaG?KbyRaI4u@+pQK1rsTipUT%L zi3&&tvh@26x7!jG@LC3U?tT)B0I7fJ*u6+$va1vPjX(YAPcr!ok1$EOSd=;O%!|({ z^d;AstFfOJ?!bdF6Kn zo_e+o|32=(o+CK#86y*Q9W|1&Rk8njsR#VOIWx~rraMskl2$jfBLQU{2|$G^*cNGu zRcRTa;|Qw5Qq|wixpevo2Oc!S@AWh(|AfiSxQ+)|3{+NDX3}}r1AE(Q4)4$B$@@MK z_x;a_0&JZ(MS>V6`excUpEOxTDq-3Iq$x-gVf8rou49KRp>Ijw50O}lDfbzud9Dlb zLfi{~f8wxjCY*KgW%ShuOKjM+^-*>JVkaQ@B0X;8DcC^jmB!es5IGxijguviaShIx zF}qtlxZurRbU5Sho)`;b z2O_>Rn?+4<@W*f;t%M5=Gdx-gLojJP6bkLq%vK_gv>+972lMFn2nbKWy>zx6Os8Ck zvt1^hBVhpZ(W-LM@AnU9fL$bl>q+fL9B0TCnaB(016#A%GWN>OACS`{AahsID1c(7 z17~TMy_C7QzmkcOESfN;SK<85#ow+Mfxy8jGK8e$k=w;p%?xbk>w$sG*Tx$!=HGhN zc}zQw^T&dbLXL(j>L8#fBrZhXgRj&j0a4v8K;dWbARokQ2f`NL#w2AP>0N*zI@ zzd*^uLM_N9|J?20wcA;3c4?6InhtxR9FhrW6S>gXVdBm#$ukU?fh?;2V1lk(p z`$-(u{`^TqC6j8xmEDCQe$t`SLfA}l56MIkp{I_mRtCvWJ@_YEC!KxS5NEZ4RYAeSFJ5@f9q?6!n2ZnoGp%dF z31?9c?S#9JKXvJ#ZoNh}r4xuLGSQvaIi}G_Bqw}>PQ9~5ixz<=md>{>He&Hd-?p7b z{`I7D{-rw+lF?UWZobvBuG1#LOF8duKkn3}CwA#G(zXbsth)Nvzkc!&^>Y&a`CRJy z=lXmpK~Vo6XN;mPP_5$$D)688nYas&qw5DB0qBDThy@kh@Yb|jp8fLEr4^yjXyG!} z?b-gwQ=cyGO?wf4`rdu_UBAd9k0!#lh{Gd#G0MVx5*EN# zt9kCsi*GFS1*QrY;c+o>SQnEfUdLRS`|S!#?s;VB$O-h*siFn#w@sU^N}?d_Q|w+5 zIlgO;dxQnb!{L6+rC{-fNDfj5f^WfF6O9=W1_piJMZfJoXbogQ+DT>|@eTTvql~{% zRy=shh^2$O^r3r{%Z4Ajkxd|h$f!e)9Lgdr-VYL{={z>4oKFX| zzmSBOJP+=L{}^`Bo%GoV3y`#7*4nSHgTCM=!4dU`bmt%26If+O{kn^?&h2sc_U_%= zhd6X#{`ytZge6$~!H#3>0Ea&%Nn<()lJ*71nu{`KOu}hN_H)E>4U+vd1>soqg#VoL z>NzZyVazx~-Ej5`cBp#vr1SoT{k8`7yOWS7?Xe1W_&&GyVPkFlusgyPEY>w5XUy6H z5Dv=f)vIMy5+=+JJlj;J55RE-!2#_SJB$@D={;ohVd6?85*aKE!9)T)=3{b1p;Tvu z(-4Yj91dJ_mWNm@=M)=9)t_G_gtSK>?`2_!r!fa;blX;Qd!QK8?_-t7OSWbx<7pFa zdDKBMp}8^t$p-j1H!K=f_&bs$%bqbb%OVtoC=A{(=JXn2k<1Hua5x$$a(n@#6XR_Gy8h$C`yQ*?FxB+HRyX z4)Y+aSY$JiNF)tRJXi-yr=pF6<%EKQg4S>{yV5yIw{0e~7skjs!3Q$=mKH5qETPkx z9h-1mX~<{5aVzxivikrX3uHB*z5vJ81NbLvW|M}HL80IpqnJn|Qt1G}J!hin3?|`m z4&rr$MSGdt7ahril0h2F{u8`?rLe;QsEf3rWrDx3)EjvZpeu@Y?b=DET^#fJB)a~L zW0waeG;!?<6;Kjc0FFI%SDm;C8I@EK2yh$rkYI6fv5&D<8Q%Q?>6Z+GwBUE6&pkJKYsS*I6nmURG8 zZ1(Pm6$ZAZUhta-s1q@{CqZ_9MfK+2P+oc{#nZ@mPL zOo0gqGSPL#iWLzimi+I~b0@S-K_?K^`O-B#$PHaC5%@n`2L6R=WiK*gYw$@XxccEuUR@I7V*eI#SkKo&lM;^bHNi`yadWADcPPRI- z`2Xb>r(IXz^G_`D`yXWS2Rnc0PPvZnxPnYvIM%UX;Igo=(Eo=ebI-C({9`wqzny*| z^%~7yo%YUf^`Z50+6laG*Y!JY6+3~Xq6maYXW%#4-^!p(>xB&=V}*;iZnE}89ND4M z$h(gpx|H@7?JnMD#R&4>Lm2z{_%{O3#hT=v@Yuj~p}|99NN z|J@X@8^FTNtEa!R=F1MSVc7|Ui7vi)mLO#0&1= zi1r8m>e{{oR+7X7vE7K?%S8GhZB_b%==<7SRdKJd0E;qN{PDYir^##4`*MbLK&jJ{ z@fgpl2;`z0{9eBeF;|0#XK^w!eFu-9GD7+enEVI3C7hxAA>(4qrMTgUU#uzi2aWUH zDk3!}FqZ=MS+e;Qa=t@1^TguOZYEz0czrA2V~)b6knu&40cci#?e}I5$8Y-nY~?W> zJD=$lp7ouIN-n$m7ekh`Y15{fwybcW_ye&%nRp3(!+-IM|FHIDbu4Ld%cH|Ce3yE= zW`oZHJ2CD|Qf0>!7BVwIzC}S%gYjFh?6h&vh%>t%v4!zwVa{srYL_3c>iluz;YY9X z`~4y2(TMCLWRmAXg6vdu+sG!+NBHwDJuW}9`ypF6pI{TEm5g#<;5j(r91k;DPAS#d;54)zM_hj#hL*?`=;9ANeAr9_bO7Ks-fo#J zvJ22pMD)im9JZ=J)f#mU_@e6rCw}ZRE^T-)C2Yod`Kxn|pMyGpz_r0vr&wq6 z@Sbf#>}}A00u0Cs%k{65{OO!!v9Pf3`tvZ0s^~y+uv}$TVvinsCLXEc;g!^Jg`M6w z0AqG3yI`_-M!4rgk{tyZEP}sg8_w}^VXxQz702mK7G;r02y5~j(QJ>YkHdZtTwR%xM=qz-}4% zQ_8G#>ndb|!jl*G$otf1?;l(l4O{75CBfi$vS_i>2Ia4E;AhKz$0Cq06HB;1ut=rE z7jUik)?q8Vmi*<0{Xev8&k<)I{FAL@*Ho-Y(qGwV3EM@{q&X2}>4# zWU}i|bBCkr2W`F}?7zYr-g)`v1>diFmxY`n#b6Q0%++5!hV@&pdGlst+cS?JFA@cZ z=Cx}+e{EbjTjp3?b((ht+{+#~ZRC;+7fjUd%eQ~{)+M${f1O*F-g0%n<5uBi*6qu> zC=(CaE&Q@W`d#7G9TTz-9>`abEVwtnaN*StGM9qH5hTIL)cw%@0_ZO>_%gzsN?I5v z0EVN{0W8{p-^$unb?jB%-Me>tqcO9&UK|K6SH;uWqTfirH#;tl{z?CpCv-dD7x(@A zlsWtx{}xS_*i#dk;5dBtKlb?tJ4*NFRIaDfujfm2Keq<; zzkdw7=q~EJs`E#t?*rrC%%zQXEGjmxcYvO0|>#J0VP1PUO};)C+t`uN8vba z@l3|bw08nwJ6cialp17{l#Jlj0xUv0OS7eOL)^A1j~ze~&eg1kWDZq2)>)|{*fW54j@T^iCc6Kz)>LQQ6@Jm zhq4?Chr>}g3>FB(06XV#Fc@SpQ&P#ix=;&p$)~!1SOw4Uy?Y>US7V*jI68l1jdG@p zl<4;D+oN=f;<{JyoJHyen3$NY>d+eafFq_YWM0 z&iV&+5;z<^Sr_0}=fn245gNRZ1vk0fJ&H)P{7rg{F&;@L_F2N!-{c%*VK9@p@TX3~ z%eFZvi-pNJPv#S>w%tuD>^T@~Ry#Y4^hTBBFYP+jmGgFChu1mgc&=?7E3$Nt zbOvw8GDjU3uWu0+fb95huh-kIsHn)b1Q(C$D9HVZ#mzV-&+kfMhf&%EB0NtloQu3fw27eyi$G%I{=LXv4F zx7HYZJHAu5yVzS1tv7H})%x2dd{&Q(-F!6$&P%@cR!#r;I zaN`(%%He3sf`akF5am@B{dVlwQLAeRdsuzf>+x@JB-Lb1F)tnw++(gi{g4TI!rgO} zW5%x6!WdrFHOcy($@>i*(Rah!)2-P4Sa39`Q@4>LdL6b+F`;Ip8_xc~bD24FCRuyK zOon^xW5b@ZF~S0rzfYbr z;tA|EGKnl3eGwYE2m~)>@zI|L4V!D*CF{H0yT;Im1N%Yi@JYcflDfg&x{YKpq)-GL zv~lvbfV+@%$%xgmlP~-^{;gZL2LJW>ypC7Sp8UkFnwondebUBIQXPp5yK45-2Vff) zkf1{69OUB?dF&5v#D+5s?N8@xH%NilHErVgSJFpFQl*UOP5PR+ma#7fc2-we?#o3F z|LvShUtzJMsIyhGrT9ai2cMZNA3ha()?w}2|H_SSa2^-g?45*m$$~@$u~`{#jYysL9)n6F|(c1+R-H^>9n<>$Tur@zO18KHD=k)4qK9Xs12 zCNTd*s*uPzbVr9hps3{c*Zyn(i$C`Ahh*7)D2XvJ$LY7NIsSlyZ?-9jtcgZPJ^Jzc z2kR~%B7Kne3O*-e+anhD#@!&Rb_vW|vLy!gDAJsy=dIQQNqPs}!}rOSO_#=Eaod>1 z-HxJzY%D~R(6SzObp(E?$hAND#cFnH6&Bc2Q#1PSix;lVsX5M>`AGmk9phEBLZ7y>fya5mI~!IW%5mKXAa#*$U{57m)t zEEyh|;Youu1^0kMeTY8c^9Fskw~tNPAM9PEHaofpZWx;zJ0! zvH*!mKUu{b<%CN5iE^#5kQwXvCSlj42kN!&;qQkn57!TN#T}4MwTU>5 zGw%;F10Hve)x_P zhe>8WkqADd-Nq#;4s2_|0pw2vb9pTIFXHC1FKIK3oxptT{;@k$Z5+EHIuy0I z=Z>Lgt|W_|CSS-#eL#1Kg#IH7)kgmGXAio?0PRK+kL^#x&afV9l{}EH{lAuF{H}$s z501plI~I5n@>eoiGr531;w<(>NN_FCUe(1%Po4V9C;eW(G23c55*B?Gm7DFb&sRlb$tlqB9lb9M`S48?O|6b?#tJ| zHO;E~M`B<8<%6)*q^~fWi!y~`KPO4d-N&7}v`tarIFWr|VXt7LFJXy7pMT2eBYv?a zTS(CeJtv?G#Uen9f}+X75bJhrr>&YasTF*t)I_hBqVL3znpaLD|&v2 zEcJ&tNlvP(t0U|l!!9zy0-x>t_BzN09V<(hEg;8KIxlkD=9C>kEZ2kE*X?1G8go`pi880LJm(C;($gZ+EUjsX~Bk0Qox6uID- zkb;Q2(5^-gsm+e#(_?p>tqOw2wTI5EDCVKND&Z( z!W8TPBC-&QMaDCA>Qvc68ViV(YJna-dIX^W9DsoNgTlV}gV%i}Sr_p#N(X>Q0=5Aa zoI@EMO;`tcCv*nDmWIL-Of15lK=~(w>j)@hmEwSNVu2pd$+&eDrgOh!0^GV-3r-6! zjd0t9KAl7K6FwE9uYD|;QVb9Dffay(N4FVmcIu>U6=xLl}>PN@ML>&VY(rm zamp!ZdT}ok!1G|vQRUT9LKZj z{sBCU<*|xPHzJA7Zs~xNy$6qQ%a$#fU~N-WzZaZ=f(*Mh%$*Zx+RV*svua6v(VRRIW@Tp$;H`0eY31@PNr;gi@> zQBk2~YfkKsbU4zX2m4lb0)p&&z@`%reJV_hR0TM0`;lS@4$$!)QXN)|9bpRB;6B|@ zTwEMPP)(j^L;9hExzfvSPzd%1@x41NNEXWR9hsEAa^=b>=6FWBE94>JxX;Pi1lf?p z&i_d^`?&pdVjFw1yR$P5i>yS3k{(Ek!=xI>PX9aWzd6%}F_El(Hyr-+)eO!tfg{VA zwQB?BOsu)C-wCT)76gquQT*Vbb$X8*`@LjJOD6El;v(#c(#NS}UpCzjGWy3J8gkYG z)8d<&XjJ;ANWPN<95Tpky9`Cc{osFggCHd(NQa+{{tHif$-BHP5@zDxpBKM!TClMNaaQB;S3M9WR-+3GS zz&g&G(b%>whNKO{c+JqEL$iME=OP?k8-37^gZIAq+Myq8{`!?jEY?qC5M|ZXW3GGa z=gk_K;_$6_lI;M}zOZE6P5pndn!1SgX^J$1*Tb$t z&;h;46af$JB$6!-_D>>|d3Oj9P}`iSGOkj=sks6Esp?SZI$ zF43_aJB?*tmFkj~P5yEj6N6tm)~=w$oU9-k>&EV?!qQfB6rl(kM5FOiM_exJ?9~+5 zs_Z)Er7!Eow_mpE{xJyhfn5eP6S>HdSb|Vp5?H)lR#sMX{6Rf0eDs9jzr%YZP))jv zNutOrxk88z*hlD-LToImh|zKc=BgsQbchtLxn#+Qv`wV)AakP72~RQDZ()pW`1ZpG z%q#JkcSfxYhey4-;Tr~<)7cGB`!1}0PwgRD0o%RE=^qzKK*tAl%;F}?Lg3PZqH*kw z?I456m6&mDhhY3ko$x6eop4V$(FH^#)`8b%Ub-bOj_uNY?6z&&NTMx^(6T6RFpfAw zl=TkXO4=Id=G4TZ-FEEQp^0&GzXOOu3<8NaYKO2(1{eVSsx8@JMEaFdFI&T5;+`c8 z6EGEf^E_TVZm4b72v?yOAS=XiZo&e(os)d%!VCT22b|Bky>I>34g9;;@i}k(<2LY~ z^Rv1t&5uAb=SUFaT>z!F5Jvk+Sd269nQMeS#^d{|F$ZP!IUE2<86j%?j5DQUu^5wi z)6RIDNAiG8O=rLwp@Qp~aER>(a!x_9i6VgYxiAFQMmLq9?iH4uaj2Xj*b>08F1ASA zl1Y?gMbCvqUI(NBe(0fxl1ZB_9+GBjameOOT)(Hhyxc}9%*w=wY=SZb&8uq=%YyNn z-!sw--6eo5ls1L7>KJ6HpG4<{$WECSLvWP!MN!iZMx4u>)A+uXqgi+63kHK;U!upT z4GT35v0b?kHxnI)l9Y7&x+B-QX4>%-f=eS6=|s>;#A5DX-~aN*StWLc+) zra&^v@w_iQ^oS?jVAywqB~d}z8@9R>ujRp?pE?o2-3a&fY?6I?PoXVETcf}qFbb^T z%dMO~Ymo#J?F7t{8eklCWw%4bFRTM+xen-BR_j~JOhafL5jqHUMBmq2w_PhN@Qcn} z$LeGUkp&FHjX@>q#bB3S#aV3);J2avR^HONbu#Qx-`i+b3w zOX`A1&1je-c3>@VzA@+ok`&;YpZt7vNx-~7G7^mqcy`H>b~?_RjGR>Q2y3)giD%fK znZTSCvD@YZ9lI5Y1WZ3{#o+kQm-P8y+xlr3Y~v)%h7j=i-Y@b6-V&DBRa15EO>?GS zUQ$vbZBOwd-MjPHFC6p1Crm-TRCN!@_`GuLH}PaTfY6qB?9`EyXiHER;ogv`y$n7w zFG*$|`|SN5Hbua&H6~wwv`qSwKA~J71W^T8;Lz}_Js>PhOwDYB+LXr=0hYyj3!@l4MPhua-vm7 zLw(`jU|cS12@6~XNdgkStZH*#R!J~8{*Ixit%9tR1R)}aI!0^13LYes3Ews7^evco zx}#3A#TIdPZ%ih)Bk1)mdiK04o~JF#eI(n7(G)DN*PH0lk$~&h$Q%&X@`xnRrm$!n z3;bE=Boh9Rte@x3P9X3#hc52>vjH^M%CRjiEwvhB=#*KL zYrJ{$X4_&$Yf|Zk4#${9&z?Pf+iEJt2t!b(efqTXpI}j~P=tZG#l_H1q%AD$$-cQa2%n{orqCapLSGs7Y)Rgj4gw=X#G!V_{%hbVpPLe) zgkzCzYTh^Lda}L%#K~Gq;MvL;fr53dqYYEa1+WexkRg-6Ya8|eUaJlsWR+sboH@(H zL=Th+-UHcTqy96-O*+e#FJG>mW;($~e~zyM{C8}o0mpm#A^&##TflJ~e*?d(A3 zV+ZEYI5Jl7Wwx9WK`>4{_mQHi`c zp`1DQMmkGz4NrMoNGj2X7wJd5RjXEcxs(E^A`aVon!hJ$hGsg_ER@3H9$>E@k9{Mu zcJ11%(;qdU6UE@TqF%DhS3b^x(lWXR&##IN)1u@&2LI4Z5Q>Nddwme|p4|9t(Z{ zwbh?p%cNb}c*5@f$+@bC(yYMy@$7k5OoT1KV#pYaaYP=e025vP?ELZMr;B?p{C@Rx z8)nwM4v0Bd5(qrguWg5u|L^4U&U@z6^R5c|?Tg@cR9D@5-$(Br4IgI#2?j*M_RMzz z$wNcR8OSU;^MnoLce4OGIg788@d>vtE|H0%?3h88aFIFPiA=g)jIVF$ruEkeLvSDH zf8YW16^3%nSIC6FxpU`6S-A4&6V8}N|ADZCC+>-tm6bI)DQ&;UQnr3F#0Nj`(qkO< z495Diil~6lF@5Gzl$V#se1^7SI3DYYd&Xo{c{pJRhi_Qv?Pk7}sz_wmlqE~rvS_7?iD(*AW|m3!*9F2 zBv4=+FRut!4aXknhb~}im;PwMLNH+ol|SRgJd*?+x4-f7kyxI_HfHyh?QS9Y7Oq7W zKjlIpa08GrV>~1|4E)$eq^$(kfy#$Z#*fXScTd#PN7JdeWHR+XrITkPVw$Wf;PV*9 zDq>wt$f|O}5ur8;8Y7&1^Mzd^z-Tz+d@RTiBDHOp^s(XmWBW)y{_cTmI9?b66JUWz zI7PFrS~41q#%sYnOZu27@g6I2n&ualgJ17A&CkfuP;F5H{O%`_Ckm z9{F92yVd4_fKF15?L3kP){TwaS%i7BR&_zj*83O7{EA^+({Er@0K#Nmwhe`Y+^Qm! zau$Iv7J#(D{5CEC!B3BMwCK&n#I0CWRaKNuQ(;*ksiA_knYw=KJ(&uj$YY<72D}Aq znYZ(B2fF>GjlPP#W;Tlk>F5^Oxo=2W!}GJ5XSlv@NsLczaW=ktrZ5D)GZkaI2FHjX zP?BvOxtap*)zQtHH-`g(z>^}$E+7i&c3{hUPT7{|I-VQQa}_#^NCxcw0f*vhIJwv% zGMZ~nB^7*&(XPjs*c2g@;(JHXrW8&0Wys(d3m0s<{;>OpP@VX`_~MJ48*>dOvk^OO z2s^#8Uv+ggTU&dngS+WdaRefxiq-fN9N$W}WswhTljq=zDZpCsGcYOYd_eM1TwLsf z{lEf{M@@@@RHxQEu@>3-K41KiC7^swFp=8b3;63ZX$$=THf;79r3FP(TD58w!Co23 zFcY3kc7^=)t}5Gpu`q-u!45l7CsyauI^(&j&R0_IK8Ap3vRy!QB8*G2*P4K#Uxx!H zD}oYsm&>YkqY$sDsfoj8h!y(+>xCiyXYt(b>ZNV$K#mD?omjvdH2(edySI;lFF63+ zMG_)8J7NE5V4-OxismGmJRdo>Sfd^m312;9!ddVs8V7uj?c9AVyO%K`KS!8HCA5r! zT`LI!Sj0lcJdp%4n4s!m@<3QvBjs|;p@Vv%tN3%(7xjj2rMpn( z3qbPN3$lxcc>@p$=qXu2>ZpI8Nq5O~0O{SP!vgrFqpBlRMz4v4n|Sh7MI*+Yf?F08 zO#c0e=d8xdi1N?oluw9p4HnI_5V%W8sT*RVyA~lf(Y>0E_QU|^`e51mq z+lC_fl*bS$0DJBUOZ=omr+b)VrN|Oo!9F+x0iU+*;U2F&88C(gd`y<8)MsSh19X5j>N!hr|% zxLX*4>)pZWb;@ch)CJ$v56x>z0EP^wDfsw3batC5Pdi0Q+7S7Koz|Dj!d{5x}{L^@Dy0J#0(;G9ILF8F_cOu{wCQh$jSy5UOxh9e-MDdMfbZk??i7Z$ zzp@`ju!sisnPe~)c|9p>;4xL498mUX*1$H20z**t_$lYnE@UD~#)?%bo1rUdwsT)q z#5}2{LxLSPOjbXUg~T|Gfig`cg_5u#Tv|~0v`8MVje(8*&7jI}`wv>9Y>k*|7oT;2 zo-4=ckhUF+l3Iv8p&C*eC#$nbTj!9{b_<0e!m-%#qu+Y@Iua*gzw#p(?PHAL$5aFo zH@307&Z0S!aq=*B!y@4w)s@CR#X_Hd%8f_-Vhw!%QFi>vHF5yTDB85NZK>;$F;64` zncSK=czp4AwvWFTW8{1Kv-f+LcEqCWJ*jK&S?rq8OghiSl^;Gb?BGLl0d8m8pMZ6<`SaI-^%^UAeruZZ1^)5Q8Td)5A zciIJaoOD2+%~(s-c)O-;+qTv46I5UOvtw5d>DqII$1<+MB9ITZub=kRr;B^TP7Jc! zBy($jbOoGwGy=N{5Xh>m(6^mA3brWr`={LUvjJ=Amlhc;X4IWw86)`JYj&*9ecN}E z{#l{e@kNe-b{zL&%&#bu=KNl}E+BDF+yj{^Tm(DDdsqW63{emWEL6Vcto9u+mtyD6 zo$*4y-zYA|0*qvQUJk@?&q$VFe9w2+)QtYyvJZR0FU;0^e%L?KPHl$!ofqqa1t5OY z0x|0CJ==6xz%Fqj?=#*DZp@RQ*S9Ddl`XYmsv~VKjIZ23+3l3e#pH|LG^Fy{%9VQE zr*n}kk#{ z<6!qia<%xWK3L2Qolug)1byb+F~8Wpl`&(VO_x@kJ3+!{A9O%r2$FNK3pnlteY076 zA4oojF^UJgUb9?UrT-yD(EJKO6q7NN6|910_};|`2>8~nU7NBlAleAN;DQT0MMXtE zvUb5(F%}xQhm)CLhHK77<1v|qb3WGJ|6qduFRt;gjXWM{!oN56nY|vkKlQ)!-*|5n z9{q&7co_QU{ZR19%I}(EKG}-KJTd|)XKbq$F^?E9CL>C=Cp^TV=)e~rH)c3w;z3h4 zt$Lsl?qQ`Jx2Be16R%Y48p zy`W4KmRPO~ovhz8_<;O3X4qq3)t0FO!FjLh957fILM8pO6JZpFXs$Z6B&!L=A;x3{ zOqHjEC9vnnfDB*fp!`j{OsikqWqFJhdJfK=J2zJoP4s!klyjKZW8_f6d^>}>pJM9U z@BHJqW4`MIcY5LVt->7Ra9%6n5Dk+xTjX`n zY=NAM+?#kyOuiVyfAABMg-8CKU4d(JS75;Fd!Oy3`DL#6OwLc-4?6FC_s^f%!!&7+ z33!j4JaSUDu*2E{`v#vRVZ&L6^qXXxSV%{|i=8j6?S2N^v)$HEg*RkZj9vG$0mlBR zs#vsCB(cBo_f^0t_S@r5SZ8z3%c?5I-1p&}qqu){>eMN4*kOnHp!@k!8BG4MLCgAJ zFzDUAd$%tXGY|OoY}x*4cEe%!4|dbYMSXy}EsF(N9L(fDCUFQ$sKk*>?V<5#B$8~Q zeJao0Ll$A!6y8s_erH^4#DMm>y$;(h$V{5W0qv$j%Sq_cvGe-$+tISXJke%HO$Fls zOfayPi}ZQgFy1n;@8MHMe9m=4-;l_|Rp`WQJp2U(1%azyf8owhBw|eDKyVLkT~PG< z31?k;&Q(7EYeAI0?f(snJ+^GwLK`!zADBboii(QRZ~7g#s&jGaEy5B^AYZt7_4L`_ zt!~e*CF&X^J9;|p3!R9|-<>*jDs>0lO-w(0^`4t!;%-)9$&w|>GA?IwfBgZNqX2)B zutA%mlG%`(S<%T#v-^KvQ)V1jE>wjqBFj5X>Pe5sQz(wk|XLVk;8{XtAH)hYl2PQ&4D3mcVnEyJ6L{v(|pq zamS7w1$fJ^9?fkpk-EsEi|2MXEfAyL&aQoU8O^j$t~sJ}tG1JL5%z@_Uif3v!&9@y zEJv|*uy8WFPpbs@T+XqQ{yXWn?e){AJi=q-!>fsgs7H7aSCH~KRkF}AzVi4<{7yE= zYMobLbuO55B3VE`mjoTRzhPboOn<-=XJ0sv{s5AyxJ{F(-=8-X<2ui+T58&?%j2i|znP7%MDH%DluBEdsaA8#*9A+Yq zAA;RmaR2TnYpHOl_`}&=HIdX{4#?cDY@L}$+9B_kZLl8lYMID^WCs{q`lXT60&`o+ zM+8hwzE&%XnEa<;9IS;ih%5}6K|5!aGb)xWkW>XY)~up%*zhT1;{Zd^>bF&KRX$Mh zLomRc)VPohFOT#>@Ap*XvEK_*A_shLnPNcEdUMu68l55r!tDRZQJrG?fbq{ zmlu}6`hHq5nMHE31C55Z>zO>defxH{L7y$mP>SyyPNqK5cn1)(og1bbl!GW&92-{tPF5Z%3Z?%Y|xL>%b5 z!RqR2#N&L^ggG9@nEgv6l7%AJ3!?czUe>_+K9Eg~jFq|rNVeQl+aO43LBVXyb?Hmw zdKVc2Y*bJG{{1~ns`+x;j)8_>7X^O*Y!^YK|PBDr%$BrO!J}F(@7?9(p^Rc!&Upg+)H5!XP5O9?=v0}7?qWk`N~5z#)<5Q zv~K@2=D4&;a<$l~K6o971w?sWJ&M}Q5{6)c0ml=u-c;KpKL8W^cJ10F?b%%R4P`OB zWDZ4~!~fJL3w!Me*Vq*8b4_nJ{J<_Fo;+pbV~}yxxMv|2d%!;s#=l8Bjt|;&>_AB( zzlVpOIiqE;(1s*If_ur-FP6Rne;box1BjRTs1u7cz){E{{U7*UAJ!Rl2PWo{5Fy}r z4v4y>T|V-KAgO~KQ_N>iBHhCb(MVKx*vhz^amqSdpYWhAKUv@??$4x;&CC6Ru-TV8 zw=A6mn>%N_e-L`?c|V|kM;|!x%=sRBzniO|9cR-4AMV3(4>$~I=8kkNbPAWBT?Fd|9ln8o@E^FrWuMa2&z z))-~+Q*-eLFmD7;G3bPmhlUI@)(^N2URn25Yn_L0q6g^DQbyWz0BKiHv>tnc_D5#z zy?`G=cJFCd(88FkiqMS$8XMbOV~&!FDIL;?q1dpTmqsFyi&bGpN|>`a;BfrQDnGV{ z!(zEen1hw}QIUo00gmJ)viXTT_6CbT@VFi?YvACM3ceDU$sV_MA?IqsT^{DZeJ_V0 zL;8%YPdX!G9GxrhnHjUWI~3I>Z|eLC9626rc3$iUhsVv^fG>iRZjDg;N-isB%CO@2 zQ|4F|ND@gJ49S9>0E~Yh`*~a!w&jBqbMmeV4lm=m&MG1lqew|fNdz*>CeiGKChCuxJ(Lfdbw4W{4r@|| z=Cl{eYr^&Iz%ZI7BQxO009eYL+BzT!Jw~?QE-o%!jpr+c9ZCxd3ZC`(d>s+sFU0*U zg#5bShNJ_J2N1-Q#UCQa?T6+Bo#@2wY0b@7;D6_ z;dLh1Gy{K#w1cUudyKEe&h5?wW0C`Gsi`x)mI*saF;V6$yPE2`b|N70;6cutP4GpYwj6v>gOU0iQ-&#3nmyQAw%-EfXh(vdUkyG!G;UY-?ZMQXkQk84D8x-#O+57 zTC!))o*LK*AsjP2bm-7XeLaeQleHKxBP8_u+v&f2up|(8Mpz;ejrF@}!E29y`|Y=d zh}Gy$AnDqtW#<$;0`Tbw=VPV(7ElRBt0A0{srB9wH^x55W7J+>5{i6TLXv1G z6pH1T4KCx!iLkW!dVtQB(oU7a0_+Zqc#X`16gj>ZDiuxchx9{tDk?Vi+b-O=<_JYr zmvVW>^kbZ-)jp?+iZd#c)p^rDB9esFbvU|BO8-n{C~RB^{FBlH7eEd*i(C;6H7+sO z&63Q>Mkk!R=IcT9u?ow3BlW+qFb4c(%a|8Me`L1qyUrq)_Qi!pWa`BKE?q#fDm{1i zi<~m*>5#^alP#DKyJv?CV}~>bcQ^|(FnLqvc!AR4DJd!8&veJ}M+rmVI}1c2sX!;j zWsP#2Pq5uD#uUQ1%*W`;V ztigG%70oByvWE5%WGkVP7JMgNG6-Mh`ozo7o|A;Iv#5RHBI$}&r1r`cJ;28aO?AYOBa%?I{ zHPmnDPF8gLg>cW`6A7XW8}!BTZpZZ^Hc@;T&iNXXFRS8@5pe8=*_A{j_JVv6FH4|p zp@R-Oh#d_?63CW&T6-Qg!u}5@oW2~gHAY7I49hKnR;l*F#1KM z-o>KF9GAh=&E@|6=&%dt+a_0M1MxvD9?AOPR{go+_xcP;N)p=|?wPQK?)(COMeP0) z&z$G?`mIPhh&|z&Yd+YzcE&C5&bR{lj7-=>(6#_^jUe`<0OBN)x%-6|UZ^by5xEO5 zJAjzWKCjrdYnSnL_wL>-#OV*KLQgv0#+XOCwiK0SvufQWP`i&Q_kC_vG$)c&_ zyYxAC^iTVLgFU5&-92W`oVj0;K1Hw*C7~beiJE^5yXa28*K56ZS4SeF{@?qr-%(Ul zBs+odn$$j>>I6QHq3@Sd{`T8z=1hC~>Nj6}>ij9=pE_^yGf&rl{QAwQPyhO@7oWav z-mA~t{N_tNMd~|z{-|L~yv7b7SqQ6Z@%nu0RVTqrcmI&5!~fm-`t-w1_vHege-W=k z`wB%f07)0{6zy2l!iuaw6*y&dED*+&HXjp8hYT5#)%S(I8e9F734rj2ClzBLSpmQ< zY~kZN9rUEI1iM~st*rR##jid8>3_XH=j8J8auygCKrb(Veiy*+__eK?gTw_gF*p`8 z^JN8nfrTWo7R`@H%1wA|ZMT5Wzr3`xGzOWKb4k}a&w@+9=!T#+5{N?CD9tnPY`V^wF~~Hul?=S5^0OB)+Fa#%bA!agUdkm08JB_6qi!(%&nZA3+D>Ym&^!Mkm}=U44Qg=&-RX6LZtBu`l&M z61yPA&7%8kzaO#?uv1AZL6%Zy#voJsy0_Ru?!H{?2r60~YU^@O~beVR>XPqi`xx&T`2Hfr@G8$c z*OM+BiPh9s$&R%`7=lK1raOROz6%>5s4vnD=bW)&sOKD1fzoD?_~Ue}!nWbsGEJ@+ zK+0w~{FU>3pL-Mw;Q4RH>z>_l5LD3XKGvM}LEX}q-TUyGjKW5fk_9+)koN7{H>G2u zWfj&e{XZdg;K1Ln3p-pL3_nF%ENKCB`efapT5jRS?u1pw}kujrDv);F~Sf zvt^CT^T0X9O-*aE4Pt+mHf|=_@bo=_p5U3dY;o&|Vd1UMn_%@5&2P_(LI;Q!2K=^i z%(YVzmI28grES*dl3&r5ZCN2{W>xrDB4iNkR#G}vBmtdVbJ434KDptY={G;~9Y8$RNf9QH>!3}WHpYoPtQomyz)89z zLvzsyq|Sx$j~4p_R-`gylgtzm1zBVffEPAEfLo*(a;|mg(8055&+fy8Ar2^MIg7sD zW?F0zXSbvlEm|ZO8Cgz1TF({Rqy7_n|=4bJ-cUl zxO%iO1dBgT=-P9{?@v5s<(@rzbmzvXD$Lv`R2(-(LO&M#V~?%5xOe}{E&b^dy0Kb7za8)zSRw%i-WENqAu3cUX1N;l7jSgeQp$BwFs?!pkQ z3JPYc7(sK{2jo3rN0Wz!oVCDX=pXdN-Mjvz#!`I~K8UQ3^}|;qpQ9ETM1? zHLLao36Tv{R$pu_FRRE$5T-`<;4=aZ>Fpvcr zA~nuIwy5>W9^zi_t-=ua+AKH_BP&*{$mXgs*`AGwi9dFb4HQo!5YT(9va&J+;SyfG zdbLg{cU#cN`46X|d=iGhIZ4JG%&mMo-y8$kOhuL%!YM$LTedqK10HVzrcCbTg@uKZ z8C4|KX>%FdSSS>t-nmX#bjTty)a^maN*hH#-vMO*NG8b{w?h_y#nLI#b6>1_QXg{O$|GN+^B4DW8k}2 z;i-!-!^8N!2_n&nB!WO~G@s~|ok%u;lK~VWTM}z**z6&w%J98xnxCEpYgZm23{m6{ zE_AZPisk`%Ei~2(y#8gv0*Xb;jr}N}jL-;RGw5JqGV&n4b?a8n8(Geq#J*}av@9@SQjqG}o8L=a z0@ojj8vFO}??c?#zkJtsmvGM$hB&EPuaVdFJ$5Db!7y}t8wPH6c8TEokxPy}cI)w7 zx}2L#5lC5e^_bCby?7(me*vydv!J`a7b{sP?Q`ZOL}IZf7n`xD@$y4|w#PJ4Cl50u z^c3@M9zMU1GBWFQcN>3xkGP2R<@orS}vRTWqu&66|PQouB?>TerU_XXO9(Lpd9?N`!z0NHb zJAO-5?m@3NWAdROd|PE@Wh^3V zb{!C^tE)*?7b!ICiW-SUYwgktF1SFX0?s6P5?PtTp}S>t?I;;9bB%>1TuAn3yx6m| zrrL-MhB%<4^zdYSPB0!nyDJRP4h9Br8=fb3zBS&9PU6 zT%N}Eznn@0*M#gTkCWY?m!Ouwr$%G*YO?^H*8VML zukqUY_U#)}?`73Q)td`h9x5s-e(VmR@l{n-LuzVj=HPV=i$Cxj!N#5{fzP(sg6-{a zA1qn}MN7#xmBFO9A2l%ce6pGs$*`HG8=+80n1UNKWJ;4+)g7Oa1t5@n2-N{Z zm_j9oWj))g?cPSQ@A$LH$0#W&@hUlE?7WRcBFh~GAk7JK&%m%)B_e7^OSaE7CrpyV zT>f1hK(P94+@@umg9whW0HoQliqPg|7YbgVFa*9M+umbL_SyY|b`QJ7Vf?!bb1?Z$ z&X;Do2ePGaY}c+`lmSN0JXL_>Q3>}c?*c+{1?VGT*i&KH!dv}*{|MniaI!Hd{pmsc zW{pU&$2QyKyIh7|pU^AFMA~fDYN7*(nakGa@odP{K35NN&tu_jbtGIW4AG&aWTDc# znw70C5(OZ!ZY}MSs}<9e8{=VpjF>m#iCM65WeDm#%|&A#FIPpKKL7pVi1tbm2i!VtD`-3E^HW05}|uSJ3*;V5@bD)y(@?xABwhIQ__t!+W^ zJ;EihfU`Oh8O5$3qh?N6HFC=MiU0B5%wOFM`nsaeW-<;RY3Z8KnRsuPk}_^%($WgOnrKO^BnzF$t<6WCEn zoo8e6B_}%paN^-9$_hMOpiLJL3gAI-h{(W)a8J;A)~;Ra!*Tss6W0ktU{Otha!BV= zZFO+A89dGjl|w;4SAj8LV)Y2v!nfmjbNlw~nek4iVkFh~i~<+N5zk@0Fa*wvo0uCc zc6nI91*IzM*PHFeOot&Hho@! z+yU|diUz?%A?(-2^MOUI2&C3_+Yx!B6Pis>SP*J8xeZ{ZL7k?w)Ho-Q`lro&ls1nn zjgQAolYGkH3|OaULrT*uvf5s7;GgJJHozcG#rN9fmW0 z75=_kq!6A&BK-)=gPIj9R>#ybvXm`0r0VXXU|d+Fc=102YJnsHy(%6`Q|%_u|){|MaRirapDs zg4rY49RzZ*kV!W1*A;yC-FNH`!n`4wWaD65&4zng%g7(Mki%`-v@!a8|Ni~ih24vQ zuQ7FEi7&96Ndcl6$tX>_CKC1XF`w6GL?Ytx#WOM%P8LiXIAugC_kVWwru_`1+sFap z_4(K1d(GY+357y2+FpLI8Ph70+{rL0>KJUBIu{%rKn%m<9#0wdP1UVE4;P@5aNr;G zV*mBO{`D7UVXYX&df1ue@53&9sJgm3g!`k5FO8vNyNcj1g83D?=J21dKC(lHksjf( zE&^G*W81S2e)R6qBGIMGZBokGG336swQ`S9B2&=oTR~aaqeqYYoK7L+e&h2AQ$!Q@=Sw;nbKDXZ?EGwQ}+Yg zHQu(MsGPBH+VVMu+BIw10mSS5!Es!L4!D@0b+Bj=<4G)*5Y5adLb5s%_x$IP1B_#3 zYqxA~t&18`;akw1GnfYk*=gk)PKK=7WhB)BL?1)gLF(0z^PX@lW=b$!HvEu77({B$ zByqC1t99$vQB$3eD2s$jq3>J8j49uvrutDlk*h*txLkmm_r_~CP*(!n==kJZ0f=UR z#LF04sbk_y6m|?8W7q`*LeiC_1?^-hDk>tQ4;546){!_JUQ2YviWOPEVKOn%wmnS_ zgM}X>@i-gTK4;IKJvCUQEdJ0%JE;cQ`4cbVapO79#HU@mc1b23kw>OrwXSm|s34j8 z>?_fmEcYDpn(UL^g&}Z?>u8*)>hxw~oUi~ySLK>AIcuz3xiV*y{j;&6J_`_6&J^Qp zIK}SmrBh!XC_ldGgzU7od-rZ(gKY1LqvsNWi&kAgn9GgemoSCj@AoK}MyXaf*tFx# zY%NHKG@M$-MPh`O4;et*G|4N+!D0j^GKyvxU?gJkM25eq?o!GH4X6J;=ONq8ve3+8 z-LHq;M29L9H$`)V@~Oz6Bjl>FGY`ItZnlhFrf@jTWg-sNe;9J>d7QiJ!AT7qdmJ9C zn6wM$zh-dDBMaf&mu+oPN9*)#+2gI|n|E)WKcm+|Un~7jp=i{o0FSJy7xwGN>X&B6 zaxUr;0phqk8QZF*gVS%;@G7s&W@HM!(b9AnaH8XMIIkMYHq7Q!B~+(XvWoV zcbEN>Nj8_wo;2^8w_h6j`^9rkhCW#Yy{G`Tlq?3p#{-B#_~*`@tBXOj?a!wivU$8L zIp29~FUgN0 zhD_syKJSX1J9oz6t8G^P>-vCtNef#z(W5Bl`ck0p(MJ3kk z=*&x5zGKU?_s@T$H|Bf5Q3T>voL0&i44ieMs(M#U@=_j~`}U*nojjeGRo@2YW6a>W(F1x3XbQ3Rt1sDMe7 zO%P>=8D{CNs=oI*eNI!1AahPtcULw2_-ngon5wQibQyD=jXaE+B*H)d>X~%p(?Aa>kM9#L7 zVFKVFI=<|nC=tjxfptm2NmQgOd**2_^%UKC%7udk27)Pg*U&9&%jX^1ocDyJcBTl_ zxKZ#+{@CW@VE~E_AgcRDy+{P-Ka`45kNY48I%f?*v5{)`1sK&X=BZnRbePd4M6qK* zui06=%G7~`UW?{%$udX@{ZX|8NDqfB2(>yN{~69WB!6nGdwwlo!5lkwY^jd9O!BnJ zhoRG55@VV-R-@f~Cr_ZJ;AA?LN~OsTxSC~28axHalW_*1f&`p8x700l;m$X+kGcjcw0)r6ciNFzGP?Ru6lB{ zD7y1vJhb&7)4}rZbN>|A-N}Dzt6D}bNKJNkKAuFne*O9k`O?$RjQj1a2ehl17}6uN zQU*`5Nu?%zwRQ9K2`@jn=r?and+5r!ubqx!5R!crUAuOTksl~3JA@pUMU2m|YWAT8Z%wIJ8Tz)snTe)Dll5QBK@2lypPe3nedXO zlU|`$zgVo?Z6?aXs}Tn(CI9vJA;u!gR~s!#B=(>o62DsvM8xtiPOe`cq^d!frEBcOkI z&?e2pak6WMPwXO)R)uu@`GBKqc~Ru3EI1SU2K`_wk;TM~@DkW^rB;|J1KBf9LT zqyvWRkaK=}@?5!|9bvjCxX)h)jqQa)QpJQ1WAb}X#sw{1!%dU)+m zbVBC*z}KluNFX|Z_#sa;gl;Li<0bPFbRc5AoHS4sMkrj5^;l{I)4KrgV17imUdl1! zv>mEJS4yU?kMX$>HWdqG1Nanlnk=MQj_{`RI)DIDz%5<6)TG6kp>?KJkxa_$?@7s! zt>j!gSt=?jI70D4=$@1GE4;A6 zGN_C%f)dXeKWLiiFmd(RzJ0qvb1X!LQlZ->?#`C|UL(bU=G|B!aUNlQrlj0f$z*cI^-# zS5CGlQU?$>-^nL{D0VCwGz@$cBNR1Ann%daI(Xlvb|}MI4vB)Eogi&#S@dB6gpapYYS_Uj?38qtO$_Zk@0dctG5}X#`0ha zSm;@a$zTdH@$Rl**Qc1TlHP@lOeCCpA#b(EHEaaNfs$jzQ{`4g9%iRbojjj=NUZtx z+i$VAr0zQIloyBf>^m%^$SCL*NIpe(kZ&3rCXavV@fE**{h0@EnE&pH6kDmH@j^!s z-WBAKLk`&~xu!t-{Ep%xjeS?6I9$j}b$7=v`uE>P@g%M%mYR+rNnt7?QMJPCuwC?V zh(@DK75PliZ~%$zq2=|Pl`{CWZ;>4)U#(0vQvS0)w=heHhx61|f0&(5Ab8-9L!WdDKn zAr@+Lp*re6g#GS19_1jkW2FvROs)#cFhe2%J{0n(*pX$Q8@jP&$XAj0>(9@)wL`q} zkA)&EkjPke9f%7)UBCLBGhcb~lkv~~?T_dTLjGUg6@>OFPFR2rmh{d$g0MUQB)TCD zAr*~Q)XPNua{kr#LwEiIlKe1$I*aD@S|IwthrD;cX)9=-*oOUrm1f+`hToR)doz?| zpvf$0?`!3*sX&L2z|8FQW6_?SW8ZOoDy9d197qE*mMUqW)gTK%$VOx(4E}tM@6t_K z4>0Xa2kfCDX0QH?RNZ73kw|Zr?JlBnJOVy1lM?{<;_F0{R!V@*bvDqN2jv}JYYO;M zb|3?Etw+UTv7>OWq>#$_mh1Y#3Je(GEZYS{C%LF_PUN+BP&^i6Oi-lYDyf&1 zu@f9d6i=+BpLN z&U7seBxj1L7Znm(_va|8Dd>8_S^}#pJ@*Xh^QlB4v7B`K|6m>iOTX)B`?R|kgm9PkS5tK4>D99SvfJ zF%PZe#hzBSd-DT6uADqWv!j8VxN@Ela&*vYGn~4Gou|a@He5qa=39oLEdOIHT<;*G zD>;%V`I)rpHpd81|ABWe?05NmbnQ*VnWTFUXIQn=gxfk<+GoE^gN2ER8kxOwko<;@{ea9I>5yV1%VtF(=S6yH0Fol<@`+lc9{I!mX|!%c@F2lH8(d8qG$6H z`usPhhyRX7qqTxj+aQ5nl%xQRy<}=e9G!l(l&NEg2#Wb7v0<+Q$p@whzdMv$rHW}2zFxUjjmv#9fZ@U`KPpE}y-x-cX3Yt8i0{Cc}%!GPdtc+RA z!kU7>k?d*LR#huIh%$D@71OFt#~}yfYS~V33CHs{W`6l)$Bb>)sT_|2hB5v6ZDCp4 zEuS*k7T=L-X=zD5I&%C~m67Nariqkk4W9Vwl=~YR8saR=a~fv);4y0yfYhg3l)8UJ z3}Xpm2DGQ~@ndD=4Sc`Js2>M?U3LH|w$9;92-E5GDDx6n@e{OoXT|#FCgsA+O45&X zk`)66rTep4o@@h+^#X!H!QAe5NPns_kO1g4hr+7+ z=*}HL{4Se9y7L}rYmsS?JAg1SKhoqeJ9g{{h4ewD0UDr@(P%VMS68RyxN}sBq5DUK z&NrSZf*#h>nM}sSy)d5(40}hLPpzbF8lp_+&YhDKOiQAbAZ<`Bs3xb+{2f2jpC!cS zUAy#5(3JWr7m<~iTil3hq-&El^OQljw6^)IJx%+G5+RFVsDb&hq(cx?? zHE^Yuo`V*fXHcD&9wx$+B5ZPi+0c{F`G%=uKs+ArNcxGZPHAPRLl^;ChO8{c3&0+) zm3RiZ@8|&1*5jq|r~G|%iS>@}DoMj02@E>>sbqR7uwKlcgXW$UDpq2w7$d!;6x(jV zb%R-il_qS_ z8y_-F(6MhO5{apzXf{X8QHqJ3r3U9S%IX#B`VR$rzp3X)%*)fJ7!Ue&yzkFM?_H`j2Lx(5_v(xL{c=NH-AZ7CMal+qP{} zvehUkpZ%cmT@KdkK*!a-4~D5mw!^a{isw`0-)m_27M#fgdEEcSb?LOn8m8)2=lZ%3!lC)Lf=SQ+1*-veLz#$g$oLGRz zdo?@euwXnJTi3QXNRWsG_3EyzT4TeYEKs4=|XprKv#e^qg6RK#Bh zit6Xx%V6(_!K#`T*}vEAe9%UOKrEyK%Qgxg!`l_4{f|O0UHySe!UOmgz9OeT|@Vbbpj2F~jvXBSfi+PYCtCPM)o zWQk)5b3?-MO1JtswTpjG@8ajyFL~ZH8AB-&QYkX1;9XGe6eBQ+o>LccQ1BvdRcD-+ zKmkaH4jr`E-07N!)3FAPN67}MN*I1u?6n;a8VsMd8^^Thpi{DjlJ0Xrpj}j^7kX^? z_vtu}0pV0B8@{^$_H1&1SzywO#bWO>MbPmz>2!J^?R~NUr0lpVER+o-Ky(1{ZPKbk ze|B4VrZP6J=B}e?pk&v8YDX>JGDNghFR)I`=Zt0{htok*P_$-5R{`0pya=h3JAmji zWbHwQpg6g_Z?yp~gSExf4>)Tcx;rHjiJo*DM=?#{*viUE-uVm(G_z;VE|VqfML-7- zwnI1Z#dFWHu&JYn3Ao>Z4pWr$WV6z>@5KDlcFB36<8!&K%i5R6gcHx9bC0O~%SQ(g z2W-bXXA>v?R&{>k&hv{=D_NXr^mvPnXHHa)9Q+?8>avIwW$w+WwoZJ2Hik- zat#v`I3My7y1I)D-Zozk1@*P{ABP=Ol5z@3@mAQ-zPBRyA-K*@zAL5*rynwp`+v5; zK_~H7qw96h;2d|Wa5{|kxblF4k94gP0Dl%FcS4&IErXdNI#pFIrnr1wi$5e}5eV%c zY4Xja>3dUW4gB$GZi+za6NxL%f9tucDc*}tAh~W^Z8_Glc8u=byBqf}n%gU7W|YS8 zC;N0;i~ zT?~blI&yiuFY@PO+lHHSmb5$*vA?|N+>U}rym&4a2z{HIn-dS8^oyJSI&}0AU8<{# zPFm2M$&CN-+cgU&Jooo&*REY#NxzD*>^>TvI&~^v+`7{e`BOSwj+`j(sox9jv5C@u zGWN}ah1~wH-T}mN`iH%=4R>6Vq8%*AWJ=s;3+VZ6CGk(b05UvBN6I!MoGq*@x_`lG z{}+=)M2F}eb3)`L1W^tt0Y$;OK2ry=m&zEy<_Vc$y#iU$rP;S|&U>A?hXP>nagWa= z=tJ7K=p@@Ipd}b36(N zI2mQp(_E&G+U?u7A4}uGJAkk<@{U%9p6;d<7}fwEFyTu3g=b^r)h?bI@6maN_VWO1Kij=5Kyk87 zZUSZ758XxbocvjIwAv$d?zJcdRr0z?zkyy7Cfg%SPP5+hK9#vc4W0Y%AP1pWWa!YL z2ELp9ws$>QrakRuO}MUN%825mucrh8vr>G-y82M}q0 z`y7rgCu(M_CL^OVWS1%UuF1cZ`QnQ&cybNxY0ZB+=A^eD8!`S@$9M04MEh99oyAPB z0Ymu*YgfH<(K|0*O4n9FzBD-f^6DQ5E6ZU6a$MQQm3$E6-3=S6~ zRsx-$X`oxTZn@*~wEy$5&`o#^8HVkXWZAsIIqrHLKz0&EZ5=EAcjzpu({aezIi?KP z0E;b;$tSJM0H%l``}A2%@gd#;q<}g+;Z)sK=(9`l}!HY2RT?Wh6RVd=#?cuL?<$>YoH;{bO`=G@qg9gy zby5JsZ1XXJPv#SYWu?<;I4a&_8j2>JV?u<0LJp?Dupt~VoMEH6xw$u;N2ve=g$VST zA)Hi!aQ9^G&%3q<|1DxACz& zufHFA+&dhy!PW^-;kZtnI(b#J;u|;;W)f{kyd(diC2d=xuP=Gx5xxlbB$1LPv z7AZAI9arn#D7f>z^T7paL5*1VKq)8p0J!y^A+PxCBPQBh$ax1k}K7{L@!9jkm* zkZM{t+;9UcBfrqyBOOWbMUQ8_z-^N~d_Ga&AR^BH5FbARJw* zHEsA%(f35$gKme*LPEx4VB70-uS+xaV|3vlEU*R*8kE7;({(4w=aj6escF9Nw6os+ z`_IQ-ct-z25ARY@b7{ng%wt7>MZVnkSFd^JqIafWNda{*X9k2U zBd~CE00|sf3`lMa&BW%89Xql-z)9Xyp0_(g#>-;c3n{TTla$-;AOutL(IDPnsus$Has<=9j#WrGD~gx$ z+T||riF{(PEF|{}hsDE89cYc(DISmWq$C!cwb93m>p|S%A-gWL{h)CwmTd=N- zT_YS0yXpYqW4LksG-@y$kjOq66 z+wm*a08cFiVGYTqT`Fq;<1%g9H0A~<2$$z#g@fBfb=gUy7;7u!DDG-AP}I3%#R|DU zmuwLTtu3WMD*b)^?i}DUcT|*Kp<$NS+O**H!iP^#~e9(fPBi`}iA@t#SwZllq>tZQHioXP-Q8?RDj{ zLzZi_)(Ij;SScCKWOjFlepqsA4@C(;%E~CqU5}FqY?5);*A!=aFxj!3aZCiWcC5lT%)@%zF2&oDJonNZl) zCh742yaPx@Ri*O0LC|074|&}Gd6J(IR%d)|OeA}5*|No0uwX&S>Y^BT^0kC8e{?#4 zr0E1?%cv&U903vL3+YpC-Z5j*}GE=H>}zh6WS6;X(J&0022sZpa18 z*fT5?2UEP2slt%}U(k6d3qe>mHz`ef6beF;|NP(DjUvS}b96b5m`rbCt0h z=;r4*I)K=I_eZq`#UuHWDRf1(viTw*6iTrY+Ye=d9_isB^25=n zSW1;QP8%^VL@7c>;rhrQr#z2BVZ-)A7c&PjrB;3j#@THLkX-(aq8v$EnVur}mfil@1`X3kZrpUS%4f z^GqV4XOPfD@S?4QZh#RR$@Dm$DT3k*Q!<&1Neh6tsbv8In3Y5lof1f?Lx9Cbr7J5S zWTzMsR+*Zbnl#OmYndKUEc7cn53e9+>IFltAQv=^pG{6`(zEQjOdW&i85|dj#llGD zWqZLU;!?%a74{d_s| z9#^3ACpmOur&Pd)OlQgpW$ks@3N1m**@35$Xwsl5LC?*T=}eAIa5pCra|y zG_9UsWS1R4N^R{SnE)^?TdDIJAc+q=7!z&sj<695$qpbse>RS5bCuIrlzS8jHaxNP zh8u1`f(=^jCOdTKkZ5jhPTV}?=d(}@^2%8k{s^5xsv@z6BSvT`D*zDiU$9}-iw}PK z@j-MwD8%%t2!zio(o1aIK2wCoe1;lhQ*cq)Hhlhz#kB|+4;?zR~J`2Y)_?>_r}Ut zOud>>z)|d1MiJ1O07n$qDPh~TZAI04X^%z{R;H<`DRtFPhAr$}-C-ir1ooGIHEf=I z!>rf)QT#eaIwLOt+0{NH3*hM$hQIz__1lhN3J6nJ?2pHfSd~tv!Rl_6ZC!suB2Y(Cl!2zdHAA6eJx(?K|FBwW?MY@sywrDZ3MfGPc!C<-p2tWDWdT z;>Uax>$ohwHT<+UO24wQ!xnN@*M}qhGgyl{g?<#J!Q4k|GBj=4w33wdvOs6!#*M~X z->g(F2qw4AHdR^Y^qc8)00|(}_W~>2<^r=)N@TJ~={0H1eaiH@B21znV>A|v8Es^F z`sF?GxjUJOa|MmgCcpUE=c1FycXSOt$d7(liQ3W7_qaGTE|+#f+{4$@k#-(4}7 zw5<`e5go9UJO7k=&_)IwMMouNy%FP&pd;x8+&*Q60c`JZyLRoO^qqZ3j@``ihet^+ z_QiLxteh+tf%qIUv0ev41_8b)pw&sv_X}>g;Rd|%ain0Sat9`YA@tdzCpviy9;Cv} zS+=PhM3F__%su$nBB_Dt!}rgH0&+|Ttr!`Lo@fVFZgd5qAMr|&?+ghxEnr)v`INYE z$j{#T>&d_T!;CX8Jnq>2`wr?_+3vEK5q>SC-u1A}KoQ74HmrK_FH1h^%R~VPb01^k z^OE$EkPa)RB60~B6-LEsrij&>w|1a-P)X{EU?zp&O*1uefMP-J5rR}m=XlvcEN}Zi z#ToHtkLvw{dewE=WTpn}1&L(1H!eRCR$6)94eY$%Y7tXVd4_yE(E&uKNU))yp==X) z2(XL3vFcySdXbxM8(u)}(dYz1GAPrrW5?9}W6qr2wX*6mrU}}Xddy$HV)}gxKkQ5L z7;{_$AZ~GFuU9$4cCanTz=NTO$ zH)kS6k>?!m95%B5z-_AgGMJx92RB3vwWRifii!$!EDQaApUzY2#sunkXGc}9KyF4i z3?DB2NJYrjZa+$~|c|(>-C!veMSu zJ%Mfq5OlzCvv!IIo{OY=-wt@WcjnHe>d5)%n=gBnqCdo0phq$c#63#!vPd{mFGw+R z$?RUjDaVknpsY(-8N)|i<@mkOJ!tt6VLvkcF@~JCP`ny3!b<)?(=vIXABuzoxn{`% z_qllEx5~sYm67NgVZXHb`8d16gx$+YKSSmzF;!TGnIq)d6H2K71YIR83SebuKI(J< zfs^T2bVVR#^?Nrh$0RgJ35-y{;P*@srfEJyFOvjJ;JSZE=q4sQs=f+(-!chy>C&Yp zZE%^EmXhLk*l>OdmwDWoGsXy4-jrg9QwejsHak`T}(z=fy^Lc*N(&549% zrSDp`YL!in5$pj<&RZJC?nu<@-Me>5Cf|eopG=a0Oc7qRTHOnwJ6<&;55jJeoBar9 z5fbSD2CX%vgdY?E@4=a5%i>b{GbL7RTJm*5SLExy6!$#o)6mr$Np8SFYLi}&cN+!g z2PqHBK95K|9*>2?;py}{oz5j~1v1blN2Aew$Yut^dAZ;?0MH>LfI;af^eiP5?ViAf zjCVYbm?G07*hZgUytq~b6HBh38LX=G7Rp$*Dv3}I9_jd8FLnXnKNs0WMhDrXU=H8J zO(feY%Mu%*n|ShzFTO~TKP1_)W5>kTUw_?l{=g%?`2U8UI{C4Y6V7_(v~&9QtLb=p zWh6eu$OGdl?*y`L_2cxtafVzBkGg<>F;V?I1`7F9gX}hf^O3NvybxBkY^{S0#Y&F> zn(MyMfmOQ{;hbww2Q{?Vwyk^$cG!hdhGA-zu#5Zp}Q&qK^H26_M*tmAI&;6W2@{P%qt&m@m^&B>c*1d=^D5{O;U>N4i&#L=!#x!|hX1{*@N-puWuC5L? zkkOPjl?h@z#@?{U+8z{f_{Kje>{O?%ieiv7Y}IYswx!7L+(P?J^UZ@#p7qeretFB2 zr%V_%c;5q$;KT*th)%3}k4TuA@#CKR`_=R@M)qn1d0YzPm1Tx!YMmm}8H*Q)(MRa* zrIdf^IXuYe*lT?A7X3%|Aj<>z7~5($qx!Wu+-x8t-#j- z3f49^wB9X|D(urL)Fb0h?(1^rDRcNv{dj?$p z=bwLWlHkeEvj%1C!%Pp8z&c7!wkQgQ3Mot+&%nZDr!mzj0O3U}nC}!s-~}w@WS0EE zqjBZW-0#V}+19Lj%9>znYg3@tIS%xB8y%yhpb_d2K&z;z@Peh#7r?k7Xa>a}_eMV} zC-MpBWnL=N+w%3SjC{g|jzr3#C3GZZ(DWD&Rxud9=mNsZ$P1(l1Us-Ov{hb@0V#@- zGQUvQDp_7Fjh553bXYG|df*8@FyzTDQ8wg-Q4JKDE#1|3A<3f{Ol{JED7vO7fyTE7 zU3UkP;k#si3^K5vKnJ9Gh(L^zQ+5HDl zPDJj|p~F`+KKNJ&8NhunG*_Yx4Gl;%y)Dnh9})vdK3<7}6S`NP+=?Jx*>3hPbc+1_ zs?=t6B=xI9S4VjYR$OpBuObpvX4n64^VcX$Evdp=sRw{n_2zlAlnZZl=JxkpjyPpW zvuf#d7?tSMsgq1l^Ic-3jDg@?u&wf>Q@Y>O>eZ{0BmL-Ef^sE3U05|*Vo&Ta9C-+rA%`$ z3gt_gVUq4kCKwo&Wh(h4$TF8k5oRrs1gg9eqme-sG1dbbfaeD{3dCv*6O+YHML`9^l4TQF?Zs9`9P#gO97 zNup>BN{uXr%N4SLLK_neO8HKnUW7<)UIEev^Xn@P$b7PuR^CsfI!*ps!Y%VU+dEjxd8CWO_gmOXuq9YVJg2W#knyL5-C#M9cNOLn^uCsMBI|`=&y67AQ=kKu;%*u5EVP)hEjsym6`Q&riCJP(Y z6EzJ3%&?n?U4_%>wAHt&y>bVTL^?AV`V%W5p9tj2f&~kBk=lPP`o}<<*~*!nWG@$k)ti5p zZt?(Rt4@Kw1}u?is#&; zeOA`1Eo1yOeJ4Ja?E#OF)o-Fq6{Kd?`srB+pHGJ7bh<-_4(Y!QAA4J6IQj_FM9Q=V zpFZW``^ooKLH@4@*`dyabA$A;$YWnD>fM}4jaNBur+CE_+W!z!4iqtE(BXyvsfY!Z zJ3;R6+J4P!;$hL(D@gPbi3H!5BZ~Vn9a4MmC$oE%(!T7>dBQ81$tcAL?2vbQ-%dJY zgy|wMa*gH@Q-xuL?Vu#UJ8jir!-iqsK|u&7E}*?1T`XH@A8$E%-(DNg#qx=f<4--| zz=IBNAB)`?@~DI5H%;5FpnWt-{@gJ9xoynj;=ZiPNLc9_(xx>41XuLY5WeFwc8!p3 z2OLLYaNx$5=03MQLLq;}n9u=4Rli7EW*z(?q8nDp#La|!09m5(W&L_3;vVFwjXCf@ z1kCqDR;P_p%Yky4Q|M~Wk?#_7-)t9EC#_n(edB;qE%tMg4FG9H{|wf_;Le>J=-F!7 znOb|pj;9caJX}er$P@wnPZR*}@z|0p0HGjPTROzI`&K&yf7)CyR6)j)qnId znwy&=^z#U&2--Lv#5K~jnm62VL!gAUOuQD8)Ai|C@qjKMX%ynnGyD&x2LybMCxuNi zofQn?IauO_f+wvmAawl~popcqy4n+_wla1PH$ieTPWfgVgtk?>P*L<`t6q}R7OI@- zXH~|&S~_Nctk7kp0X|3Id<0WHI?yqF6k|-Zzf-QQvi#aT;h6AbMGOIc&TUU#D{{El;tJ>rUHk)ZPOA^W_cyKI;63&F}!TqvQo00&^0OqMp2e6 z@KQUN0LHW;LOaQa6%YKoHCucRLDDEI!DLu4%+X^}Wkat|rBbr3^6r?T`QAt8w{+G$ zVMrtrDY?QgKsxilXhgn=UaH+!P)G;1-Bqhrp#V8=Yf2`!&?{V3RTZW0?MuJAiK*jr z(ve5vooRr+^KyDS9M?eij4*UbI(dT*Hwm1weY&5Z*Pg-BE|AOv>@HopSjQjGSGfSM zIh~ft)upu7at!BNQVC^0G9VJ1y%-Nyb0i?$205^djF6i8?ze!v3R)F}Wa{ebKr5R% z;D7^CU|dClO;b}-^ZyJR`PkzlCyYA2SHB-uL?X{Hmze*}>U&Y7DSXSizRTRaqQjNv zSceWBRFgqa$ZC%`XuwuOF~KOANy9cQ85xD=MfP%N(4avf*c%&D+m*W%iSragHb|yu zt?!16&`o4jK&~5UbvUMMlf1ua<)L(crN%U#Lq$ae*Z1Gu@U`;1iyM(86qm#JY9$z0 zmZkOV9do^$JntTMR=-haL8pgaL%tQc$@``Vgk&j*`%=kdGBx>>30J$>XA@>-{P}N8 zyPEdbIE@qU2Eusr8x}@Pcj1P07pR;M``x`i`^A&wOX1{#&=*)41%Z}n*9v~x19bL$ zsHe;aauYr*5Cf6!XvM;@ub3kKy>aUh(Fv}U_GGTpf>9d z11!S~341(n^H12QmLRxAeq3hSv}tJ+gOKmOh2~%4ioqv+`S3|+-1^L@vkymC5VxcS zY0DgP)w{192Y)VWdmfi{i`%5OOpTXghX%Qy~1a zApg~EvLJH5LVMc(N~!=vI4mq$bS4>Z?=g*}cv8qlHZ(Lu$oaU-MJt$TIKu`hq`l}@ z!P+N`Sj1Y^scOb_SNGwI^!0#Y_e@(5ZtOb6Y&ch6hJu$O?tUdNf~-00eRk6?4B zIcrm(2XbFoK_fYIjKfh^D)ZhT=MR*!W0*S>D`=~J5J5L!w->^$udlag&oF7*dWwE- zVCq0+`-P;SMr&(p%Y}pa15ZXFrzdj}AM)+GaJDMd0j+s0Loks@@WcSp@jqkg7=TV0 z{cnT)dDNY867bTE)_ zu>7v2(6VLA!sL62(lhK!*ZTrf1iF3F>(>Y*;+^T~KeePgO{P77+s*W|F?H(HawjyO z!A%0E2%LD;UBMG%+ZU-O))-)vV}1b1cogN>$LzP?Hd-u76{8ej7%XiKRzi9gf}>!B zOxfnqOdn<2w{5Udq?k=kQ_F4C3CQ~u&SDVy5iicRx_R^F1jRmDt~%!AFD8$eaMAGx z^!jl{I69SS0*N)xfB9u6(hJ-q0PuAV|4vAM&9~{+dz2w6~ z1*uHneIghS6o4?%8LlyzQtr%Wc#zoY0z847d|gZxB7rsU`#;4Jc~Sa))$NoEKsGlu z4Ut{3rcIkxQa(7`PdpwEkscP_uzmYT#lzV?)zx!pTuqFxZp=hg-e54vIjcn>KBV-oJ28pB6i#BQVd$daRFs9LqP+br2fIQ@Q)?uV^}1_w+3fKLXs`{ zN{(fUpmRTsgnd$E3sD8=IiwJC*_Mg7R$@iGe-YfU;OwGc55zD>;JbI9=e-SB2 zs2-QByoG{I#)>5vTjn6Sg{cEwKyHr3Vo2tdfva*dOP)aUjOjpj0AcE`q}(iP^9(*05IL-};W(lteBu^+6 z6JsoBui-`~1x>eU=uCsjHt0xtBQFGJ!3Q_wNq>#dSZEau(teX;%q+rO3pto?lpqpg zi=|hA*(9V{00O6Dr}gXCmqZZ~Fm&ipBy6LrW-q2*gftIqedJ8S=kFU&oAe`__AO+0 zs|I!lVBO0xXQc&n@ChSmt<%wz=HFrzl(Ib9^RP)t-lFqIn4Uw7?5JB|dox9#+b7AZ zWu*4E(2tlf=P?c7yXl@Er#&Q2KSsxn9b2}WeF87!p|f?jU-Tz_O}2JIZBb1VBPegWHgU*X0wWOUbfyERZPF{!hL*6sg$P4L<2J z)dYh?COJ5qWt1{V|12rljr$?n7=G+KzbIvN*wrMsnx7>y{mgq-EwKIqW3vTeWIcX2bz~Hl1_uVWZt7uWnBzuDEp8OXt8A zt*@_-1D|f$bf#*e_8}cQJtgdTy&z>7S|GoM_@qt_XoL-$^bP*G*nU67x*GgbJd z1Bd|jWj3t;4?bmzNSfwwxi7QQu&KBnhz`hWwro+(c|!dM#R)wr?s4j3x*b5s#$f>f zPkfC=qpS?(BrPyWXNFag{^vjc$w>q-Kh+*%D$}M-Bwg99cxxXO00aV946!u4FEm_Fo zHrJTB&^sXK5i{PlZJQO1MCPg-i#_GFFTcd1{kWf@0{$Gr1xlz z8=Ye$ImSvQ$cQ<}3vqQw6$o(5NoUfozd!`1>HN0n7NX?LBh)4miNna@l}jio1zkfX z`9W`_F+xim)nqFuY;fk~twW)_Mhg5zs;jHx^c`1gTqkfRJ^I-ss}iL9BoR2o&-8n( zpdrf^DqTtYNDMj<3b4!iXYt{jgV{tB^*ISUNCr^+f({)z=rLJ)%)aRY+~*1CBv)^0 zY&?bRLs@VhbX&uq`4uMF5&2~O+EbWIcqMmr2pCMu)@GY47IZ>Kq-RRmUaEy4wjfpE zT|wxT*2uNKM<`6CimY9EoV{|18gtHVMdBV3UNyhSGdPg2CMj z<{U^LK~`5v2?pGzMMuIveel*{s`CyV70w!XBq(bwiUVueB%3yEV(Q)2^!Cp(b_>2f zX#cQ{kfH3OZb&BvU=GpR2#HQczJQpIR`1@u(_OlBNsS*gc*Bq$-N%Gn6oG7P-hTI8 zAI&*}WOc#jn3 zlYb3ePF$iSWwkj zAe`c{g!b`@_n88qx4*P(X(x1wED;j9d>|y^*UIbNv~u2^y{5RvDnP?FEIX!%hGcRQ zNOnQo#j>vGz{(_N;yb;^_a!;t3I9J?DkYb@`&fbBX1;SnW}`1?6~fIcmW8-Ov`p=E66z_V(NKC$tfQY zesEyW#6%DS6t6m6K*l>atyceLKyjnM2pq3URSbq<%teb;QdXh7T0z_lo!3lbtSrn( zsq>ovv`EOC6kNxAxr4b#h9!e7*9D}&g5Fxcem%(x^8xx^o$JSQy%$9+EL*MUnz5~n z?*grQvxWa93+-xMu-uDdRgDDR0YoJADFIbJPU(aqwrwF6(ko_R{sEW29U@gq9YBEQOllmM=;TBmt!4U#7@uk9+3N>&@Kx#j^#s%Cc_9u9WA^K(+?4^zJy*T^ z%5jJhiKP4>&y`%_-@2v-E!(ijN-`%T^0}gJ=Mu@Hm9H{IEZMwa0*VOh>gr0OGnSKN z4>rlNt(%od_JE3TbS~-m+#W=t9@9X!1Bm)?l23`1;Vq!QMvRbhY^PZ;ZIg``hOf&{ zx{36`(+l|k=?e!Ov;4SjyB7zIALq z&ag*yz6Zn!tjO@96M}&Dy?_u$%K7~&Yn5|FV1tU7aiHV$63!whkf7eW@I7T>n22G_ z8`H0D3ks}t^Wf3pBCJU|O3#9oG7Kwhw>2(QlGUf=z(5;sizXNX{dY2TAPe=_0}ePK z$KvFe_#r56q+pL^iXdC^&W;^BX6V{X5o8G*oYhH|Wd%*S`VJs`$axOWONQ=e6TKd0 zdN_ak_U*^h##!iOauhsS6sRNH+}=X>^AOVj9ovJ}%T*Krj*)W1?;)UxjlKr08>R!= zwD^1qfga2glemz~iy#5b`EXDfd_~aOK*6GDEvlSE=>!EN`Dt0< zf!5JwM@V77lVNGU%FvW~fN6r}-aT~uKIF(k@neM6Pq`4p1IYH5j$bLS3wpMJSxa(% zDRVkg3o zD4Nm5;CMXFS)|(1vfRf&;cA%H5^U-N>F1-F8s^d71EPmEb=0KiZJ`4v;ZvrV+(&B! zOedWvFkFlxf6syICtpbjx-XoRC~#qlpfPzB$!pLhSRM+%1aqs=u3ft*=^6c?Z!krm z^C~^>OS*OImLxklEf~(hbjlNOYHDgy(P;D%{2kN7qcnDX={zyHACv=nMMr3dqgUN# z7Kwa*A;I9m{49&6y4$dr4D7pr4f%Wfu#xYpCRZbvc=fxl9ZP|SNYJ~lVb24z{?(jW z-%@7Q2jFLs#0MVvg+f{c4C!Fm8i0=HS~oBsM9AbCmMxS_H8(e>uKwwXpLDHi_eZ9P zf7P!WLi58-F@)y0=VYbQ_lgdY=2=G`xrKs^%E>*Olg&eDZ6hh)FZ+iMxZXv9*6;(L z%Etg}j;oABrd)d1(JNb8TDVPI zF8(MyNZy28rnK9B@;PKXZ>qrx`HCFW2KzI}3ihjRryL__PAA7>UuL54nFSCs=LyB7 zJ#=voK~)wtT579^8SSN~Mr(J%?#@bs(XMiJfcRBYw?;j^wPx?^T&;(1~Ov zZGw4W$}AM5;!8mxrUSz;{J8LVG9b;D3|-$HE)qfhK<9`|}y_*ejA*WRaJ=*L>YGJs+*}(xw%Z4NL3(TqLRR02&47 zEz(C`Vd~(?yr@t|@|Ivk4h+PcHOLWR;2v-^zJuGJ-w^rVq+YO$f2{%pdgJZb*&gg_x$g0I9XfOXU6OKgADv?o1(~FM6!blg z7vCfj3Ea=I*vpwB(AAWjqb8kWPu*9PAoW_7O+t*u6GPx!bYBbHo(JC`y2o*5u*L>uhVxyY)|+wA=>Wo_ zf$6|0i)BJO++ltV%;ytg&6?!z=Y^7gIqCGLRawdt8FL`~Q%p}^ay74b*Ch4xNIlYQ zAB!s||7}mD&VPBuKcNe7`!Jw-ISMEvq<2LZZCrB}3n-90K#^uQOo4gb{~ddxdC8BD zUqxoL(%M2%Yjz~_dy|cP`jeaSfwDxj;v{HP>xl0tf-s`eF6mV1tnm7%Sr3K_!`%m^yVT&kLsU2tV}c2fbU& z^f;!70Uh_7*{M?}bMxlS%53P4t}x6DyJ|q-`)8m_5Spg{WOIhSvIB?&4Il!IFe{b2 zI?zNLPK&lP*fE(%^1?feA#(B^s+x#Ee?J8sNyyQINgvn(5b30)aY5o2Qw2@Tkl#9o z$RHQGXwW^S=;zCr9tKm8>{Oa}oT0Fg$>b|mtl(WhXlysr^((WqNzn7XoE)Y{P_Q#b zKgtBhz*mueA6f+-utCefo#&f%wn2k70%6SSID6W)YnMw>Q5kcoppiVXs;a8KVtUEE zS0DiqAHj$mq`59wt->8Uc8oyD#J#Y9!c7-|pfQjZ8*<$s=mWikWd8k3FZmGwGdj9Q z#k+Lrl1q*)b%&N?-^e9kd&R)$96O!j2mx2dZ-G#h>qDSZc8(&QNqNtp=QEzxQ0GmX zHU)0J%Xy205wc~X^z&IdiPaM503wXlAO;(X^~^%rfl>i_W|Jx_D?7s`6-nno11~^8 z5QOgKK&&aI2*WUtpa=9o*Kk_NL-CzA8r+DGj{#7+fL303% zS~o3xFUP1AG{Od!?XqEP-MTgQ$Y%=;e*0znhc~5%MWSHYb{ulWfQVJ~xwvO*Wc#0;Fz?vWRoN~}ap$E!K6)i$$6!zw zUBAjG99+o@GvmJwkNBU9m_?fsm+}ql8|VZ_>w+hE$0K27vVsPZJu{ar?aYGP1;jv% z00ndlH?6yXxj;T_u8_Yv#&`xV$mB!^kiD^}z(!&zins1Qb?h9~E+8A4ng*a7Iq4nj z`(nAWNC#bl&~0wt_U)C40>0X|@eh*Bgaw>X%1NB-cFuX>LlXHQp&V^+P9)wnPeIEt9~c~p#YX-7%p{V8}}F?!vUqQVUbYh z={I%FZM@$5J|e6@BCd2yMA{C*v;RQ>LC&QkzUBZ*@Aep@Ri*pEIy;AxV+O1pW+V zy^-$uRY__g>`zZ1i&Mx+7~QsQ8;U^_B?d|nDh0QH#3(Ii5{LorvpgX2!$j8 zgU_Es?%Gtn1z=<(-|F7%W3Xs$n6#IghjrTbHKhY>y9bS(o8k{mR!oa+-MUqtqwQbl zh##HOv;S5ykd%Fj=mPT6iWOaulrIW}149mgSQol~Ao1_h_1~PS+J(Uh1A|%Qf-A#C zcWL~u0Q*rB`|CC#Da&4Y$L-Yy!~>dM_#pj*xlI`8)sx_{uiy|m%WXfwE+AV0FUyl``D z9>CWYNP(CtBFt~#YZ3|G2UON-6#~Ds`isDI0dW#y(moowf8mFHn$qd>nI`NVdb{g; zcj{ucexhp0@6CHFAtL6-ph1JoefHTW1ADbH996R2T6x#(J$)C&&rRg@-G1!I_f>sa zY0EnLst?~hi)_R|b_oKUow`L#9G%ueZerkGj6sm|LEvZ7@pa@A9XobJ)q?hzCNi1v zSHJiA@#ItI`Gl}h12orp*JgBdCA}v4)%I=oFhx{^BhQ>(SGSpEb^N?OEXTNNJulnz zM`5MZL(s*Q6&E(mSL7I)_nmgu9155&Ws3OM)(tl!W-L0$7=pMtFp$rJ_ErOVk`FHa zuupR)rA*k^xuWtR5i`ab&@F`0n-e=mJTTqLbr7Zh+8P%sS%yL34sM4d~!OIw%ZCC3tuLSIa)nsHf`FJrpI+1 z(*T&1Nx)q|PHdh8QYb}7&Ud_U{Y;PDmIL=d3iR}FINYzMriK@Rh}KmlB`;*6(XwUB zqNLo#T3TB6p-uXYhK7a{!Qv{4K)f`{bc3MsSlNI^9tNd0Z$m> zP|!ktXYX~)NzUSoYo}pXHcVQh$jL&s?)Q702(uT#MzECRU7Ggh+fl5_G(nH$o~89UqSo)^|WXFH#uEnV6^u-AsC(3bPOetcoBp) z(7HumAqswJ9WV7g((BENiV9>2>DUM3NCzG&x}^mo=|URvGQo#FU+Jd%2jC3$1z@g8 z@M|U=FZVg1Kw^9!6o9gT6t6lv(BQgh9sP*rjdCkl!b7z7xZa241%g3-wIc-_M$L!xw)?6E#{ZmsL1 z{oxL>Uz2pNXt{0ej@?uS4axRTlVdVTw*PJPYh@=dK;L-<`HT*S-OL=F!B`mu2MX+T zlTSq=pRb;qV6#_*I;JWmA)pt5b(d` z?HA5++x^1{kL0hG{T*d7Np2D({Z9$SPGT=MM6!zoR{e{9eB4(d7E&$Ta=k`%STV(X z)_HbdcjYpwYehR{0x=~2y}bOBE0L5h3WbBB2N>wA1ic*HKN^xPuE+sUl})mN6$$*g zQ#^+0zyk)pHUVpkY-k&?%3=F--=b4+1h(<}7X0Ht$ub#0hE)UCjgG3>d6!h4cNn*e z4?T{vOEP!5j!pJmNHtH#VR-vacMK{du^q`!vpO1AX4wX|CR+aQODq1_1%xNe$7C(e zB9NXvdnW(-^D}O($zu^nmx{_eFFW+7D@gW$IL#_m-7N^vC2I=rD4Ougv(qNF!KUTI z#C01!uG=)p3a~Fj_R#a?{vf}1taQeeN&ksbTq`zz<5$Y$JWhmm=>I_kBHMU{$1Q+B3DR`+Z39D%$;kywWG6XjUKrk3VEK|YY&i;}$=sCc>5 z+mRg41c5I3?ct9L3!j|UeYl;omSKj1oDV`VEjQ0UFLMV(8X=eSa&6O=D@fOjAqk#r zJvX^<1%oVZLaZ_#k4G-k?<8u9Xdt7@MH*(5p!FegrE0EV{l7-RCD{ zKj<=nPNL)cAi)NOLooUaBeJ~0Qb(_==*2r21cbdej6_}-CNwWDbeqWA3PqV`E)y-v0SrCZNQkBVJVo>9Qsx4( z2!!;c`2PFvj|5pRL;YS*)kbiK7X>8^k~d%{ub}-Ag`!u{Jzqq+;xO7r_&vMb^Br9R zWTCCLvpWECEJWkRRN*Kl)&(z`6-nf}1+&h=L9QS9al9R^nYqpbCKj4wed+Tg<`U=t zQmAB^T>bi7t<@%e}CL5p`MXdT=`Isn&OST>I8TUjgbT@)aM!Q@UqKg!fm zPxj3mva30xj;dZvI==;GKRSmp<1RqocQ4jGSb_4-9!hYlTLw6}Hv5dr;E4O0Z2>vTGHIq5YSbUC~6#v8vko6C`W zlcDe4M8}@XGy?wU%0q9HN&neL2K`yN2&5<=+eo{hopd|A`Nf{#_kbXrr8tl1&qJ-x z;|GxJH{lcQ+p)7UvnqhV`5S9_P)@s+T9mpWtyf>0ZCsbcw@1_d_@4lj*fMCg@Ij!#oyDK_s z5AWMwEBUW6ojm{Qd7Sk!M=Zm94B?|OWLLhA*RH;jd4OVfBF&oTY9JZG+;(VG!O)>Y zO)wVI*k^jycKkC_1PYEm+_3WAR4NrGn}jn8`_&iWgp+L?*PTozV;`>l>RzS+6ly`I zz&JAfgmyk&*=|p2W;6^4p*NOweUISi#*{g=|A41;DnOyrSEfe0P({d^i5$i`4xD~`1AvR^ni{X`fYRl|J=Rk{l4TsjiOMF z1tT|yiDEup5WITz>e%mIpL&%}ch+b)GWU`}$F87{=86?7+z<}fl5k2D4!gwf&W(04^(B^9EVzM<31F+FH;?-N7^jJ)zbxjGJl0Kw$y8#}vdtBd?zv=}2Np z)8fC)4Wqj(0-^ipNB0qDK2LIN3!DK5dckms(fxI$z`+dqd4%}00g*`L;;mb^qWg)L z$pyZv?Dh+)tE;sYJ%=K}+x61BasUmWxiN_XI7bLZci*-P$NEEOA^ON!l99+uzw;!a zq}WWCS`4Jos4ptkg9sU5gPueRC-kKE+!PLqbKXt+#>FTCL1!iURX9i$@4JFt^;WB#_Oc;dZiWlJ$&1Bn)Tkva#!QXrFMim}F#80!KBzh1|B^y<3@qW~0L2bs`Y z{8(`3WD+4qD6TU`vQ4W44c+fdI&Ni4ON(+#Ws&S}!6rv{PqMy%aih!J?F`Prb5y(wXl1Jp+7&~@s2;+%;l&@^OX$~VCjCgkrmKRxji9maD6&ZjJ0a<5!tq}N1zY+rB$4|xj_LES92r?HeU7~)y1*Le(R7dcoe=I>=O~uF*@wGadg^{s(ycxK9Uqh zHzC{B8+!u@*=}SXCkXgikAt@8u)6|ZZQFd$hXSbMXovwVlI)9)O^$N8XksJc@vD=Rw`F<=MQQi|d%jCS7yz3187!}OCz9;`+ zf|LT+{hxDRyDDX71~Wx~UGl$A7&U|RZWH6^(&rU|ywPF2b?91UU$h4hYme)TgWK=3 zB_391Uj>@d>GQ9d_xka$dzsIR@&#FeNwLW^I#@nC;ry#A!qKVBLmbupz)Qhej)F6$ z+UlkZ&!M|Vq5}x+8Ga%M41o+t=YA|R6cUBxzL}4t6WM~T*c|=CL$wnB5}EXf%iezB zZ0J>k1`YDHe-^d}Vg$&Gn6dI-`>fcy`5xv0ywU+Zl-VU)Jd&pWye4FDCj8TSkfa+1 z>=_@b_aRzmD2(F;aw@<=C*7~je=oX6Tj)CXv!4C8R)izUv4rMKX8Z+jKYualuF_xZ zJNw^>R*djb#Q86r^}=<@jCs*}-+VO%a?V+V&J^M0J{AOj)UE)xE%Om5#u^HFP?S>N z(sISUi{^rOkdtBvf0r+xkt8Fd7-fWXn)v%`zq;45?e0twonn=DqJR`+^sE57jopf1 zP<4DA9YBf-c^}9%X8Q1|@DwD|);{~}lOB1Ho<9&Viv{b~@|cAniuHXFi0})B(6t1% z^HZz;rPZAk7_?vaKci5RK3bx)r!wT;przl0vcxU{0di_Mxhhix3V=lyvpu5ZgVNKc zO|xn8N@jRv<%}lWgh+&Uy@}GGf;D%z%JKBQQ@~0?!RWwu|7Z=J4j>fx4AINa3A;S# zLoXzOY^H0~3j7d8(0n;ww&dm~E`Xp%aucfS=wpVieKY-h8FK+y1VU?I7zNW%1kze@ z3ygy8V89NOLI-0jM$7sjy1&`u%`^ZuQ&!?Z1<~qF zYkXuPI|69*tWfNy96$qmFU|8u>D3jj=$Q|bDaAYtkxb#nH~H}Qq51m)^B|EI@QSHL z_v>{MP-uTG2isggtR1vB%#afbg^#zpDI5go_?u||yPBLq6(skhV+%=IvM|E;&rBvp zh@=ezMi7`zwr}5FK`#`C?qmqjI1Z+JzKF&VPBAZ%K6$4#-3}l@W94NUfRk4j&VpYA zL+R%!Oamw!gsw-=d?yF4UkrO^(JPXxMS=*rT^-FlMnLVGVmv1vwAC^2cX=|Bx115+$)bF!1}V0yTm9C=p> zMk8N$Uvb(#q+`X%3E9^)&6}A9ASY>GT1k#suaieGH=-C%dM4;hqRc#qVjep70h$|W z5Fs#iCSaRoV zcWwDD<_;l}y&+vaJRrs9j%!m}N5}@wvch|GA=&2@ra)MjY>)_u68`+=oBPO4%gkp9 zs`6S$$IMa++Mzc4o3IQVojA1b$yT0QJ3V8yAh?iGI0(UiD|_5)?G|%qCHlH?0i6l1(rz zBpmC)BH5HVpsH4@V;rzz%clQ*=#%;VnCS8@W0*d*%WQP6LD63xPK@H{AriYRi@_Jg$tK5FhII+&>6C6`i=ynZVK@U}=q3iNmh$*+V(ydtY2z zllXQ8T1U()NIea#<;a_PJF#USvR8R7kRZ13V=-lj<}ouumeO^_BjK-QewxBuv*61@ z?EI+xduYWHkQjg87mGOgDC{ui(;^V3oDgyS*rYd~y{0jp{saEqw5=idnc@q%{2A4l zh`eu#FEAZMnR>@ZAHi?WToU`)Ud%^2SZS=kf}N?~0LMJm`A8b|(bD&9SYogJt~+7Rc4l z==;M5b^L)8Y##jUnNx3bki+@56OKdHN6@w8fkhwnowH{73rqv?NMy<-hyIjz|Clyy zn)VuyYg4xa2q1E4eGlEaivr-bqNEEzFj+{5+f7YPShO&eUt#JPOvX@`L?RJ2P4hSv zMoPhy`4nv;CYXj<2{;9vf=Z_Y2pKiD!?5vR>3Fv7Cs-eNaGI{wO)~5i=o-Uzuz(f> zqPh<4>r*smXVYh`0K0^k^Dj{F?;3jMl}LgWfy#&sxcj||(H313MC+{>s@CaVPC#+X zZq{@y{o7ym+i$-Jk|yccqTXls?#hM7#Q+~0U1z2=_%z2j7qG3VsY%nk*X;s=xxm?b z!GePMBD)t9bB;T{UotO%#XU~Oi57#l4OQ-g)Pp$o1D>AB5)>!k!_VoJcZ@qhOFezr=RKoK9=;@~Wz; zF0{XRkq~T?Otf?gZ3BdygNbPXPF?}6`6P>!zP{;0jjXqQhDI z!LkK|WQ#|%6YqBguZkEnpj|M^cAhWAxPJazbONQjJz*$x57{eyk>D?a2)?lIpXdd>XK#LZ;910Lf#;ydh15;1%&s`SncP3cq$v3b21=$IsnMf*OLgxD^ z30V{{^8g-nTDo40{dBr#WurpK$2*XWL+_^OFyIAiJa;srqaDeo!)YEX%O_kL`Dw1D zXPTlP(=%qwFjcNi+zYpz4>;g}H0}2(I_GUnqnKQP&kb3W#eB>_GsuY^XK)nnce0Bkcbh2&XDuZF*Wdc?I4XRG{E|o8QJ5YE$!oa$$!n6 z)cEt?n07S^@5u+^s{$YAT!h{5mrv&PLy(t#?+Kwyk--Tx3=Mzu+O=zoLJENH6-dm! z?Sxa_ix?5DJ#j_D*8jcvqj!#gzY2RI6CHS&PteLI!HbR_qIvh(rmrV64M2DJ%g@F< ziS8dPD_`8m3T=6VFD`+E8(#vtBFSO%*5il2r>iT1fpyNhyj|Ebc=ocgE?<(uoT@5@3Uq&t24zY|$eosHjBl zWv02g*&H-zkjV-F=Q!5a*9Ts=M<;}09J?Fv_~6g`9s2U-;JZ7t^zm zgzCO40+9qo^x`q&=fC)(>j&#sPR810ia>YPLHqUiGl+CRY zNhil15W3?Y$Eu_YKx8Exlrx6nR83}hdJq#xDT^6~F+$}y`un+H7A7Ug0@H9Qux!YC zKy{LC2N33Spam~BYb3)0F1l#s7{Qq^l2`#CoE8>}SWvA5<72T{SXL@C7jRRx$aVx! z{R~@l@os>!Vi`#VPczlL*#8*K#`T)f-^9 z)6XW^@ot=qm|JNSlhArP$3i(`Ns!M|JN5Ur!4bK%eH%dw(XlI|2}{I zzBFgxTa}45yz46~E}q99R*FE}?ss`mXAI@W1cUr3-I6!t~&(tFR=4 z8hM}|>mPt5nM-F)KLsts{PlN4U%J` z^GA0-@KUT#bTDT6gaA8TCM&}?a4_0(Ioz$fMq5{eY@7S_is?wS#N2~^#vDX;%qI}{ z%-0Hhz%aB&{P3{)L-*PL56lJ9mUYM#@4q~b?CdbbHKqJ7cc&~eyl|xW#ZJY1Wg5T| zWIhD)K`5@w<W9Jf@ZS2if!Nm!D04b=AMSq6owhBVfgXQwKzM2P7q4`HwfX z6JB=(xFDFf;OEnsFBHdPeuvw2YWHLD-IUmHxL#_N4af;u>$3OeO*X*ui-_~T}Pq%#VCNDyl`7^5bKrtRI z+Ytp&K_(>8E&L*VmgNh04qgTk(Y^Vy_h;Tm^IXagK1alUR1`RhKR8(;J%@^OUzmK( zmX;PR(jIjGxZ|jko}!PLe*OBHh4dpZL5}EBuPjzLGOhKwS3o&p$Us zLAC2kWvU3+jzF<2TbKM-Hd)w{%qi&t5Wv~Wn}YC241SdAyUs1QMSurK<;k?QzjMIQ5p!gcnkwp%hG&&;Cyt|L~jYpVEh{WW( z>Gc4*fq*%N0@E>(pe&0Ai@I;vJ9pTLkP|VCHpis9#p#;&rLjT5;PY)^O{c#ZPwVtJ z6b`Z-`@Yh>N#hso)a9>OiNUQygk&^7jrdV_-;l>*-yj>;dXnkudYI=Z0_jKd#@XVt z(A~qF9jiE#2{{@nNbfm2A4&Nyu7`Ji2`q#GG7uaV}bfp*}e zYlCD%t2l+Oe5Gl?rEU>rYf+B8#kM5B2(k?AFW*3!8qqbnGS$)Q)U65Pwn z$Ug+bn<6Y81tOi^mvau}JSaE^v$GC!A^`TprJL40k0KBxDnQ@q)2C0)*H%co!kOTT zy%$M_4=nkp@0@SHnoeQed{Ixix<6;2%|*b_m2a#-$laTv7|zC4MZQii_PPiPVUYla zM2~;0UG<_fu$D2c0WU0H@Zy7?eteMdJINy6Viq%nphJSKk$g|#Es5p{D(C1>QMp*m zD{WmHekogD*U%m}*oUNNT3~k_-S?2q9V)7|laDtxH{E^F+tV*58C5}J855mFxsF)q zXDf9>$SBOkSHAkJ>$6{cHXZYic?jDqPVtA-OW}_uy_cy1T`fU!R%pQg2E7wH_ASSa ze78Cr)$+fQzS`p->(;z*(>t#m1iNeh{r4|+l0nF64~ott*}}+gm%pG}bW|0IJ$%iv zKVROZOP91D`YZc^OAx}Iv(V+`!PCyt))fJsXt`#^bj&^IXQDH;FOn_gcS`~ydPT@z z5+S=de94>7P55l%w+}LxSl8He!=K)N>oD>y=h&B>-(5!G;igEEd=$(NaLyk+JHnJP za^J~tiYs$sL6Se2(FY&;fNmi$^s6cV{*-qD0V#;^&lV+b0^bLM7?OR`KO09<;+CXd z!t3r3B9ThA4D$kBjFI0Npv67fGKo(WmSM`{Gz+nI=xL-^MbXLOKW4pj&S#s}&axe` zqwT5073gdTqD&-lBhg>%p+)m~GvRxoI78&|l}mdk&CKw&9*<<+|32yT&zOdtiXt(3 z<96bR?)^5`#w)dA30yZ>zhe5-)&JzALF6;ekp~w;J~(Vs_${I!3V7q&f9*5z<)|y=oG8G6L~Z=_r2&$sfyfpsmACWx|=W7kO!pF zo&e*B7vj$3SIV4o=ub8uP~HAIrit}5w|@WDbI9Z4#JI)o`YgW}YY@3vy?ghLPF^st z>p8DH{dgjiR%(7Afg-2}PdfbptONSdqWGunHfAhO%~TPP9l_aTA>ZVxC0_s{V3Uz$ zkstxt+|x|mcsq(7$_QJHp=KU!Y^$IUGVk}!qCqY|8b__=L`IDr6y*$TOkjpaGCAF^ zuAr~YWHMK>!nb;|k)T8GCMlFDB(X#ykugl4<#xx3ZMz4phr8*uh@NGC3YJtL!H70S z&Zfh%n^=x9`BX2<8>}U~OaW zpgi}zVjnC7bd@#1a_=fZ(a97Axl8!OWdi&8NO+@wEj+9-5wwap=XE~kwoDqJk72OI z!1$(MK!r#~_q_N+LL>_ndam*BBg2cifnQjVf(itn;q0b;FI`((o1z!G%y`i~3iEk9 ziU;ZAxm8tF6<`N}vCYKZD4mCeGJsmGVV;~rb3ZB=IxBFE4Gj$wN&YWnik)`oocEC2 zfG0<*bCetdB-W!i&Mw_1H?D4sWFqqj792I?bcL>`o#Z2mK&B(n9feLJxQsbaki=i- ze5O|f>kEM$l8fi$tM~(ug-Qi>Ubxa19Ooz&^77qs4$@xAlYgXM4%tfjaiR2au7^v9 zDuPI~t`0)y58dJq6tFioHr`Ib#1!d6u&Z4;ljO1JBtq*H=ewV|DE^|iN9o$mMX?p` z84f+#ON}DzX@;;U@epoZkMSS*G;7rKVd z^*u~;>wX&hBn5YQ_YdX)U_YayV5d%s`W3WeMBJaSInVmW&EB8d4J z(z3b9-p`)IC(nswXh_|am{mO~2}VhsY$C<3|sy;nv<-=RHE z`rE?d-}AoSaDM4~g4{(h%Ae=Y9yE8&iWl=({K3DPc?6vfoI!$I)2izTu|M*0Y6YL1 z2{q8gvTUle2fe>;`8(DwbRi^({bS>*7f~QWKk~r1Ox`X=u>ii8uG;|7&n@$29iFz# zquY8Qt?~NXj~)3w?SGs_Swm;Y{e6+-Fee9qz6sX9r+zu{frw#f`$M+0G~I-xsmjVq z5GV0sVNs-85Pu@(k4P@&x*_a?2S1tH@7XWDn6At4_k9SFrbF?Eg6})dakMm?C)ods zVbfxL&|2qvBIeuZLk_!5mt_(~ARm15)vW(J`_(bn6OqhEzm62z&jvX5Movfp8F=*@ zFASRV?ds{4rag#pIi=4J5YypZ9BJ%4(ffgH5|(<{mTbSk#A<>BF;}@?dgI28XqhGD-#nG4cR?XM|_2u&%pOhy@Hmo>?G)gUpF7H(fM|8`+c@_ zsi^)V(}YQS*3^H0`p(0jew-`LWCbFMa>Ts2J%SvGy1F{v;r@58KYPu37YVQ52gknX z3QqeW6tZ;B8HjF`#mJKq^McO}BxRGoK6d^a&t1ErrE!;Cny61Ct{DI9->yMNB}p{xEC?>t9@@GS z9O)kMTW7s~5J+KEiYQy-m>zw`Ahsi7#*&y%8*)!Zcc@Z@^O=K)hG{_Rj(eIvdqn|= zsM@l~;f~3-m3~%Uu%wJ!rSkXm9Z#bj94W+Hp)9e)RGStw(}kn5-Pa15XjOy)3OJWP zq0d^$3d_I`n>2(l&M!)!eIoJ9khFZOe&G>IBDo^vt4w)q4NzVBYZZk-~Mpcv8*x9=e9_)^E`0dx{wLU zgqX`T2m8~{FEagkyhn8E)JY}_6&l*(=X4SjDM_GY;E+`&$#5v`DVld@+un4K%5?)l z@gTZL(VkREjtibJE9CZW3kbbfXHKJECdrDRUj$uAI(P0|fzDRs=s$t}W(Eo!nMTq) z9oK{8&xM;dZ3@#kO7?wi_{;wF9=eHVBDutsypnqc95sSL{#|-$wRs0H$H@Wxb|ezH zLHANz%~@I$ukMyqBNF_G?bjqW^Fy_5pmIbJx2i`y0f!VAU7DMl z(bhLb*LexkPeSH<8-2V6>kbY*>}d|>-Ml#UTCab4g^xV)$S4Ky;*dWihfbim@Q%x| zzmA+r%b7^FVlL;daJqwmIzkS2bXPr_X%sa>FQI352Z~i`oY7v`GqMfHGQs@FoxNn= z-tVHI{Cs)_7vWxU&&-FJ8>NV>ZJ@s1a2(l?JQ>9=zB`g9x?RrvtUU4rYY;Y^+^x?h zUFJl7BHONAyS@I=NpC)T4e9Td(0`aCpeX((AF`JxJm7nT4TIv3ip$=9@!U^0t$V>z zBMvGmNX99Tk6}e@+G|(<5TZ3LKB{*BasJr$N7r zdg;umCqRFrz%&>#$4UV?!}Khooh}{I z_Yg3E0M-VQydaD3Tl|my zAW9HLQnKq-LDMwg}*RWJJd$u%l?C*w4->d$E72!Y_%3yMnkki*#{|eEX@toOJpVZVG`>fHr5{ ziWmOhf4n&idYB}PK@J1rQu*=`(#Mb>=HW2{#FZy6n78k5-+1^QbGD=Ph%ZB zJ{tw^2v`Vsp>K*#I!bWFC3x`%I(z)~^{IF4X$o62D;W8a zEjk)P$Apjd!G$08Nn}#vRnAcpiN4CR0@V`axk|uuTd`tA&Q3hKSC30VETk1nfc^j8 zx2xa2df~g5pg7Cn%QEs+z}-52Bfc+&I0mla!Z)73^u2Gtnx$eZg3My#YtP(DItJG- zn8Mma605H7lAZwzDs(%|Iyyvqxp(sMC*3%2wA&AKt(ru4TN?pjI-~^#42G1`8 zK~L8XwjnKr-Y|@NBLtEBQP6b<<%5KK1}wKU{b$oNbmcg04VvrY>Ha>Zj}Jtn(P8us zHUJnbGGU%ORH4u@Q2+xLverc*tv9({{hK4$&J&$J{`g~gelQ(JX>K9FP?1a~_od;! zE}c$)Le~ZZ)>ZN&J^R1V=IA<+m&RY~`0m}imr?=Q?%>Hi3Pr;t^Oh19Jh`8z^wZ`hzVZ3c#F<^sWoyceVb-xp~1b z1>!LF`_i?bTb!%0r|)=%^k~_}Hx1pugEqb)nv3CziVBLO8-3|~H#0@hc^YVM9Zlnm zY^`)%U0nvrb6)Cz;#?*#lGk}=m4=p=3h6Y9%a_^oxu zI@Gyy=P>DC(Q(uMe$9rK=6k-c_#=0Pmfd67mQ62+f+pT&nPgf_Cf6{BugT*CZhl@Q z)F2R#1Q6Q$;=g|NnOkv9D(C7LtDKq1WMmPg&c2+xFH8B6?R0?KPH~b9Dt+BgfA&d- z%1UhpXaLNYNP4*b!?#YLA1hiHt2ulY%FoxS8wxom^-k_5r&k<|gBQI1!X?=x{I+!` ziiC`{^imG|kRO5-3-F&%41lQu%zLDt;$uGLwV zfM6S{$ptJsQ7BGW_CMzlVqIiNr%nIY38%hP9ge!`@Xi5QJb(S#>F5T6A}RQSMQ3VB zMkKqZy0+JL<(Fm-{Sm%sy0^;r*L~IXviD~`fc-yT-Q#4DDLOzghb$=Dq=g6HBQxq9 zK#Z{A=!j6<*q4|$HoBHG4TK}i*N_R`q{nA!YHCtL4(N4B$W0d_%i{X|1#j`<%u0}t zA^Gp{!w+{Rzj~qrGLhJi7f1@gbdJLEKhJ-E5Llj73d|}aQRTR@BSzo}bP5~*dTxQ) zpP_?T8hS{S{u8l39UnPzyCTmmU*6?cuRJxQDV=W9yvSH~-CWv3&wl>ViKI76pSMh6 z%^RD!+(&7>gi&~c1lk7{ebo2eZ@zp%z3-%7K1KI#(Ydr`>9}#@OyvAgye0Gd zAN+JhZM?!2p~<$Erkj2}bISdH`Rs#(P@qbBNgRH2wqsm9iQhQl2vQf}1agoO4=wrV z;B#JmYR0y7;!kbyTeL>J7|-u zs`v_c!P8e?&AQ<2=Q&XU^7zm#m+k0?ke65`y<`Wh%IoI7b;6TNKAzoHnYTN1tf;*6 zvO|Bm0^Pz8laj^jtgw)I6Viau91Y<-Ym_|^A#>RHEIPjnL3W;Z6w4qG~8yb>(kvuPh zT@g_jlIs}q&O7f!96>ARD|ZY6p*UC1o;~BD>qwjwuBvD>3bxyqQ>hfXowzRikg&6i z%C!{GV5;z9(?T)}3jjQ*y!R}r4+-oD5RoW;m*%FcfGrYw>Gda?WACBc4atFAAqd+s zFC}ACXjjIH{rv?^vOo$g*ti0SN?ddvq3?Z?zVmr{Z*w4k<|$bV`uUqUNI#1gVBq=Z(j-`UqkB&Nri1H7!>%B#`H1f0tJ@8 zuD^?P@nY%veoomSJQinW!YX$uao@Yppr?*q%S@}zjl>yCG3Td(H$`si}6Hc>}3d2iI(cQ zVi4Af<)!ub%Xg;?U^~`-&XJ9M4Yn2>=p<{#(0$FOzjZbC*zZXu+)m??MBtlwlMu%P zO-L-IIkFFp5el1??KE_DM8OUP3otKq6HNCA%nvwwNykjnI@(0zaW2!}7bcTlzYiGp zL?Ulq0;6Sc(gf|q1GG*T@YS=il;?^iZ92f0`!y87LYQn!SlZP z>f_(P_1v}4yC4^ZEfEEA1Cra>4!dEez3=<&qj~w|3&QH2}<$u2O;<Y!S7B=?-6w)}=>9=EfbrYc zpS^v1Dscr<1W(?c@x-EsKmYh3dOnp%5|w@@Bw3@NiFve-h-5LY%RyI>#UHE!QJ&V@Dnx6{v_fuddJvI{z51}=3zFV+c?gU~51{m{?c7plnupI<)n*9RW{=LZKl ziq&=s{0g7OE^WhI`4gEV!2v`v;#|BSd+pk_oK^l;FC(Exjpg$6g9o0&6z9KfTA?C# z0O8M^bWElSN8xBu_J3J;3_=RTZ2mTU>}_7mH|&XLKKHk4Z+ds;LG&v*-*&PC*&Y(h z+TfSqBmg4Ol(US#yq2WO2$QFToJ$>?m1zkx_=j1@yCJhGAHG}4kFP@bo2L*FG z3MiQ(%nb8AP=E$s5C}D|I)23RKJ7Z4$6R7tf){5lykyq&iHJub`PIP!E#t~g9njc= zym2PEB5^P*e2%g!aJK8m>(Act$;LHrtFSzGsHl3hCLaHgDIi3bXPoqa$B^ zB5?(X3P6kjeHcCq6oP<=ggNH=_Yp|9(z~5GRy!42Gm!&=y%1eLRyJ&UL6zX-JNF!k zJ$&DZr$2!_W01?SfgiBh+L}qQbgp~C;cypP)R8`Y z`V^FD1q?|L#I&h^4l9Kur+gHFi0`UHQZJ3w0(yUs-tVLs;)!IG?oR=zD*9O_Lh_ej zC5|DeiI?bnz!P<4vMydIBEdTxvv6V{un(47I`%Xs{li5h$N`fNNj8) z=Z{uvNW4QRXY0aB8eX(u_X?cR9qX2!-~IF)Ut{i3hr~V##G~8I32@jslS|mniM>t+ zDW?IGOJIl54MQIDo(vtHoQdQ~B&E~5c|dg58P3%3_edo2S4+cr}G`M3fXbM^> z!^zG|b2Lpx?ecsSfdF>45CkoIr#Ch>qHEKGSYJqgc^ufRH^^-+m+!gd6%xgjrJ z*gxW8-{AWP3IwrlpqK;w#y*f)3n4`X-WzvG+0!@U?&r zAnu8*{R^5~A>o);0*Z;5hj22z(ko5QR@dDC0DDJ~(;^hcib4>{`syrhii+0kI~Byr zy~9z=gn*7Lg2g$wUPOjG82_)M*CM7kpXobLEO}V*EVLPw;Tp_$%<6``CNy@BMF}!ey+{1iYbz?YFbxR84XLnG1V2Mugsz1|OGf7v0{p)&uhKeYMC5t0HON-Wh8ii^{@89=g zw}ol|80hD$UbEoWuTOpO_V-^om}O;DPB&*e%c$0V#MVB=TyR(T&T%}~%deaJ`Uw}j zH{&G~oun;OiR5D6s`h{SMc=wrw&MO13LC}Td$hmXnZR1C{U7rUDN86c>bcsbni03^Ll?tV%Muzubke9xh~eoPI6c1jICvK zF7XWh5*#n|N1Tt#=KuGDH&6WS8_(T7`Lj!?HI3BTS?8iu`owa`Tz92<-=HH(z zR3RwmJP4LQfT-K$?PRMK({4A7c{418Q#l8FwMZS`cD zNMthOp-!JqgVznhmMjX2aqxX1QV~V{&L^*-%&#i67K7~|ZTPrr;p5afT* z+>~>j?}Z@o*;TBZ?S?d3#wlr@bDbMH{WD7z&|dh0*bChyy#tUVV}5w(?G#r{AZ^}``OL$- z5QI7Mo7m2sTX#i*+nS_`cVTt>t+YLP%ow)k4YbDK<6U0A<#`XfO*Z_nw)4ab4*ls0 zvP(EI9|-7~$Dn&)rbd1v<^)~qbpZWOcEH-x{^#?ii{7Jm@7}x}IR)|`NT<`T zm785)9?h{;2xKClS&GFU1Y1`qd!4jb2;dnKI5RXk%2Uy36onH?Ed~KQ%L;ozEEc<^ zbLY;c=qysOiU0^=B%-3l;r{#Yj}}9ZH8wUbq3hCOU~MCiFkeUS=T}rzuy{Pal*V&4 zZ9Gfqdl%E^diwnu`gyeox^Tfo3YuM2RmCe7kpN3$R!cwE(&vFR{xI+ckWn<4&fUYZ z@~3AZ35vesTr!N1^n-wd@|Q^=453W-TG9#bV5FpH%v^vQZEoeJJRa*70o$%!yM|~E zT};0M6Sk|QL~(wwGvT#_=Hnw|kk6&LxeA@1$S_Bcm=`mk2!ote{93S~qpcUlNG21I zVUtU&r1{Z_#_miy_i#FNux8Tt(o`ySS0oZi)A7o(h2)zwKnu;3!q-(usTJ(C7pGxt!+eJd*WO zXztFTxeI5pMc)N>F*p>MbI$;vZ};rklMfKdXoHMvgZ7ya1$8>o^&LvTMsgX}i_W}m zpf&Q_%F4=>bPe5fYVc@Z3#wjItd3XOAX&DA=FkLsPE$0J{2{YZ9Du@#yJ-H*rOzwL zX$jQ@d$81RZE}?4#)N{pHqAu?`U`#LV}^N1`+`Ap8BV8;Bx}#4Ya2=DJ38c1!5Yoy zi6}fInJ$Afd3#R6KEj2k#IMkG(`o%0xamdDVaPh#G6JZzw6c^G z7ogi;MS9GwbPvv=!;-cQ3k9d%6w(w#V64R2gWl^GA0Ay3aw*i zwR!Vq6SnJhKRIdPg>Oy2u0GXrGjj<96p_@op{2Ru^k*KP*}bO2lv|G(@jmHYCTt0k za~9+;{VDc1TiWQ5#j*`Ih@!Q{yxbCvt7G(IsJ{~mQDJ+olGW?DBC)bVy7el*g4Q4 zD4>oX4YD!9^c=&aQ$#O#{kaRbru33g!-lcsFFzaeB;p{XJGl07Lw?!r=eHMtjRvON z^aY0wTCpWjf9~g-H@s+jpm*WfQs8&|hif06Srd=VJfTPbS5H4^z*h1-nhL}Wz znF+ZND&#&Y_7nb*d__zZ!l#rw7b_CRjT>i)8ws6tD^Aty8>u%JDr9e1GDg!CT;qp!tNkhGbqYKcp=ver8Tv5WZKZO+w=q zzU1X8$F9TPm`RRzTl66ToAu~!-9}$nH+VVi+i9?GVtkml;P;1)LwgZFVxS;>qp(Ry z4zmmEj$BsX(&U*O2CzO*h%smN^2x?9a z`J91FhQAKJI;lHCMng7}T!u)-UK=v9;QHg{Wb=@gbY_TZL7vyhnAU(7mMwVvjDrsz z<@7&7C&zn<<}sAvJ$QnZPDgK10OR|FU95%cufHD2e;HaUsYiy7y=~&FQ+p)L)Oh9+ zY0El#V{^;V6J|Vdd1W*@vvMuLa`{0dZ)-`XRq|4-w0=tuY_8m*IEtxTG9TH5>pQ{}XUM`UWwC@cJ z(3_H1UCG1qZ@V&@OqL`cWx@8^$bG&o*bDHa6+M|rst5-+f+4yy*8O#59rD{QwL`}5f zq5t!vA*;|;?DNk*x83TLvVegA5@-g$(NSG{pYy?nRWI6Z^>#WQEb-^R`_kpHP-tG? zF8eqOpMwW@aBhi@OYrg7Wl zZn}N6M%T)!%l_+>aSNG(pI-QH@r>bTXF3RS2ZOBbp$bE?+vhEJN7eTnqS<;(_dc8c zwc*>bGr#)sbyYtS<{=6}CcOOQnhx=bN4i&ceE+(^CoCrW)PxQ$ZB%;Jx%B`ULFT?H z^>2I@J`n@sNj@^n^YEYroBg>bLdY{IgyK17Ey1u9oMFN z4MmNO3pwD$cjxW;Q3OJ|AUeZ^DE@8y?(sbKP`Fl4Ij@H2`A^&w&)|h4FZN7;PNWy-192!}lWyh1Uq_6A($h9Cnt8 zT7H(tbWf#HY!|<0`2-S;z<7@YG{_9vIJiOO=2_9EjQ}0fg%`yU`G!kpkJoF#7R<$n z0&b+h(P)})FM2rFkx+iOD0*2+^JNJ+-ImbLi)nqKcxbhdlW2Jbk{6UapxF+5o<1(3%+`f8ms&?b@~b8kZ(@5wSmX$G(D&OPC(`d(GS?WuR*W z?WgyV(;TG79O8(AR34o11It?}pIv1L*ey zXpUpgaAm;L_56`^+J*ECLP#WIig2=}78qX0{niO`d568D9V>sns%x9(3K)o2(KGuE z&4Xu|#`AX+BjNK>*uwOAZewHPY7~W&U0~B*ER&BI)+fm#e%YqbUA3o$mi*upFWT zm`*WqE67&!VRFbv>H7Q9^(z-oNSh3dJ7C^Ha%ll=XImu1q(x%SZ8ZMy0qFRUuAsSv zb@n0>cHu7yf^8QBNSzgqUL;O1Mc@Fh7%RO5T~kOk3EDBA0>Q3|pt(#0c<`jt?>qmE z=Q@BnoOu8QAdzHELB;XoXFSmmH9|`&jOe`f6%|W5R8@a9uw&OXzvw@(9`YCZ766(1 z{1=OBH#Bdr{o=!o1DX?=UKBJLoU*LJwo8`gnt1%K{~j{p0VE5iZCgD{*>P(g$PL-S zO=|-e#pZsu!*jZxYWN6*Z>&}EL|Y*esm=d9?D&MWG0kx+wr&_V z;pHdR#EkH~{i`}mxnt;v_vxQZvRN!iKEgx{LKZej$i!4yXDDjo#x#<}X02b{@%eXO zx}Y(Y8n2sVPJ`5N)B(NDUAJx>nyqK(do33%a5?!Uo`Dh|Gf_ClybWYqq98VO*YGnI zUNrsbi7T77DicEO4kV*S;5Fr6pWO4@7mGiLg(FKkR7B^-qS5+69eOVrH(ZR*ybg5X4NW@uh~?poIcD~n71P}ofk-6H%xa{PW6yv2*}RHK zbY8n?d~wHk#p)Z584mIl3w~KZ`f12d5E6cg(7p5L_58Z2zGr>1d0=BEb#&S?hu9u> zFpvp0NcN{Sox=JdS}eyP3v6m zt*wDXv6Pt@ylwqAZ2XMJ8^R%DNmZ2e##qG?vPHM`Y~Nwcd3^_h)XsLs!;!oJU&){PJLXd>T#=rZ`SL5fcTk}Etc*RVa$PEGx*s3UQCmCkRV&m3D zP0r$WnZ%0XkBeV@=GgU(4W}ScR(CH2QNfh4zrLToGY#@JyqI_3xhEhCxbR0uE{iT@ zqvxz$^`aMgJ0DkyACi0+KWXDjYbwYG&@LXEyMMb5i?93X&z6eyW|2N>BZdII9eF1x z+=h+46FIXQeG`fR~BDK-cQ3%kCaJ=5hL%nmv1VMzJFTCpnKw*lk*+=wv0t%pL%A=yqfu6Z|Je1rT$Rp-Dxv5ARqG_{;p$sbRTm; z-BBP*%gmoYKSMgRUF5l!n?JUG7o38}#+`7GcxVIpc22)%J^+%RY15{e6w^&Z<~(-F zgp1Cc@kE_hx*&>z*Ecrd#Xea7_0o7aGOt5L)!Z%=v%uV!{u%tObHM2L2GAf!mX5id|%ec+lzNJLp_lb=h6Vo$?~>H&WhvUxX%f6x|$hq2C0e2n6$1rvL=| zpZK`PG1}`ofWT;@7w-Z>gL@~_=;W|FAUT2Fp6b%23qL!o$obnBxl)GiLd4bja zR{D7aD-g71cLdL0Rs_|T4>L>3Nc}wL{5*d&lh_J5@+dJ z&ZXe^Y?ft_Pt!iDB?zVQE1jWY{Fw&)zAju{AI(MA=hziwp)4UCi^VYZ7mH`$n%SWh zYUrH9>3ul=vYpvV8!)G6&w#_K##zwh73f@-(!TK|*d#KU43rZsPhue)ExUnNfs#VA z(`qw3pa2BUCOUReRaMpL^fPQ&ZKtLvK;>Qfa9Lnog1$oYZ5!sF{5#TvYtTsv^49Bl zxC`7#-}N}@GYNWSkU&`o-B-%pMT-`}iHiU)ia<_7(J=F%6G$t_;(^?_6ENN=9L`n5 z(f!MDhYYA8dBlJ3ZA_KPbMoR3dS5_4rfJ+EkW4o^xh#01Kv<+-zkbn*ii*DU9WUry zKOOf+vY%JLCPSc-&R6LD4c}*yos&j^B|VFCXk2FN*b;Ov_t5iPLi)m&G&W$tLy>JE zik$WaXa_I+&HJn!=qnnNmnx*rOg3z2+PL4|I&RwE?d_?At3-s>K*B)Evyh&6=cbkNj8Nae&)A(^OJI!yrMxwU!o`&*uuFA1fcOd0 zUPK`_*nEFmcjPiN685AMNNZ%1-DyuuCfPTQ4Q%X-kD*xZYadrFv@@sr+qcFn^-JL6UqVWSH&6VtkEm*xdBVNqMZ5y$#P-XMV+L_%yYK#p8+ zL@2bWB3?0S@`AZDyh{9&$R^R#E0YhflEO>cr{1ep2ydFvok)RgDCwc`~pfc8=~Zf8*Z>hjT!}id+4sCPQLB(58q;5 z`CgF7x}~N0=xieQ6KkR&V`)4RSyEFGUmP={^*uV)tiHJFm^J7oFXJL8az_H|@(thA z)~Bu7_08J`HfGGamULpITe}BE#ULsmKW=*K)~%eVz{@>O+WxXr*(B0mEi*G_?z*q1 zd#Rtm2hdDD02IP(XlmvvM#38!Utsx})rk1t5|Y3yT$< z9N?_+WP#A6oC{|;Z2}fKYfIT-qJ3X9Mu8m@2NMbyQ9q&gN0_zuwRqY zqR$^w<+&nevzXvD|$(!+x;tyK4 zPoQHVein)U%HychoyH@$3ZAw;Pl2Hwv?aY1k0}$2#h{liq}v}&Kchgwv$+C&h0eK? z&SHDq;p2P0o6pO}#>V9G<;!{J4^}`(eUbJ#&g4VC+OTcgHWY!}R)DpSXSh=UxlrRS ziijuD`$Dj|ux!^nS9XuRquWgwt%pfgpqlP4^s|nx0K$$JTUuIpSCU5`ebgE@Y*<0p zZfFN9k=fMLlp(!)6U~c>^gf-bufJM4c5=IR?M@>n_lBiQm)f6y{y8fbfp`Y3MPvD| zac?f^*l<^#;`<^HG4FW-1cEnkf-|6|s4wxOlj^CFO40W!+Wql~Huho?R^^303LkC-GGY>}-2{Y{I^ zqKNF|SyF<}S>Pe&n~eex#1?;d(2tg-Q_Z74SiAN`J5R~(5=a=cxGk93#lPlP<#(z;lc z_jR!=Qzke00hDH+6N3hD7r6-@o;o{m{>Td0Qfp2$5cx zau6An?b19TtIMlzuaGwspDt+r!1r7WcaMt%Ze=-WWji_qMt(XI|3E}FjImAWfj>U& zsl^|^(kc^EuLWUI*NQghM zW9M^kKKkU@&>JWYjtk%$TM>NPo)M!I5FtIX3rpG_`MXy|*E65^XaSN=!@X(WJ#_RF z*UXvu^{R%glk?$Uweq+Z?h4o+>$-F~?~arC{>Y0zia_SQJ9se&epoj)#nybU6FPK3 zbRACB*4EmSM^3ot*E6TqH)m3lSRNtc6_#yv|E^yx;rzOjJbL9W}Yz4N)TfF zv=2Bd@?Z z8aHknPmU#LFy>pLs;X)kO}C-+dWP*~5DfVUyq(t6)bu62C&}rBaq+|vAgjQ2dJOVlry@A0ejbxabnwpE~=et;dVP`wloY1aaJKkY|WM_(AU~18_S&E(X zpu5)zHs@l22U~QCXW(dpok{zBy1BVIMbB{|>FmRTuFD8ndOC_fXkI2smxMDZBkYud zC;HPq2N}-wMRHndK=B6% z26R})m(cvUo#sXA+i$<+g(-r}qu`LX1PaP&{iW%e7Sgrd$@DOQ=GN5}6&10%y1MY# zv10?31mSlyHa05zI8kJQgavxOz6%_xE=HQF&bbtrK&V_}Bs0?FYfJw1q%&?s*9;bL zpiTP2bT6dWCqXVk^23C)KAlc69SFPE6V|sNv_&r_M7KdVuxKyj`ot|1Fgy0Z-lGvj zW+eg?4Lox4SR{?6pu2%h)+4=56o9DDaSF!fZ7+pxBobq}9{xYWMn6{9x$CfyeoVj@ zfSjE?^2`hAo)WNoNd{*G2@-<~nki!A#*M6q(0cxUOytK}0$~^mtB_=HVcpKOR5gOWdzWqAP?gsC9mxgA=pYq!x6 z!$-L862M74`>h|+qx%>)xJjd-)+T(bf8vC!$ z-CwF>?=#QV%GTejHBUWlEasH=G@*JvQ^7??fX<6c() zBET0aDicl9e3f|vIGa``lgWZJmKF;-)k(4g2q`)&Xb_t_@7RHAPg-1?nSKEgl3;$d zb?a7?u@$Wd1dvIN^h!ZVLvlR*?uZgjcG?y0r29RdWWqZ#DYCAvE`wxek9_*Bn92jv zw#r6ALM*0TnVDBeCPpR#1!Vm5#C7^PsD}8%bp4}{oQOazlJeP(IF#0W!ExvS!ioSS zpy*yV9D;cFi}6+B080#BaC~FVC2MPI*VCiB5bW)HOYtDx=RA51BOUVA?c2Av!02NR z)^rb;(+kqVvdGY8ErBlSNXp8m%(6xD0TP>%q$k`OVBH9b-a|>p`LhighfJ44*;~US(zFX+f2}xb8?KGK}WZ zEZ89QtF$QoD60IEwo!e3eHv?@KHo~$I%x@6db;S60g?`PSLpgK68*7U{Owrz{k=LqRkh8Vjt zG~nEWus1Oc(0cb%QVqWj+UJz7$HAH(#KIy@wn8ryfh532*}YxO#2_XjAXsupm;Fw- zci8C1P~0f?Z4+b*S)13D&%~?eYLh z*Cmp`jb!b|PC4r$1U(V#pD|;G=~00uFO-ITp|c^1qzHMQe1Q8wK!(xO)RY1P{W1IZ z2xNi-{ace(UOVPU>?@SDzAHV`1lJ?>3Pt4__mQ$!3tCS!J*sjsf^+Q`whiF(jdaR;IN~= zoN?v_quNE|B^$#;0_oWU2M)dIrzgEbzPkk3#|gUk1j&WO1$9G~9p|#ZbbBgs#ig^R zp98z}>#x7&b}92e30-gz2{Tog4uW^T#J;d^>U01JM_3>s3N!hh4B3M{`H54`JO|zI zSO9^-xibzLICRp0!_gff38E7w;>t`DBtw{o2)W#!^O`8_7IGaUmx6qU13I@KURCK#)w0;-K-b^w9F!W1D|O$MQ=w`72Igndx7yyQtONYbM9^cely;UYo4 zWEf+d5c~b<^nNPc?-vNTA)$-L!!x#eX-LW>1UKjO`JTa@@j6K&=q9&m(o>SQ;(OvG!@*zld%b zbbW!gi$q}sIwMPzJP+wo4L>CCrIl%Euw0$Q0c&|Dw=yc*U zA`!dw_4pW?#26vzV{lyr!W^Aw#~%8Vjn9q$-Bn#H+g%paIAXhT|S_uk_JGqB@9WCLMb8mp|>=Z8VBvSU|uTpL_NlcHaqO{zCQ~CqQD^ zdFm?h4-=X8jD#Jab93DG%EQ&xfSQq3wO-vj2+gk=7(Cf!RUl&f`UJk73a{6N<#{cTX zt_Sg$CH5W=*3mzkNZ4*@Xh=;u_^9Q_b#qq)vaz}8?n`D(Kc~978g?mC^vgvcc>wQ{ zl~F&=2=6G$EcP>TkXC6CFE~3o_&jFu9XR!R_|X!HMDo9W`t#ekULT-1SciD!AD=ws ztWm!?@|g9cx5?yIsRN2iOxNS$Sbr!2Nui?y z?3!mroqc#v2@9|e52&h{_|z|d{TGnD%B0mo$r|Z17RAgd_}u^c^D}Neu%_ebK}`;p z9mxLn^Kn<6-0R>CLT029LtuKHKO|h`!wFxF9Yj4P8`D+|apXTFVrhJbgMfqkNSNq? zbHO1;fBEuR7akY10w~D7Cr&x@w2?grVt?GJ(8;e*T?5FgU_L<5bU+D55l9+&L5Le) zNxuJ|k}Vb_pWtrLa6r47iGMxmj4MITA>=&@K#ErESIQt?EK@~zhp&8(``=z!03sJC zIZdpZni@D4U3CFLA_bk}DbXf-uPFW?=Ni+2++2d#0Yn0h#uY18SY(7|(gY-^Fg?^# zVC#Z-JRYTUapzhw6jr$&V4R2~awIyYU?hPR7R>JSyVIEmg;GAkTzZY6`FLt=ZSB9w zz)etK9)?-on5MXsPZOj>1i_GT9UuqtEkB*kI^1!)pv2mX31bs!T~q!`zj<;g)`w74smqPAl+dB$>Py| zpW|Xr8-s#D((BfPITUkNwyb3?fnc93R4S7Gs`Z`Zh~$T}0>gvWwylrNB@`kF4;^Y) zwm{9-x{NGjrR%*4g}6?Ks-J9VX&*txfAPf^DJ1d2E~0gG5p2JJ6@f|cj)$2{=55@M z=y>P}8ye;}y?9qknvW5(Z)5ab)9H6Tm^$vFIWn8x(9zZ(z zCc4janSQ{3a&>j}K4d2%>KiV10dWV|%(|@Q9y3;Y2=(PD_PHR#qIJ(n8=Q#<0SF#) zCiBNfP8fC8k;iPP!E2^4P23Lt}#ZGYVq}w8{Z6?5#4-2Q z)YK%FE?vqCxtRyP*bsLI+Fs628}r0jFr<1({3QOKW3H~vi-LJEpYc)JHA%#F(Q)pC zfE62KejiQi@Y=%$zeqpw;t#LlUmD;fq_lT~NG@^nke|;&M}#2R{)C41v8*m~<4;cH zUE7i9%pCf<%dyoy*092DWNJz3TdRS<#>=XZovbsT#apZpE& zd|DJpx8>NqIlv57Pm?wwlkhZi;TWO~du|4_y33Yvr+FzmMr3t5SRR2&4rX z(#c3pwhWEQdrY%9f{y(?3AMiQcsxu-w_lh)b1kgUQW!=`GMPl8&?pw*LJ%aJ(c8l` z-a{?R8kI;Ski^@z9TJY^IQJOP3NPboNp5WwBO zeft-r7F`$)hmQz&Aqe)nG4%Qo-OuE1-MYy=Lw1Vt6I*@IAosPv7-eMa@&GFm`XGyG zPe&s7r9Mprzq6&KWoSB`eg_4;^m9ru5PipKm10)sC-?^#lp@#(yLm~`+T(dz|KNXMC@Sax3ej!{Pj!^9q!2FoAX`?hP>ZZh4& zX)GXJS7R@AexX}sgQ!eG_Y#bDt<8Y z2ej9vZig(tASAjCGDqi5BqIFRPfxnluZaj1;m9+CyB|38{u4*v3np{=RdUgyMLg+1 z$gte=L}%D^TCW2LSVEaMb98U8UvlH5njV9iMs$p09>GxnWTGPsiZFT6!0>SwVNQby zpZOT*YI97t-p4;aX5wQq5tZeqJ6CSZb#cXs+vS>eEa=O_q8BHeu_oc0$a09N`i zxd(HsSNqOmo*nzUKb&*uahw={Wo-ykc5spUpl&gQQCxBsQS7BYZya;YxBhn7^#I+Z zq(1`tB9dw_=Yo<@h61GVKlsrR_nmP1J+x+84nFwcq_9cUGorl#@AjlBZ_0|gFJLh(C1wP%UY#mY_+c0dz$XT!< zWe>Miq1XSe?#N|NpL*8e=mz4q#J`Y8B?dHA<*sXFUczofvG^eMW`l}yx4nXJ1_OXgPpFZ_hLoYbw z$6xm8(+4rIVite2j@hJ8sQJg^Pvf>fI+XZ%Jf*ytaNrM)m^$`%H>2wZ>_&7T6Zum` z_s^pPj>rcIS_crGTZy%cDWY`b4w>xroOX3Snj8E+PyiBY)xA>YT|t(-a@K`ELiZ`Z z&Ha$0(CNOB<7Xj;4$@tg^>z6%9<>wr0??3i@JSaN;<^K!OD+Ap$csWzEn)#r{NlXH zQv^{*5DG!0pAf}Qqk12_fn`aHc+42F&wS?0wuV!lo2oia@Mcm-r<0-z_FA8n+=yrs zGHFsFLaN=GCy_|pNr8q8ZEBXEs{y@6LxY|_Y2(I?HW`>K$Phtko^+K;rP8KpUdJ>* z=czFah|mY21_*#;seo2F+=qmDE^hwtL4|nyAthfr1uv1Ht^J>nM7=Hc_;6 zC%q3P=hbwu${@JRveoC9v%8!Vg85MOIXE3hMUz3gzHnAX(ctI?V*U8XKTbOfL69Wx zC%x~ysSZ}oMZXp|I9riW^guxiPD}!BhjV!#CY;jqOJQFm zJ)s42?$Mu*z3}+%2OiqN5L3r|+Ed2P*k;kmZe!_MpaoMfy zE;>^2B9Lo-a@@incG+)?n<9|;>%N};z~Tjc$i@X(p)qaRG^1>rx(E3-xc;nDs7U@@ z6oK=-%16F4*qqAyX2vvSL3`jK_vHEW=X0iW6a%9h$g$mej|P)6D?S=(V-|4+j|x-+vw8Kaea@Rs%5*q?6domM!z7D@E?Siq{b$DJBR8eXrB8tZi>4 zQtC1x%~h!EIlPhy4&Qu6=1L$Qde6{PC!@Po*Xml|6@tLdId=aZgC6?X>9=g(zCD*W z!Tix-+*zc`;|L&9_}l4czku;XGCXOYUo-!p0SU_})PCq^zr1DSK?B!Q3<35A*G2MC zP(%KI?y6y`2pc&a+~@J6*5KC)B0g0aYkS{iNy3%P#x;@L-}!_)WQokXN@9;h_o<`1 zbsJst|F?JML2_01y+60RXVE?*jEoTslrYA@8j+ai1tc8v%I_H=8#{gvfq50@k5q)H zh`_A^IOmF9PD730bPQ)`I@(0^g+lw8lB{+Db&!- z!t8R}Xmn@jD6P4ZfgC!YQopAqXbz{|g`u4f+Vb)5d@1Y)vns zBq>%U6o&v40TC4aNP^3+Nl|*6NzX=+zfGc;{!s#OEnKvegyMj%4G$hX7>c8htT-af zgCip&cgXvGTaJB;ynd@Zcdf|?m_XzjRQAZ-J7_`TF_uY3_oiD#wD}{nG8uZdQX`;KdSlbFYv*#Xi zAm=>Qzc1Gb6YuI?rI0ip1U$}BzzV13tFOKqiSUDRt$r%k_Yryi18Otmz4fdP-f+0lcnXhL&n|^d_(b9Vw-^`Uvp-BG(+AZNwk*JHrvWM#umd(pLkIxwqPWFgh@%{ ze95N!-P+U%5qHfG&_SgoOP2hVocpiK>z{<(Xr^pC`F&twV&Z}B?(S3K>lBwRU8>`+ zEOsNh{`%`{%7P>(tp1g2;7sw{&_tebw!gCANK@qvlf%m5SEVaTNx`*+w$7OP?Hwvu zGx|y{J}Wyt6^aaCm&=1aJw4k+CjL-<_ZGQrYqng9Z?RME;mbS=ojNr&h2(G1>8PwX ze$*nCd*~pE-SQF-V>h7B%NY7?jDPvPx6A7-Tj@acfgI1*Po6ya^1#4=PSh;pm?f@Q zsQAY=fylpmiQNCLsRNPGUlG|w?k%4}}M&xySD+G|CT>p<+l@5}RuhwF)nZ}08x z{U?#BC*_udzmM;4!!goILb(oB2i7tBGj6am*&v>bnzmDvf8#q(-gav8^zD-DX`0@}q`IYr!eeWeuiWu_8*g~l<(LpR*qwpSp}XvL zna-C}>o?a3S~-UK>jwMzp`jtW{GdruTPc=vp}!-U$u^Ggl0%3)%D?rCn=xDLnqG0E9b_yj=F<5fmLMu=?XS@!V^!n}5I;Qh@&9AU5dQBYU}H9e=RZAtu9r^@l&$@zXa>UiY69 zZ+!7q04Yaau46%dZ~9$HDu{llX-^QdI$eS~plw#(_RjYm?B3A*<*)AD_2^GdoY*op zJ-w}&_7VQ~vaX)5edMyAyZ=|NzV@VX`a^EzC!Tl$yS=xIe^QBIMJA$@3#Uw%in_3n zeosqK-Kg^`OiHrS0HSDZ-Z(ukdNkZNDDvLjeYj94+<(V+p8VwK)TG$X*2baG`4=x= zbb`acdR8TreZRP7Id0kWN_!sI%K(AE^Tv!4G7vLF@}QT1s7b9I%tC~ zF|#M278L&)Io)I@mY60sI=KmZXj;PLbDR8My?V6Z`AY zm^hv$j)cSP#!AbYKGN@Aj8633l_I_oI7;UR*2(GPjIdf9j7_Ot{S7-Juh z_ue4y9W+gDB6N*{Ps#lrj5)Ds!$@jQPEKNHuh2E$#fg<>=_b`jSUm#?*0lew9DDi5 zkt0aXwnGvu!ub~eJC$LCEp@(R%a$T=lM!vxE*fOvXDnU1wCEZ^9u*aSo!s9dvUQ`p zH+9kVTzE}OUxWP0{m_~vJFuf&4_`znf;FdQl4c(c9kkwvyQCm!k^7xjrX%ub|Ng#SAk;^4%*cSQ*d)9~wbi zvgtl~eTzKzN$6d(aA4;>=+;LikK{MgsZMR%-7>x7w$1UFTw*HcJC7AIdwY8ylCku# z6mu>`(~ZVNW`Xg1$enWACFi{?x)m{pl?A_ncp=WcOX?f(;Vuz>d55WwKak`7q1>b4 zx$KTN)A$X6rm~_NY}#!i^UuoP112T%`|lIo@I#TS!-jrsD*Q%x*rouhqop{7<;v{$ ztTjzU5}TFfccij&iI}cq(A6mATIXO#(cmgRK;fZJeCi?jy^nnLKYp;`;Hlx;r^=;` z3speGM1v(=g@5?iC09Ipzl0|!v7Dq%H$d2YMd)WUCm_sNfpOi$g= zjAoEZV^jBIZ(XrsCnlq!7>K3;9M_2xC&C9Gd@yvoAucq~eG#rfs!tia`$XK_&}{t4 z$I!nxZ&(HJ-j8nmN6C9Ud+(3-T=H)xUf=TOv=n_8s9_Do+$Dv=p8vJ($_GAt*_Ef@ z4~m{FLFXXW(@A-Ai4P5$*`jmD3#A>cf^ZBRVvSiiH`)|4hb(s?UGQ{6F zMi)C}DHepj38&NQo4_{jwWZv2I=(KJd)o+d-g)Phe)Z?BJ|sDjmn2qwB-Y2p;^t;f z!hxOb$#*}svbV73!)xEZ1HL5uNcj5_Lxz|rg#4Oo{z&D2=!9J?wt>fA=CFq(5Dfj&Tj<((!P4d5xc8%< zL_R8>)avr~bDk4{8eSNtl(!&KKRhySWpYy|(V3my!twEPx?D&UMu=}sd&rpmjpb4? z^*9=>{Kf@q3iF#5T>N0;e~C}?5C88^*||XX{rI^{|NYd7O+!=7X$H|aXUn>E4}9#h zciT7ziztZQv+*BnXPUZJGfBq&ZcLWG^r6iM<^Hgo*GE40y&WHTbzYTQd4 z7rF&!8huUd_x1Ppo3xDt`Z+VpE)vNGD9AqI!tGaHb4Y&AAB*hY_nGhg?Utd*$r~q2 z)3=xf0QNDT%k5v&yZnIm7*FRLA$RHKsQ8F_A3StX#xxi85{q}^SunH&B1va`R&Z1}laf2`~hPtq9*|PYY z0q;HbJmDPTT%qggcXf5y-!2JNOoAGin3x!phu30qFS^(p(G=n)IUSYz{qk@> zCP2!+yKic0Y8V}UC{Dr|#^kSt!U@DCAv-7=D||J?`-wR<%cy1LR+>mriDA_*Rl>@y2u@0YFX) z1^FdQmRQ*qXDE02^l41$yULriURM##$uV}Q!EPUW<$kw!K1wKFie9jjTg)62biO{4 z1V}+X-zDFbS9^wYE{@%q%#a|m7$L~_ZP{G^y++~blL97C;<)9!SJ@Qrz1SMfEMLAn z)JdgU=xnv@keTtvmE3YzHC=Q>4mJ}mmyE5vBoF)LeH-NQdU?-AXD=F3q(-xc+#Qhb zd}qP6-H znI02z`1nomp9-R9yVNeuiN`Y|#XO4D5ZFiT9g@W(Hy6=c(P4C6G3fHY$ z7w;-y%(+)4Y&7n;VKWNiZ`$#JV}*@#J7C%4hR=b=S@E}37mAH9h>vHF9p_sDe%NRn zzum6h{T8g|YLDF|;;lbu=9q-{eFBlo9OUxC3onGOg)iDK|Lg|2NAYoey>jU|U@ANA z?Ux%G5QgB-XfvJj#p8*won)(%7^I4eKxBCSyJJ7d1bnKBNAP-|DV$($8Jc->EYF%rnpA z4D6`8E-Vg*sX=`2$i2+_QH1irxQj@S1&M zmx{=zx*gm6!f?~#E!BO=d=_4-6(hoiA}`d+d{z=%Pw~G3?4T{jpL^H4-US_1KwOAx zid|kXVQOT0a&s8TwG8XKmSPl+_T&qDdh)pggKru5*02ARG7?)l^PxHTU=Q*BS}|KY=jZT>`x#POnT?dE^_;5`qs$c@Dk~_dZt@Q}~N`AV0o!>sH35^Te;4;@Thv2t3sH*4ka@fUG#* zTkFGFh*ROS=EcvkF^$-nf}i_H+FjM1E>=~W+mUs=8FGXLu`$-C|K_3RSMPcC^-Uw9 zbEd;+a2}KY#^j{$@6P4-oxfz+Q}^F=+b-k;oxH?;pw_qS0iEM~Ct_?IbCsOdcz>jF zirt4AnQM;{^N;j6V4pEQfW~ilEO~BQt7s>`&iA=}-*u+>BB&Q&z6*XM7BrSzM6=@Y zz?leNrmZfkvZcq5Za7&KTUsh@EQj;+F@Xdh2h_*BQF1Z24ZdgCR2qxJiHQl@RO}Wz zvi7N+^hvFJmfQ+QjvOgGeeBrkpG=$>ja_F{6H2#LQNW8B1f>Yl1q(%rKp@gC2vLfN zG%1my=nX|cIsp`<2%$v@5C}z(-lW&iJE0@eA~n(>AtV739^ZR^zw3JQWB#3e&OUps zDQDKq_)y1tdAr*QRXK!XG3G+GQ4x9%`<~8QshZkG&ixAxU>9XT&Kr7tMWk1%NYLFM zl`N{YeVFt@w^$>Q3VI~sWMHa8o>CRUW#f}6cz7qKVgZ#ONZb&k3??=Fa-U=!B>GXm z&s*}JR7k_G<*KUu7&=&G!BL`kt6h*OtDztX>`Lt{CcJZxkN~NydQ=SmbmR6hme`=CMm-e5gt-(Dt(=jHVo6QY*c(iMgB?}+Vi?Dm>-;^77)D-O6%)F@ z`$yW{hKBT`VeJhsXW#A@AaBk(n@vI}hReOY#Sc8G_oR@mXvgesDHHI0pp)FmPElQD zFYtg4dQJ&iTnbl4PD-Zw7@Wki3fg2|i1fX}?*AdvA^R75gb{py{v@fvFimggDu_ux zO75(wjz@KdORre}DhHdpe?pk0Z!EE5rS1@F+M9cv0d>=nh8?oMTixy+-9*57eg! zFA+z!bw^Ar5PY85s0k0rOmmu^pzbSn2pv+=@-y?4im=8Xl^3Ym$w;b@q|Y^Lt54m1RN9+im}bPEX|0F&OhtB#qJv+ zQ*|d#KI|8b+p$dG^cqG)Bbk*4@>RCzZOGQt-GkR!=TKK99p03)-i;F8Z8hL=xw)XX z_MA0X8%r$X$$}&VCc;pvXVv})dN{N zzgQKgnXvmQ#lZAf#80%pFbY$6bvai9)KsXZ$3s*TNo3lx==o@8JV#fLXN!3m8Jyg` z@g}Be^8Q2nLQ!q^1dzJ@-Gq`j4fzi((gH24KqiRPLhRqAeI7b1fm++-et)|VGo7DE?bo_LSx-VNrQXRt|PYNgXu#=wLZHF%)LY*BTO9N)@+MQ@XP26lE2pU z#1os-^2WQh)z`aFU#IxdFOBraZb0)Imo#T|*s-u$Fb@7>;>3Gtyi3*R5_NJSSRW;Z zgI=SiNloCR$$rJq(L^^0XJi3a{;r3tTqnoDto@2Lk7E66t#8Nb#^u-QJf?t0S0n@l zo4d1m4aGJs4Tyj||Ft&Jr_los4y8{<$(zt{Cd2T~sN7EohWDw-Yh`@WdI0dsaXAjDyjWPizcOLG~`MdKDK_K@RMHQB5Y?;gdX5MNAd4TmVb^p z3astpe&+>u%X(emmfG$VV%|oudt{2$ox<= zO>Ok4x2iXn(h&rYeo6Qz#>0qurf&Q=>HKZ`Ici&vC9-(>q?tuJV5J?|%(3^~dHucg zf~hbZ7U^dn#h4aiP6n#^mi&;2^5dg+fUf&LV>Ig7FI|CP%4&APOK%9IN^qIMk-3bI zGA%CP?#YilqQz`$((|5O&|%<+wkfN0#Wf81_s7RtmD_4@RmM%-+xNo6c#ebIKivuP zeQ;8c9^k)Db?tk2)o)MZpVGbSuW?VzUFlA}!~XYc&;!^i>~u1_Xqa;pe ziC_`X$Su9oH&xl{<7upBTD*3$v?V3Y-Tyd|AUgdn;@GgQJFMv03TeV@_n#JL;DW3q zy-}pDxg2{XT}9nP>~n;IZxrjcinW9dXyKS@2W-?O=?OLmV3+5A-(HU0Ki~R02g}%? zG%@|6Mz--rDZMd58G7DL)!5<5mYpo}r9eZy7O1pSzKlNy{_y_l)6`Bf<^(3P!!pxb zO7!578>dHu$4}&?Ge~cb!d9ghU!Pcvd&+PzcW+4(auIPDQX31*-$q4rPrtU{YG|R@ zW9@P8(|0*AEZtIpt1^`>B+mMn$X{@)`hQ6DlM^(cdGRQzHgm0=<^`FY_KUv_(dkKI zW%JI52$x$MEdM;*ED#CH75mX{#sygx#?>6a*f$pi%@$v4yz5jsl%72bDh)g_!l3h_ z1nWK4hV-GGA2O;4HTS{efN4L~2rP$j!#(58g+ZZ9Tz3w3dDW{`_wbMadp`oUF3<~{ zxl!&qPk_#>7|I8K9-Z<$Zb*R2+OV=l39OX8=(=WRWxEAf|Lwg+-##2OM$qhY_DCzY z%p%FXZCCwl-dYYuv4C6$x1c9O!@U=L-ME?q2|jz%cw! zi~+GK>~CYQ#jqBcH|1h#{Fjlq=GN8Maa`k|%o+GZUtmhtFVoDbzew;#Uk=A!ZFqo# zWe0a}9R%O!`}el~BZ$hqj>X&8?O&-$zwL=;$y$t+uGZ3h_ew*_1S{7(QA@o2snfx_ ze1PeTkY1qM%*P4g%Zb`CPbiVgO@30-&tup-`wHwD>05tEQ|6c%N4V_}ny1Ha?~h^% zCN7hJHBTa%blR?xOL}sb?h#yh6s#)Nk{yJ#gA8Y)k=JF}l9N$Z(PXP{LAj7yEwv=E z1Jhpe@Nk?YXKgV6E=pY7iNilLZBM6dI-2*&je-;Cf3f(q2MmP-H5%+p`k|V{d~P^s zD2jJ8VXIKlF9&{d%+I(4#8vEt2zWQy&j`ghV{cwAZVG0ND|dt-%3rFK&U$zJfOJ{B zqwLx?xHtQ&rTB>GC+fxzwYcbJyqrzyFB;6tG=yPC^IeK+3g_}xRH&E{fs^EF;v?br zS17E$b_|;xER923!3@bI)6NXvqtzC~H3Mam&$SkrMND|exX!^YTa4kCcDfzz%`S3T z&gj|#S`q2lvs0J~B~^AjE!DNg)hRgWSMepNt8)1hPs#ake~RpSI+;YWMG8?Ar$%Hn z2|dJ@8J1u3^Yil%f5{MqfUOiQOuEWGIW@?mb!RDce*mg54^XJklA;}4_*RBrr^~!n#cGyD`2ctFw*Rk$LA<; z*goiuo^1gt$Q_OS*!&UqQE4u)t8>d-zjxirSMu{zD_TbpEd8isgLUt3e2U8FA0p(# z4-`zK*mZksV#Tu0oBn{3Lp6$jBGEdBL-y&V3v{gDd&Tk_z7sxGFZVd!BFFfyI`9S+ zMz-!(CM$7%psdH)TZRD-QH&fJ`8hjOfjyEB|Wd-;En*G zAFp*whj;u_ zpvKg8M3Q=lkG5S~!Stm?b>laDaxSuwG{?D6baisv0zFkyfxMEtwDN5XJZ(1kcmEXg zaj#Mq6h38eWHT5mx(+%hkGa^fv3;`;Sry;bkDz?7$lx$$CtX_##HTJ#tYK^C9|2t@ z?gUj#{m5?aT^7~GncZ{jdMas=pNzDQ_IMMGl8^1CFRM-2$$wPGMetvgHz2{(g96VV ztNY&B=R6m+I<_y}1S^?p0TejeDHIO@=p#L;<9(|KY^iPyb^Tq0VseMsQrI`Q>b3VsxZ zr1APnE-h+q+E{)?PmOvcR|;)67x>=Wd+wm;KX6l-%j5J}3wyQhnYWOZwL_`+-1#jW zb(L#u`wU^Zc*X^(CO*jLeI|cz=ji(mUU!ii8W+S>IE{Z&sMpsB-M^QIT5A(i24s&M z$UGj8coTps4sx7lLcN?{wxFsZgjCD{6%)}TWdL6?K`y@85FaBburws1F*sif6ipF- z2TXB5*EqXV>Bk;JQj-Z65I!Z&G>pZnwgsohrHQxfmzjZzAjthEz~|9~P~7I+lg7RV z%LOVveJ8 z@`T1r(c~Q*0Js%4u(-Nht0>exhdy4a02Xo16?nw2K<|x}k0^L6R5^vM2Rh2xHDoSH z*s9denU9k>K{E)5gkSaCS|v0TxAYD+-05vt%Bd$KK;$6 zTZ<*%mT5XgTRCi9tROz*X;a}|eJ+$VnbrebY1*+UBicMRwS=X?ARaP3R+B;YZAhiZ zC5}DXB+-Bxm!a5QSY$P6VVYVCyYXef+eg*Mkf6Ny{6`O5+QEr?;DA?;aZUaRrwjUN zd}H)WWo+|ozSKx+H4<7uAXAoN@n0_&IX(&F1oSpWAl~3y_w5g+yi;!i9(;x8NW`B{ zCZ6C~UIuuvAByen>mwnSUVdHk0V+lJ&rQfs0xH(~EWQ&*B&b27$_eEKX8i?g_J`yY z;=xuPuRv7EWQM)6a)T*Hcck3Jl_rnPBeG}dH;9(Zcu;r9+b z+2X?VS`35t&}Dtir)=62mecjQOKz*4x=^!*t+)fP+g}F-lUtB4jW2G~ke;20mf{ys zwJg*JZq1ZnG4yfnENsGKJfEmT(tvxEcnw14`r3#F%t9n%kynN#C62g{OR(9qSrTcMU&a$ z_0*aFo{0a@&~>y$DV~;;_1P}udlP9sQ(eoT7*R8J$L>F ek(6f%bH>TU<{2_s>-U+{2G%yvD$#uL;ePavn>uyLw`w+526gpvdt+|PK-CsSlNI7(JIX$ehl_!C1z*QNei z$HNI%nE4DFS{uR$XBq2ISh>J}M}=Z_*DMC0>MgUV7Qe;-e9ozTW_qA!*1G*PjL}=! zrAe?hSPxh|3-h%>Ly&{xT|e{JVo9RoN+%!npP5;iVc6W**x)W>fks9SXyz}Sq)v>F z4^O!@I;}ZEWgV~4&Z|3Le4!Yyud=u$>g6xbw`*ruExXs(?wIo@^2+(+UZ*RqpVPS4 z^MYQ`I|845-+c^m?Ua1BADAfmprs0vBF9}6jJt=5_!}4)*snIb?dg=F1-(AsF81|( zLcV##v()J+=jlL2bTrk~Zz}EM;bdp0+G^+P!_R&C2Vn6~XkHpBF4R91+sFS;g-_J* zBVx)t9&U46n+2l3%kAk3Y8dSC`apv`CnmOt{eF_-UreEi!s>o8?&(_RT67*QFFaF0 zM9~|68!wcAkWn~^M$CBKTI;g0L=eZh4Vj!~U9^#r*(w7r&1_GPLpIJ91tpwr%!4kN zKWZDB;&17;lz2CpY`gw;M$SJ6f_o(v1S%l771%c4UyMi3cz$(tMe6;kzZrL3NSC3V z_`)$9z*UaeCHRw$W-fTvpN=HBUN;3lteEX{ZvrOW1VoV;9L}tHKbS7QiAaCKO#ags)sgSzDPt8-Snzvkt*FcWE?oz zMVeiLTsZ2Gemp)`%AxN(-L^Gj*It=1w6hwx>@|~=XNhkt|_9v zc~nao5p;RR81H&i0o6N5zd^&at*70V0Tjxb?Q@0bS%pXyfn&E!QXf(Sg#2wP!wT~} zj^6Hl1FOa^Ttd<brP(dk1%{aIC8$HL9zpYEDT0O-2Rs4XR&Xuk z#@LF~oyok&1uk#7nqXn8>#o7i(*CkFP?-=yy%*&y98(e!fhU1eFps)GcX#|)-1BQ2 z>699JMq9Svb++mNmIb|e7H72pg3DdXxfEE3Ke=wMJ5zl$oGW_wv0%GV z%*I(?RNui%TS$49c18WRU^^{gsl@T%mtc3*QsZJS9A+@0Ia&Zh4naN&3mO2`eMeS< ziEJ^1QiLvKUM*7FBK(ooy4h%m6R*)my{xUa)|G4YN5~new06(@fGsLqQiXhAmBc)C zC2fNL(I%WqP=FpH`YuzK7pU{cS1E5$SEz|CR(X@>=&z{{%2^j-8+~L}ipL3yoEmFd zO3MXMA>)Dky13dl`K{?b3DWsFXRg{w6x1ry+jVlyUsNMhJ%lC_Z?5iIU)M`CO3Y4| z*^2Dfv5%$>Ls~;2Gu3u-`R&kje!VaEt_uo_i*7Q)3vTxN3Ssv} z=f{2tG08fgnY!p9Tt_pp!y0O}RbMS}sxx__o0a&OkSz!To!Ln=#QqDd&-rk0Irw&d zFoec(18@@;jvx5hOslz^|Jpw;Pyh-R6stl?=bx2O3<%d*q#w$&`TtT#)hdTMq;`yj zUW7e5NA+Fu;TgCMGV(uk#&2@tJIe=8+7-JBjZvK1%ucW%H1sD>iG>gkMV1Q$20t17 zr65B&5dQ;w-{_YV>(&sdFBpqbq^6o%RHuEObavBxTr@_}t0q#!q5~O{r!Ir*KrNS)TYja()|5IgJ%hUUPWJl<2wAxce zMTNIII3$`5PnN6{(NIsX5Hn`GuT9n@190aZ%zzS0zi&c36vD@SXzv-2g|{WU;Nrfg zI2oHqvB^!EF384ptJ$>yXqyCdPm}S?GEeq{#;TvtN{+EF(kT^*;48dDrS9yTanOX` zzoEDI9DUU8IR~0T%fCKd@3Tbgu*$85M25|F$~w3W=(e(a+klVA{|^sRY#`SV`toTA zRMJql*=zx&>D)OIN-uuW_G_?w*tqX3QJ6#rhV|a8PJK z&u_T-bb};FpH7A#y|)sdC)Kl@^=E3DM^U?wO=-0Y{i~Tg7$G#4-$H-W_C-mchMsoM$W(it4-O3|Ac!9G(UbH z2qCu5=|33Xen(5=`TrRewl2YhgtkxO84cW`0h4F>b+vyBDql*qRm(0?w^6US>pEaF z2r9wmke%U#XqmvFEmppieq13o;RGZ*4u8cDh8a<;QeM!3zw7AE2qw+eV2<|)(`&O2 zFU>mDM`jb2Bex`>E|{{-$KOKCo@XB^Mc|d;>_0=mo{An|&m~c9qLi5#4K`X0*?X2S zq}#7djQt9|_NMXSU(0WEy~E$kq<7}Z84fKszCAL#!I<_wFeyp=+G2rL$Dm~`h&n`^ zv{vA+|3y!2BBXf$2omRuqOIMRxqRTZ-CmV5-uySov~Q_BNmkp`G-#_^aKpoPar*b) zY~r#^r1m(T1wVd_$T%EqP?pYf-ER$%Thz$NXtg=u(NlrFK&@6l0;ca;XLu6&ly%hs zw2p@Zs_t?0sFk#nTjQl{dMvOM#YR@2jBg*oq7F2m z00v`Ib{Fitg>e%Fh^LMHT%4GOhJ8^G#Dt$GQBz;v3-6`BOD2jCuGyu9Ts^%CerH~T z;J&EQEB2mlZZNubDGW%ADwUbb=8_(6pGA1sGMQ8{Zia zup?!(jJ~g|>XHw2k_e4l?Hr^@HLZP+^zyeG#F8BOLnMsUAT6$yFy@K675ZZ*WCo4d zk<0r6=uahOy&*SA(BKET?qMefC9MyBu$r0s0D+LBWF#C&`WOWu=G?T{K;ZO+tPv1HBM46tDiN!k01SgVC;2g_MBE$)~5%F3- zff2Cr&Cq|vX!jyi+CdibBnh%xJ%*G&ho+Nqex|&=)-R%n8c-GFhm1x#q)XiHIjFOISoVEYUHAIvyWj7FK2($`bOU4>n; z2$`^r*{{S^KRy*=%%yr`MIf3F8j!s?@10e?-K$^+ro28`GEGd6F_*l5vizPX9)g;; zAbY1?FHUNILbGA+{_uxVv{ymK?o9e_e8t%2RPQCiHKiB;O@7ikC5+_vMC4{7#oNt! zW%XtU=AO-zN4Gj07QSmW;WjXvrq#Jnd&LOsOSkaI*Q@$r9eTzj)4<8~DQF*IfrWOw z$cUBm5;Bx2uq8{tXSHs(glAraMuwN7W9zT0r>EEB_R)MZbB$Jd{)s0usW~#MZsgDU zVEGB{Q+r$6vOB)QWY7PV%pWF~q=RYB_V)IE=Lg{L54JTm{VmN{F`#{>Eq$D$BWzn} zi^#jMGkw^bF6wdN7@k|Hm$Y^;oLIifnhwy+TWR;3_l=MMt&=}4KQWCkX~5368<~G6 zu_Hgf80#4diFvzo@q=oEUjLm9f4Vh0dsaE49YiYf)%=i`mlNr*Z!oS;8~8Ly;r?7a z?`KqF@b;X<8yIiRTLbnIbUxjZawzz_9Q~{ENV}Q3LIh+KTye*(Jp&@;2oMJk_K)VP z3qYD>Gvx!f7JWg-y<K%| zp=X?lQ-$0N8Mc{Od`=C$MLvy;7>QW?bWrHDj^TbNhju~)#u0hGu?^U1P5JzZ5IwhU z?!I%}v~kuUt+?0xKWjvoT2Ma-5<|A?XnfaL#y%D zBx~ZZ6>N$1T;*;|V`V}>UxXhp^x24tU)=q6msTYB;mmoUjtm!Zpg(J_KJs6zpsV>5XbaNE$m&^yu0 zPK=I*#Lh79mv^sjYJnP!t(EU^$Nv1$f^qHy#b*Q&dU3Sa$NI=pXst+noN5L6Vh70{ z$uTa<=la-PB{z!ByZcH~Yw^@8 zh+xwor9irUkN=F-QnfwKw{CggE43rSk4$g^v{EyY2uJXYe%ua>4DW@*6Oy!|Uiik( zXcC_KAd>UcpaD5qmRF6D5rd*_D1T{ zivRIdKF!Y|9HETbUHpoN*#C90Km+?eg~v^>uWVp09+b_0J))AxaRt3JGK#?x2e{{qXH%*nXJ80yB4UcVK zx~&Ip#pyRRQo7peu)uyQ=I>5jdGru7(hf&ErbMsd*A6+>fW z&3DrGMiHEP06w?`JaR_J(T_2po>2wGOTJO!PN^Dn`|E8>QPN!|#C8Ik;I`__QIt!a zNEjDT$uF}YyK5k*XPg>0nXq*dp>A>}bhrx^xqFbGfu*I&$B*t)@o$f0W%XW(z)F7a z-$dq#&5rqa>TO$+_Rg=Ld4byfV0*1;B_n%~7#3n@lm0qdY4pQ1;~<|1KisKiUmvV$ z{e3YC84H!yM|VO3I^kue(l!AAyF&5;=j!XzONT!jTUpdVLY%MZ@kf^YC#smtpO1(J zr0tC%xUhJ!^=qndygB6JGLl!>=Q#;7OQpJ0P1FrdPz&y336c#WF62OW>tvvwSY!`( zU!GF%*D9~~^IeCN{z=O16~*(mUZ8YY0au%*M~hy(QO{a7hJwugHMhqqSvkX( z`bqc#a@~C8LdRZo;E0tdyOT&X=3#LhuCfo?msk$jA(gA?>AzpIb_L8g6E(58TWe~< z@;wV<1(MIx)5;VeAxzq{W}{-#lW{+idBNQ=)p zrtlsM$haUKxd#2VST$ZHRHBzNPT58yADCU?Q4%+PN0sNK3VHrcB$@PcRwDItr~b(= zVFDyE7BvT1&dp?f0?GW17P~V9OZ(`5YiZK5o z&Q@usCxj}a*2LP-;LPDJlEM9*XH{!I%D-Hr6PFTa{gdGS*C*~qGs z0%kRp;J(2Gh{CkYW{Ht?j!1Wq?q3XfB#Ug5c@2WLklifep0J$#V~mTW7n0lsPu)NQ zRk}0fZtoVt&f^#h>x!kTH#Tk)Za;G@e=SUkby))^dB&XD<8h_f4V;>8RZTm09mGz- zuYmQtg=`p^S*4aFIv;y*>L=T^BRA?jb$k=dBm$RaTP5U|Bx%H4g%%mw-3p}D6Q688 z$uZ0(ZWs`7+wFC@1YM&p%AE;}#NZMfl+HZ{y8DXMz)(es)_`(8a}Bjj@2Bqb*nlY4 z2Lg-frE8(AW>vJQyDjUQwh3XSKsq1CaD%o$9W52 zut~;CsqDp%xnP91g?j$=IDwyNg6{yWT>RMd{V_e#$A(ZOCPNjBVgGO+k(4}03m_?? zjSl5t+RuPagu@(dgz3uFrU|{!3Z;FHj;g9Au}k$A=WX8$n`_7fvpe(zC%F}`e$2mh zuZuexmJ_mC*Gx;NM8h^NaX%G+!mD|3V(_kDOp}4SQH^z>)yN6OxsaLKz^VK?X{e0Q zs4Q?b(IYcgh#Ha6MqbYESu)QW9SS=Pv%a{pTFhj^rr-m7#O0ggd%v@h`K#EU+_3y! zsR*say-utAq;$>2`JoqwAd3eAu86EhV=T{Goj>=;qO|LK#jVO=MBki|C*X!vV=3gR z8>zp{Ch0?UG-g6?ChdZfE3`W*!#(2Sz*3)j=Ew*lb~(97-VS4(Z+N z6K_y52R8Jwd1E+&t@evNe;faFh1{7x&;Z*t;7B%wu;&+gK~6g`1k=2F6d&)>2qwWMLiBZ}H`o)(9v={VFmE+A}9C%(=MeEP_E zcQ)>OOr?jF)3duuOYm1nq(|yETdp7g$fHi~Q!oX|^X=esB$c@8cy4#rO?lX`ST+28 zK%rxT>7NYLBxY>pyLN}`z=vj~M)WklI_C$X-R9YqqF=<+Ti?P@M3&}~=`%K|Cm%?K zaJRag9yS;oaJ4*xG}BO6ixzTMHy;X4#EG9ag7Z0O4FlGkw6#AsFfw!t1;6m*(qOyY zJ)^y9C6!+Cqj|&{`X~qxh{bbA%*t;0WIV7zLmi-w?@Cu)e3fo~1{}%5Vx*LY(25@o zpdX8k`UScam0AF$4tWFugowZ8s&eY`6B|bop*KLLc?RA}e*o z-^bhAn1^nemi>4xH$`u2;4W2qx&rKAMM`c_AKqyN&h#<%|pxlhW{FiDfp F{{b%nsA&KI diff --git a/img/vrct_logo_for_light_mode.png b/img/vrct_logo_for_light_mode.png index 75b1afd911aec2a3d791198c1fb9c6b071529e0c..ad2b0b95aab222183fd838922405d513995ba6dd 100644 GIT binary patch literal 223936 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 V0&X93{o|QL7xj$J6`ira`yXhH)Q$iE delta 982 zcmX@<@{3)uGr-TCmrII^fq{Y7)59eQNV@~EBL^FhY<{_uX`-S@J#(q2i(^Oy-W!F<{nD0B(Hf{4)c(*HfzC&!;QoBZ--CqQ6i1yCe zv_eyN(o0#7stK1Eojlm8_ZMIIb71C=O5>T;=?M0+O8t30<@7dh$4iG9p3UDN!>Z%X zcslSvkqD4!>A`V(+uXeHJ(K&}O>Rl37+RKGynaDC;L(ZC zCVfoz^VeTa6E(?8p5T(F{Zqb`Ge|)3h~5#EUbD7`b_g?p#@EBlN3sgWT%yG;D75mz z(yMMtMN++PbL;N*RL2WE5Cxm#xyI*J<@S7Yvs2b>i9KuHpQ`*3xqSBJjN}PNo_NeU zyW4w)apo4mg`w>^k=MjBTFi=cPTyO6dS{u;a+X;O0-tm+{|a2Raw>oQ0?w!tn$u4Q z^(b%8%zCOYr|smNGfzDPogPfiSa@=G@P&pmPfK6QmdZqbp2NdaHq2rNr~ z{qW)OqbBkShKnz;=q^05a<-b>cP5WE>)sIcT_0ljzGY<8U$`%vKCgVc{mJJVPEWl8 zrpKwz<&$eZHpS~_*qf)jrig^S>=*qcI!*eR($g>Z^TQJzJHBpNq*P{F&+)(|FL>ud ztCb5}mA@_Cb92v9sUC~zw}S$f23=g%G+o5Fx7ccKe2_=JUZ>K_4y6qCd4k3#7gjm1 zzWj5iZgu%if5DQh$n{%hInHRE>3$+;LSlkS$GnPu@m*IM(p5LUOwi=`wN>)x*=etz zD`;%Ivub`2i$4qRSx3ta2`aNHs^-&DVLRU%{KKk*X>MyI`6v0;wdUZE}3oh zEZkk3#RtC2@6*v=uT*+T&-GH+#vMwFpYwjP%CY(W_kHs8NiPn>Jn`^wSQ`)#sqXkb zBYfYX+uZBLT+hXH$2Kp1!QtY3`(|6>Gcij+Z*TpqWj`OOe%U|!=grUi>X_0#{^Yfo qsADetvc4}9m?8O6BRtc5eHpZXYz`m>flI-YL6oPfpUXO@geCy5K)P=L diff --git a/img/vrct_logo_mark_white.png b/img/vrct_logo_mark_white.png index 0a6f1968fa8e156c214edec51b9c1d9f928cc6a7..f99621f78f88c220a6b27c7b29976d13f167ad5c 100644 GIT binary patch literal 8225 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 H&f)(B{0fY! delta 861 zcmZ4J(8R9T8Q|y6%O%Cdz`(%k>ERLtq&cH;Kxs%I-{h?OJdZu|oLL58lCrlL5 zPUFz2cstvF)1Hsc-S%s*@7}!oq`-~5?NeWt+;Z6DbgIv3;RSuM?z*=B4Rhirgq%+? zat_*Y;L3wT-)-LKl`}N4O+Hz_#Q79o-Ruo}3vVetRnYQqVC7t)z$mJ+poyg@Wx~&z zdok;;uU+xE@3GD9b-yR;oPTV4jAh~7fcrUSwk8{t);ltC)ug#@A0M_gr&iLNPI9LIamj0E%FJ6AMt((9uT7Q~a4!Ws9L2N8*KamMwpr zuYag?Vfmd>D3{pN8ngQ9D~a9t-A5ORmGb(tDVzq{Ws-4z#=pkGryGy2@|xw%=BC@% zYaP$}SoMWW*y_;f%jYRndQVI6<>XQ;_$aefn&!ZS%=_#w!{mIISxtq-udk$*8F_-hny&z+5XE9 zIDOoZeEE8AdedlMsg}6lk-PkR(sVk*8R{k+Ia}c+ApZKS`_^qAmr4gV?Rc?)-`nZs z&gKQ-CMr8aCVt|yaq9AWC3f56zq<#E3?oOHae+qWBl(W?r4E~6@vd$@?2>|l~dq)5O diff --git a/vrct_gui/config_window/widgets/createConfigWindowTitle.py b/vrct_gui/config_window/widgets/createConfigWindowTitle.py index e638f61c..4c10dbce 100644 --- a/vrct_gui/config_window/widgets/createConfigWindowTitle.py +++ b/vrct_gui/config_window/widgets/createConfigWindowTitle.py @@ -27,7 +27,7 @@ def createConfigWindowTitle(config_window, settings): font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TOP_BAR_SIDE__CONFIG_TITLE_FONT_SIZE, weight="bold"), text_color=settings.ctm.LABELS_TEXT_COLOR, ) - config_window.side_menu_config_window_title.place(relx=0.275, rely=0.5, anchor="w") + config_window.side_menu_config_window_title.place(relx=0.255, rely=0.5, anchor="w") config_window.side_menu_config_window_title_logo = CTkLabel( config_window.side_menu_config_window_title_logo_frame, @@ -36,4 +36,4 @@ def createConfigWindowTitle(config_window, settings): anchor="w", image=CTkImage(settings.image_file.VRCT_LOGO_MARK, size=settings.uism.TOP_BAR_SIDE__CONFIG_LOGO_MARK_SIZE), ) - config_window.side_menu_config_window_title_logo.place(relx=0.08, rely=0.59, anchor="w") + config_window.side_menu_config_window_title_logo.place(relx=0.08, rely=0.58, anchor="w") diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 0eb5c5d4..cb8cec2a 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -40,7 +40,7 @@ class UiScalingManager(): # Sidebar Features self.main.SF__LOGO_MAX_SIZE = self._calculateUiSize(120) self.main.SF__LOGO_PADY = (self._calculateUiSize(12),self._calculateUiSize(8)) - self.main.SF__LOGO_HEIGHT_FOR_ADJUSTMENT = (self._calculateUiSize(8)) + self.main.SF__LOGO_HEIGHT_FOR_ADJUSTMENT = (self._calculateUiSize(6)) self.main.SF__LABELS_IPADY = self._calculateUiSize(16) self.main.SF__COMPACT_MODE_ICON_PADY = self.main.SF__LABELS_IPADY @@ -95,7 +95,7 @@ class UiScalingManager(): # Top bar Side self.config_window.TOP_BAR_SIDE__WIDTH = self._calculateUiSize(220) - self.config_window.TOP_BAR_SIDE__CONFIG_LOGO_MARK_SIZE = self.dupTuple(self._calculateUiSize(30)) + self.config_window.TOP_BAR_SIDE__CONFIG_LOGO_MARK_SIZE = self.dupTuple(self._calculateUiSize(28)) self.config_window.TOP_BAR_SIDE__CONFIG_TITLE_FONT_SIZE = self._calculateUiSize(22) self.config_window.TOP_BAR_SIDE__CONFIG_TITLE_LEFT_PADX = int(self.config_window.TOP_BAR_SIDE__CONFIG_TITLE_FONT_SIZE + self._calculateUiSize(16)) self.config_window.TOP_BAR_SIDE__TITLE_PADX= self._calculateUiSize(30) From 83fab83f5bdfa9cbae4a919cf208d1f919059a6b Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 6 Sep 2023 04:18:14 +0900 Subject: [PATCH 109/355] =?UTF-8?q?[Chore]=20=E9=96=A2=E6=95=B0=E5=90=8D?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=20updateConfigWindowTransparency=20->=20setM?= =?UTF-8?q?ainWindowTransparency=20=E8=A8=AD=E5=AE=9A=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=81=8B=E3=82=89CALLBACK=E3=81=A7=E5=91=BC=E3=81=B0=E3=82=8C?= =?UTF-8?q?=E3=81=A6=E6=89=B1=E3=81=86=E9=96=A2=E6=95=B0=E3=81=A7=E3=81=AF?= =?UTF-8?q?=E3=81=82=E3=82=8B=E3=82=82=E3=81=AE=E3=81=AE=E3=80=81=E5=AE=9F?= =?UTF-8?q?=E9=9A=9B=E3=81=AB=E5=BD=B1=E9=9F=BF=E3=81=99=E3=82=8B=E3=81=AE?= =?UTF-8?q?=E3=81=AF=E4=BB=8A=E3=81=AE=E3=81=A8=E3=81=93=E3=82=8D=E3=83=A1?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E7=94=BB=E9=9D=A2=E3=81=A0=E3=81=91=E3=81=AA?= =?UTF-8?q?=E3=81=AE=E3=81=A7=E3=81=9D=E3=81=86=E3=81=84=E3=81=86=E6=84=8F?= =?UTF-8?q?=E5=91=B3=E3=81=A7=E3=81=AE=E5=A4=89=E6=9B=B4=E3=81=A8=E3=80=81?= =?UTF-8?q?=20view.py=E3=81=A7config=E3=81=8B=E3=82=89=E5=80=A4=E3=82=92?= =?UTF-8?q?=E8=AA=AD=E3=81=BF=E8=BE=BC=E3=82=93=E3=81=A7=E3=81=84=E3=81=9F?= =?UTF-8?q?=E3=81=AE=E3=82=92=E3=80=81=E5=BC=95=E6=95=B0=E3=81=A7=E6=B8=A1?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=81=9D=E3=82=8C=E3=82=92=E3=81=9F=E3=81=A0?= =?UTF-8?q?=E3=82=BB=E3=83=83=E3=83=88=E3=81=99=E3=82=8B=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4=E3=80=82=E3=81=9D=E3=81=AE=E3=81=9F?= =?UTF-8?q?=E3=82=81=E5=90=8D=E5=89=8D=E3=81=AFupdate=E3=81=A7=E3=81=AF?= =?UTF-8?q?=E3=81=AA=E3=81=8Fset=E3=81=AB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 2 +- view.py | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/main.py b/main.py index 1f834b47..ea563ec2 100644 --- a/main.py +++ b/main.py @@ -222,7 +222,7 @@ def callbackDisableConfigWindowCompactMode(): def callbackSetTransparency(value): print("callbackSetTransparency", int(value)) config.TRANSPARENCY = int(value) - view.updateConfigWindowTransparency() + view.setMainWindowTransparency(config.TRANSPARENCY/100) def callbackSetAppearance(value): print("callbackSetAppearance", value) diff --git a/view.py b/view.py index 26cc8c35..49cbacf7 100644 --- a/view.py +++ b/view.py @@ -405,6 +405,9 @@ class View(): def clearMessageBox(self): vrct_gui.entry_message_box.delete(0, CTK_END) + @staticmethod + def setMainWindowTransparency(transparency:float): + vrct_gui.wm_attributes("-alpha", transparency) @@ -416,10 +419,6 @@ class View(): # Config Window - @staticmethod - def updateConfigWindowTransparency(): - vrct_gui.wm_attributes("-alpha", config.TRANSPARENCY/100) - def setConfigWindowCompactModeSwitchStatusToDisabled(self): vrct_gui.config_window.setting_box_compact_mode_switch_box.configure(state="disabled") From 9583203a350b4991365c22ac1a0e2abd54e112cc Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 6 Sep 2023 04:56:11 +0900 Subject: [PATCH 110/355] =?UTF-8?q?[Refactor]=20view.py=E4=BB=A5=E4=B8=8B?= =?UTF-8?q?=E3=81=A7self=E4=BD=BF=E3=81=A3=E3=81=A6=E3=81=84=E3=81=AA?= =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=9F=E9=96=A2=E6=95=B0=E3=81=AB@staticme?= =?UTF-8?q?thod=E3=83=87=E3=82=B3=E3=83=AC=E3=83=BC=E3=82=BF=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 57 ++++++++++++++++-------- vrct_gui/ui_managers/UiScalingManager.py | 3 +- 2 files changed, 40 insertions(+), 20 deletions(-) diff --git a/view.py b/view.py index 49cbacf7..50bd8f8f 100644 --- a/view.py +++ b/view.py @@ -304,10 +304,12 @@ class View(): - def setMainWindowAllWidgetsStatusToNormal(self): + @staticmethod + def setMainWindowAllWidgetsStatusToNormal(): vrct_gui.changeMainWindowWidgetsStatus("normal", "All") - def setMainWindowAllWidgetsStatusToDisabled(self): + @staticmethod + def setMainWindowAllWidgetsStatusToDisabled(): vrct_gui.changeMainWindowWidgetsStatus("disabled", "All") @@ -321,10 +323,12 @@ class View(): self.foregroundOff() - def foregroundOn(self): + @staticmethod + def foregroundOn(): vrct_gui.attributes("-topmost", True) - def foregroundOff(self): + @staticmethod + def foregroundOff(): vrct_gui.attributes("-topmost", False) @@ -377,7 +381,8 @@ class View(): self._printToTextbox_Info(f"Detect WordFilter :{detected_message}") - def _printToTextbox_Info(self, info_message): + @staticmethod + def _printToTextbox_Info(info_message): vrct_gui.printToTextbox(vrct_gui.textbox_all, info_message, "", "INFO") vrct_gui.printToTextbox(vrct_gui.textbox_system, info_message, "", "INFO") @@ -386,7 +391,8 @@ class View(): def printToTextbox_SentMessage(self, original_message, translated_message): self._printToTextbox_Sent(original_message, translated_message) - def _printToTextbox_Sent(self, original_message, translated_message): + @staticmethod + def _printToTextbox_Sent(original_message, translated_message): vrct_gui.printToTextbox(vrct_gui.textbox_all, original_message, translated_message, "SEND") vrct_gui.printToTextbox(vrct_gui.textbox_sent, original_message, translated_message, "SEND") @@ -394,15 +400,18 @@ class View(): def printToTextbox_ReceivedMessage(self, original_message, translated_message): self._printToTextbox_Received(original_message, translated_message) - def _printToTextbox_Received(self, original_message, translated_message): + @staticmethod + def _printToTextbox_Received(original_message, translated_message): vrct_gui.printToTextbox(vrct_gui.textbox_all, original_message, translated_message, "RECEIVE") vrct_gui.printToTextbox(vrct_gui.textbox_received, original_message, translated_message, "RECEIVE") - def getTextFromMessageBox(self): + @staticmethod + def getTextFromMessageBox(): return vrct_gui.entry_message_box.get() - def clearMessageBox(self): + @staticmethod + def clearMessageBox(): vrct_gui.entry_message_box.delete(0, CTK_END) @staticmethod @@ -414,18 +423,22 @@ class View(): def createGUI(self): vrct_gui.createGUI(settings=self.settings, view_variable=self.view_variable) - def startMainLoop(self): + @staticmethod + def startMainLoop(): vrct_gui.startMainLoop() # Config Window - def setConfigWindowCompactModeSwitchStatusToDisabled(self): + @staticmethod + def setConfigWindowCompactModeSwitchStatusToDisabled(): vrct_gui.config_window.setting_box_compact_mode_switch_box.configure(state="disabled") - def setConfigWindowCompactModeSwitchStatusToNormal(self): + @staticmethod + def setConfigWindowCompactModeSwitchStatusToNormal(): vrct_gui.config_window.setting_box_compact_mode_switch_box.configure(state="normal") - def setConfigWindowThresholdCheckWidgetsStatusToDisabled(self): + @staticmethod + def setConfigWindowThresholdCheckWidgetsStatusToDisabled(): vrct_gui.changeConfigWindowWidgetsStatus( status="disabled", target_names=[ @@ -434,7 +447,8 @@ class View(): ] ) - def setConfigWindowThresholdCheckWidgetsStatusToNormal(self): + @staticmethod + def setConfigWindowThresholdCheckWidgetsStatusToNormal(): vrct_gui.changeConfigWindowWidgetsStatus( status="normal", target_names=[ @@ -443,27 +457,32 @@ class View(): ] ) - def replaceConfigWindowMicThresholdCheckButtonToActive(self): + @staticmethod + def replaceConfigWindowMicThresholdCheckButtonToActive(): vrct_gui.config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.grid_remove() vrct_gui.config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold.grid() - def replaceConfigWindowMicThresholdCheckButtonToPassive(self): + @staticmethod + def replaceConfigWindowMicThresholdCheckButtonToPassive(): vrct_gui.config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold.grid_remove() vrct_gui.config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.grid() - def replaceConfigWindowSpeakerThresholdCheckButtonToActive(self): + @staticmethod + def replaceConfigWindowSpeakerThresholdCheckButtonToActive(): vrct_gui.config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.grid_remove() vrct_gui.config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold.grid() - def replaceConfigWindowSpeakerThresholdCheckButtonToPassive(self): + @staticmethod + def replaceConfigWindowSpeakerThresholdCheckButtonToPassive(): vrct_gui.config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold.grid_remove() vrct_gui.config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.grid() - def reloadConfigWindowSettingBoxContainer(self): + @staticmethod + def reloadConfigWindowSettingBoxContainer(): vrct_gui.config_window.settings.IS_CONFIG_WINDOW_COMPACT_MODE = config.IS_CONFIG_WINDOW_COMPACT_MODE vrct_gui.config_window.reloadConfigWindowSettingBoxContainer() diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index cb8cec2a..06f5284f 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -180,5 +180,6 @@ class UiScalingManager(): size += 1 if not is_allowed_odd and size % 2 != 0 else 0 return size - def dupTuple(self, value): + @staticmethod + def dupTuple(value): return (value, value) \ No newline at end of file From cd16666aa74be2c795c79910570b4fa69026957f Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 6 Sep 2023 07:13:03 +0900 Subject: [PATCH 111/355] =?UTF-8?q?[Add]=20Main=20Window:=20Text=20box?= =?UTF-8?q?=E5=9B=9E=E3=82=8A=E3=81=AE=E5=A4=89=E6=95=B0=E3=82=92view.py?= =?UTF-8?q?=E3=81=B8=E8=BF=BD=E5=8A=A0=E3=80=82=20Text=20box=E3=81=AEtab?= =?UTF-8?q?=E3=81=AE=E7=94=9F=E6=88=90=E6=96=B9=E6=B3=95=E3=82=92=E5=A4=89?= =?UTF-8?q?=E3=81=88=E3=81=9F=E3=81=9F=E3=82=81=E3=80=81=E7=84=A1=E9=A7=84?= =?UTF-8?q?=E3=81=AA=E5=87=A6=E7=90=86=E3=81=8C=E6=B8=9B=E3=81=A3=E3=81=9F?= =?UTF-8?q?=E3=80=82(=E3=83=A9=E3=83=99=E3=83=AB=E7=BE=A4=E3=81=AE?= =?UTF-8?q?=E4=B8=AD=E3=81=A7=E6=9C=80=E5=A4=A7=E6=96=87=E5=AD=97=E6=95=B0?= =?UTF-8?q?=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=81=A6=E4=B8=AD=E5=A4=AE?= =?UTF-8?q?=E6=8F=83=E3=81=88=20=E3=81=A8=E3=81=84=E3=81=86=E3=81=93?= =?UTF-8?q?=E3=81=A8=E3=81=8C=E3=81=97=E3=81=9F=E3=81=8B=E3=81=A3=E3=81=9F?= =?UTF-8?q?=E3=81=9F=E3=82=81=E3=81=AB=E4=BB=8A=E3=81=BE=E3=81=A7=E9=81=A0?= =?UTF-8?q?=E5=9B=9E=E3=82=8A=E3=81=AA=E3=81=93=E3=81=A8=E3=82=92=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=81=9F=E3=80=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 11 ++++- .../main_window/createMainWindowWidgets.py | 2 +- .../main_window/widgets/create_textbox.py | 47 +++++++------------ 3 files changed, 29 insertions(+), 31 deletions(-) diff --git a/view.py b/view.py index 50bd8f8f..d16fe3c4 100644 --- a/view.py +++ b/view.py @@ -37,6 +37,7 @@ class View(): self.view_variable = SimpleNamespace( # Main Window + # Sidebar # Sidebar Compact Mode IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE=False, CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE=None, @@ -54,7 +55,7 @@ class View(): VAR_LABEL_FOREGROUND=StringVar(value="Foreground"), CALLBACK_TOGGLE_FOREGROUND=None, - # Language Settings + # Sidebar Language Settings VAR_LABEL_LANGUAGE_SETTINGS=StringVar(value="Language Settings"), # JA: 言語設定 LIST_SELECTABLE_LANGUAGES=[], @@ -70,6 +71,14 @@ class View(): CALLBACK_SELECTED_TARGET_LANGUAGE=None, + VAR_LABEL_TEXTBOX_ALL=StringVar(value="All"), # JA: 全て + VAR_LABEL_TEXTBOX_SENT=StringVar(value="Sent"), # JA: 送信 + VAR_LABEL_TEXTBOX_RECEIVED=StringVar(value="Received"), # JA: 受信 + VAR_LABEL_TEXTBOX_SYSTEM=StringVar(value="System"), # JA: システム + + + + # Config Window # Appearance Tab diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index d55a66ed..4e65aa31 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -60,6 +60,6 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): createMinimizeSidebarButton(settings, vrct_gui, view_variable) - createTextbox(settings, vrct_gui) + createTextbox(settings, vrct_gui, view_variable) createEntryMessageBox(settings, vrct_gui) \ No newline at end of file diff --git a/vrct_gui/main_window/widgets/create_textbox.py b/vrct_gui/main_window/widgets/create_textbox.py index eb301ba5..14a5ba5f 100644 --- a/vrct_gui/main_window/widgets/create_textbox.py +++ b/vrct_gui/main_window/widgets/create_textbox.py @@ -3,7 +3,7 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkTextbox from ...ui_utils import getLatestWidth, getLongestText, bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, _setDefaultActiveTab, switchActiveTabAndPassiveTab, switchTabsColor -def createTextbox(settings, main_window): +def createTextbox(settings, main_window, view_variable): def switchTextbox(target_textbox_attr_name): main_window.current_active_textbox.grid_remove() @@ -78,70 +78,59 @@ def createTextbox(settings, main_window): "textbox_tab_attr_name": "textbox_tab_all", "command": switchToTextboxAll, "textbox_attr_name": "textbox_all", - "text": "All", + "textvariable": view_variable.VAR_LABEL_TEXTBOX_ALL }, { "textbox_tab_attr_name": "textbox_tab_sent", "command": switchToTextboxSent, "textbox_attr_name": "textbox_sent", - "text": "Sent", + "textvariable": view_variable.VAR_LABEL_TEXTBOX_SENT }, - { "textbox_tab_attr_name": "textbox_tab_received", "command": switchToTextboxReceived, "textbox_attr_name": "textbox_received", - "text": "Received", + "textvariable": view_variable.VAR_LABEL_TEXTBOX_RECEIVED }, - { "textbox_tab_attr_name": "textbox_tab_system", "command": switchToTextboxSystem, "textbox_attr_name": "textbox_system", - "text": "System", + "textvariable": view_variable.VAR_LABEL_TEXTBOX_SYSTEM }, ] - longest_text = getLongestText(textbox_settings) - - setattr(main_window, "_mlw", CTkLabel( - main_window, - text=longest_text, - corner_radius=0, - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_TAB_FONT_SIZE, weight="normal"), - height=0, - anchor="center", - )) - label_widget = getattr(main_window, "_mlw") - label_widget.grid() - MAX_LABEL_WIDTH = getLatestWidth(label_widget) - label_widget.grid_remove() - - column=0 for textbox_setting in textbox_settings: - setattr(main_window, textbox_setting["textbox_tab_attr_name"], CTkFrame(main_window.textbox_switch_tabs_container, corner_radius=settings.uism.TEXTBOX_TAB_CORNER_RADIUS, fg_color=settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR, cursor="hand2", width=0, height=0) + setattr(main_window, textbox_setting["textbox_tab_attr_name"], + CTkFrame( + main_window.textbox_switch_tabs_container, + corner_radius=settings.uism.TEXTBOX_TAB_CORNER_RADIUS, + fg_color=settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR, + cursor="hand2", + width=0, + height=0 + ) ) target_widget = getattr(main_window, textbox_setting["textbox_tab_attr_name"]) target_widget.grid(row=0, column=column, pady=0, padx=(0,2), sticky="ew") + target_widget.columnconfigure((0,2), weight=1) setattr(main_window, "label_widget", CTkLabel( target_widget, - text=textbox_setting["text"], + textvariable=textbox_setting["textvariable"], corner_radius=0, font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_TAB_FONT_SIZE, weight="normal"), height=0, - width=MAX_LABEL_WIDTH, + width=0, anchor="center", text_color=settings.ctm.TEXTBOX_TAB_TEXT_PASSIVE_COLOR, )) label_widget = getattr(main_window, "label_widget") - label_widget.grid(row=0, column=column) - - label_widget.grid(row=0, column=column, pady=settings.uism.TEXTBOX_TAB_PADY, padx=settings.uism.TEXTBOX_TAB_PADX) + label_widget.grid(row=0, column=1, pady=settings.uism.TEXTBOX_TAB_PADY, padx=settings.uism.TEXTBOX_TAB_PADX) bindEnterAndLeaveColor([target_widget, label_widget], settings.ctm.TEXTBOX_TAB_BG_HOVERED_COLOR, settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR) bindButtonPressColor([target_widget, label_widget], settings.ctm.TEXTBOX_TAB_BG_CLICKED_COLOR, settings.ctm.TEXTBOX_TAB_BG_PASSIVE_COLOR) From b596f6df544d395fd029b0d4d73135f0da52e893 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 6 Sep 2023 07:47:06 +0900 Subject: [PATCH 112/355] =?UTF-8?q?[bugfix]=20Main=20Window:=20Config=20Bu?= =?UTF-8?q?tton=E3=81=8C=E3=80=81=E3=82=B5=E3=82=A4=E3=83=89=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=82=B3=E3=83=B3=E3=83=91=E3=82=AF=E3=83=88=E3=83=A2?= =?UTF-8?q?=E3=83=BC=E3=83=89=E6=99=82=E3=81=AB=E8=A1=A8=E7=A4=BA=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=AA=E3=81=8B=E3=81=A3=E3=81=9F=E3=81=AE=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=80=82(=E7=84=A1=E7=90=86=E3=82=84?= =?UTF-8?q?=E3=82=8A)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../createSidebarLanguagesSettings.py | 39 +-------------- .../main_window/widgets/create_sidebar.py | 50 +++++++++++++++++-- 2 files changed, 47 insertions(+), 42 deletions(-) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 7bf7be09..a76be24d 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -274,41 +274,4 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): command=selectTargetLanguageCommand, variable=view_variable.VAR_TARGET_LANGUAGE ) - main_window.sls__box_target_language.grid(row=4, column=0, padx=0, pady=(0,0),sticky="ew") - - - - - - # Config Button - main_window.sidebar_config_button_container = CTkFrame(main_window.sidebar_bg_container, corner_radius=0, fg_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, width=0, height=0) - main_window.sidebar_config_button_container.grid(row=3, column=0, sticky="ew") - - - main_window.sidebar_config_button_container.grid_columnconfigure(0, weight=1) - main_window.sidebar_config_button_wrapper = CTkFrame(main_window.sidebar_config_button_container, corner_radius=settings.uism.SIDEBAR_CONFIG_BUTTON_CORNER_RADIUS, fg_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, height=0, width=0, cursor="hand2") - main_window.sidebar_config_button_wrapper.grid(row=0, column=0, padx=settings.uism.SIDEBAR_CONFIG_BUTTON_PADX, pady=settings.uism.SIDEBAR_CONFIG_BUTTON_PADY, sticky="ew") - - - - - main_window.sidebar_config_button_wrapper.grid_columnconfigure(0, weight=1) - - settings.uism.CONFIG_BUTTON_PADX= 0 - main_window.sidebar_config_button = CTkLabel( - main_window.sidebar_config_button_wrapper, - text=None, - height=0, - image=CTkImage((settings.image_file.CONFIGURATION_ICON),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)) - ) - main_window.sidebar_config_button.grid(row=0, column=0, padx=0, pady=settings.uism.SIDEBAR_CONFIG_BUTTON_IPADY) - - - bindButtonFunctionAndColor( - target_widgets=[main_window.sidebar_config_button_wrapper, main_window.sidebar_config_button], - enter_color=settings.ctm.CONFIG_BUTTON_HOVERED_BG_COLOR, - leave_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, - clicked_color=settings.ctm.CONFIG_BUTTON_CLICKED_BG_COLOR, - buttonReleasedFunction=main_window.openConfigWindow, - ) - + main_window.sls__box_target_language.grid(row=4, column=0, padx=0, pady=(0,0),sticky="ew") \ No newline at end of file diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 12323aad..95e4eec6 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -1,4 +1,6 @@ -from customtkinter import CTkFrame +from customtkinter import CTkFrame, CTkLabel, CTkImage + +from ...ui_utils import bindButtonFunctionAndColor from ._create_sidebar import createSidebarFeatures, createSidebarLanguagesSettings @@ -6,8 +8,12 @@ def createSidebar(settings, main_window, view_variable): # Side Bar Container main_window.grid_rowconfigure(0, weight=1) - main_window.sidebar_bg_container = CTkFrame(main_window, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) - main_window.sidebar_compact_mode_bg_container = CTkFrame(main_window, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + main_window.sidebar_bg_container_wrapper = CTkFrame(main_window, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + main_window.sidebar_bg_container_wrapper.grid(row=0, column=0, sticky="nsew") + + + main_window.sidebar_bg_container = CTkFrame(main_window.sidebar_bg_container_wrapper, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + main_window.sidebar_compact_mode_bg_container = CTkFrame(main_window.sidebar_bg_container_wrapper, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) main_window.sidebar_bg_container.grid_columnconfigure(0, weight=0, minsize=settings.uism.SIDEBAR_WIDTH) @@ -24,4 +30,40 @@ def createSidebar(settings, main_window, view_variable): if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE: main_window.sidebar_bg_container.grid_remove() else: - main_window.sidebar_compact_mode_bg_container.grid_remove() \ No newline at end of file + main_window.sidebar_compact_mode_bg_container.grid_remove() + + + + # Config Button + main_window.sidebar_bg_container_wrapper.grid_rowconfigure(3, weight=1) + + main_window.sidebar_config_button_container = CTkFrame(main_window.sidebar_bg_container_wrapper, corner_radius=0, fg_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, width=0, height=0) + main_window.sidebar_config_button_container.grid(row=4, column=0, sticky="sew") + + + main_window.sidebar_config_button_container.grid_columnconfigure(0, weight=1) + main_window.sidebar_config_button_wrapper = CTkFrame(main_window.sidebar_config_button_container, corner_radius=settings.uism.SIDEBAR_CONFIG_BUTTON_CORNER_RADIUS, fg_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, height=0, width=0, cursor="hand2") + main_window.sidebar_config_button_wrapper.grid(row=0, column=0, padx=settings.uism.SIDEBAR_CONFIG_BUTTON_PADX, pady=settings.uism.SIDEBAR_CONFIG_BUTTON_PADY, sticky="ew") + + + + + main_window.sidebar_config_button_wrapper.grid_columnconfigure(0, weight=1) + + settings.uism.CONFIG_BUTTON_PADX = 0 + main_window.sidebar_config_button = CTkLabel( + main_window.sidebar_config_button_wrapper, + text=None, + height=0, + image=CTkImage((settings.image_file.CONFIGURATION_ICON),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)) + ) + main_window.sidebar_config_button.grid(row=0, column=0, padx=0, pady=settings.uism.SIDEBAR_CONFIG_BUTTON_IPADY) + + + bindButtonFunctionAndColor( + target_widgets=[main_window.sidebar_config_button_wrapper, main_window.sidebar_config_button], + enter_color=settings.ctm.CONFIG_BUTTON_HOVERED_BG_COLOR, + leave_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, + clicked_color=settings.ctm.CONFIG_BUTTON_CLICKED_BG_COLOR, + buttonReleasedFunction=main_window.openConfigWindow, + ) \ No newline at end of file From 0c43fd8965e20d1773285b4c471a983bd232570d Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 9 Sep 2023 06:17:53 +0900 Subject: [PATCH 113/355] =?UTF-8?q?[Update]=20Main=20Window:=20Language=20?= =?UTF-8?q?Settings.=E8=A8=80=E8=AA=9E=E9=81=B8=E6=8A=9E=E6=99=82=E3=81=AE?= =?UTF-8?q?=E3=83=89=E3=83=AD=E3=83=83=E3=83=97=E3=83=80=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=A1=E3=83=8B=E3=83=A5=E3=83=BC=E3=82=92=E6=97=A2=E5=AD=98?= =?UTF-8?q?=E3=81=AE=E3=82=82=E3=81=AE=E3=81=8B=E3=82=89=E3=82=AB=E3=82=B9?= =?UTF-8?q?=E3=82=BF=E3=83=A0=E3=81=97=E3=81=9F=E3=82=82=E3=81=AE=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E3=80=82=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88?= =?UTF-8?q?=E3=81=9A=E6=A9=9F=E8=83=BD=E3=81=99=E3=82=8B=E7=8A=B6=E6=85=8B?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 26 ++- vrct_gui/_CreateSelectableLanguagesWindow.py | 159 ++++++++++++++++++ .../createSidebarLanguagesSettings.py | 60 ++++--- vrct_gui/ui_managers/ColorThemeManager.py | 16 ++ vrct_gui/vrct_gui.py | 34 +++- 5 files changed, 268 insertions(+), 27 deletions(-) create mode 100644 vrct_gui/_CreateSelectableLanguagesWindow.py diff --git a/view.py b/view.py index d16fe3c4..1364e053 100644 --- a/view.py +++ b/view.py @@ -35,6 +35,12 @@ class View(): **common_args ) + self.settings.selectable_language_window = SimpleNamespace( + ctm=all_ctm.selectable_language_window, + uism=all_uism.config_window, + **common_args + ) + self.view_variable = SimpleNamespace( # Main Window # Sidebar @@ -58,16 +64,18 @@ class View(): # Sidebar Language Settings VAR_LABEL_LANGUAGE_SETTINGS=StringVar(value="Language Settings"), # JA: 言語設定 LIST_SELECTABLE_LANGUAGES=[], - CALLBACK_SELECTED_LANGUAGE_PRESET_TAB=None, + VAR_LABEL_YOUR_LANGUAGE=StringVar(value="Your Language"), # JA: あなたの言語 VAR_YOUR_LANGUAGE = StringVar(value="Japanese\n(Japan)"), + CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW=None, CALLBACK_SELECTED_YOUR_LANGUAGE=None, VAR_LABEL_BOTH_DIRECTION_DESC=StringVar(value="Translate Each Other"), # JA: 双方向に翻訳 VAR_LABEL_TARGET_LANGUAGE=StringVar(value="Target Language"), # JA: 相手の言語 VAR_TARGET_LANGUAGE = StringVar(value="English\n(United States)"), + CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW=None, CALLBACK_SELECTED_TARGET_LANGUAGE=None, @@ -248,6 +256,13 @@ class View(): self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets["callback_selected_language_preset_tab"] vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) + self.view_variable.CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_YourLanguage + self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_TargetLanguage + + + + # vrct_gui.sls__title_text_your_language.bind("", self.view_variable.CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW) + # vrct_gui.sls__title_text_target_language.bind("", self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW) entry_message_box = getattr(vrct_gui, "entry_message_box") entry_message_box.bind("", entry_message_box_commands["bind_Return"]) @@ -345,6 +360,11 @@ class View(): self.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = is_turned_on vrct_gui.recreateMainWindowSidebar() + def openSelectableLanguagesWindow_YourLanguage(self, _e): + vrct_gui.openSelectableLanguagesWindow("your_language") + def openSelectableLanguagesWindow_TargetLanguage(self, _e): + vrct_gui.openSelectableLanguagesWindow("target_language") + def updateGuiVariableByPresetTabNo(self, tab_no:str): self.view_variable.VAR_YOUR_LANGUAGE.set(config.SELECTED_TAB_YOUR_LANGUAGES[tab_no]) @@ -353,8 +373,8 @@ class View(): def updateList_selectableLanguages(self, new_selectable_language_list:list): self.view_variable.LIST_SELECTABLE_LANGUAGES = new_selectable_language_list - vrct_gui.sls__optionmenu_your_language.configure(values=new_selectable_language_list) - vrct_gui.sls__optionmenu_target_language.configure(values=new_selectable_language_list) + # vrct_gui.sls__optionmenu_your_language.configure(values=new_selectable_language_list) + # vrct_gui.sls__optionmenu_target_language.configure(values=new_selectable_language_list) def printToTextbox_enableTranslation(self): diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py new file mode 100644 index 00000000..713a4587 --- /dev/null +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -0,0 +1,159 @@ +# from .widgets import createConfigWindowTitle, createSideMenuAndSettingsBoxContainers, createSettingBoxTopBar +from functools import partial + +from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor +from utils import callFunctionIfCallable + +from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont, CTkScrollableFrame + +class _CreateSelectableLanguagesWindow(CTkToplevel): + def __init__(self, vrct_gui, settings, view_variable): + super().__init__() + self.withdraw() + + + # configure window + self.title("_CreateSelectableLanguagesWindow") + self.overrideredirect(True) + + self.attach = vrct_gui.main_bg_container + self.vrct_gui = vrct_gui + + + self.configure(fg_color="black") + self.protocol("WM_DELETE_WINDOW", vrct_gui.closeSelectableLanguagesWindow) + + self.settings = settings + self._view_variable = view_variable + + self.bind("", lambda e: self.withdraw()) + + + + self.is_created = False + + + self.selectable_language_window_type = None + + def createContainer(self, selectable_language_window_type): + print(selectable_language_window_type) + self.selectable_language_window_type = selectable_language_window_type + if self.is_created is True: + pass + else: + self._createContainer() + + + def callbackSelectableLanguages(self, value, _e): + if self.selectable_language_window_type == "your_language": + callback = self._view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE + target_variable = self._view_variable.VAR_YOUR_LANGUAGE + elif self.selectable_language_window_type == "target_language": + callback = self._view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE + target_variable = self._view_variable.VAR_TARGET_LANGUAGE + + callFunctionIfCallable(callback, value) + target_variable.set(value) + self.withdraw() + + + + + + def _createContainer(self): + + + print("create") + self.x_pos = self.attach.winfo_rootx() + self.y_pos = self.attach.winfo_rooty() + self.width_new = self.attach.winfo_width() + self.height_new = self.attach.winfo_height() + + + self.geometry('+{}+{}'.format(self.x_pos, self.y_pos)) + # self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, self.x_pos, self.y_pos)) + # self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, self.x_pos, self.y_pos)) + + + self.rowconfigure(0, minsize=50) + self.top_container = CTkFrame(self, corner_radius=0, fg_color=self.settings.ctm.MAIN_BG_COLOR, width=0, height=0) + self.top_container.grid(row=0, column=0, sticky="nsew") + + + self.top_container.rowconfigure((0,2), weight=1) + self.go_back_button_container = CTkFrame(self.top_container, corner_radius=0, fg_color=self.settings.ctm.GO_BACK_BUTTON_BG_COLOR, width=0, height=0) + self.go_back_button_container.grid(row=1, column=0) + + self.go_back_button_label_wrapper = CTkFrame(self.go_back_button_container, corner_radius=0, fg_color=self.settings.ctm.GO_BACK_BUTTON_BG_COLOR, width=0, height=0) + self.go_back_button_label_wrapper.grid(row=0, column=0) + + + self.go_back_button_label = CTkLabel( + self.go_back_button_label_wrapper, + text="Go back", + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=14, weight="normal"), + anchor="w", + text_color=self.settings.ctm.BASIC_TEXT_COLOR, + ) + self.go_back_button_label.grid(row=1, column=0, padx=10, pady=10) + + bindButtonReleaseFunction([self.go_back_button_label_wrapper, self.go_back_button_label], lambda _e: self.withdraw()) + + + + + # self.scroll_frame_container = CTkScrollableFrame(self, corner_radius=0, fg_color=self.settings.ctm.MAIN_BG_COLOR) + # self.scroll_frame_container.grid(row=1, column=0, sticky="nsew") + + + + self.container = CTkFrame(self, corner_radius=0, fg_color=self.settings.ctm.MAIN_BG_COLOR, width=0, height=0) + self.container.grid(row=1, column=0, sticky="nsew") + + + + row=0 + column=0 + for selectable_language_name in self._view_variable.LIST_SELECTABLE_LANGUAGES: + + self.wrapper = CTkFrame(self.container, corner_radius=0, fg_color=self.settings.ctm.LANGUAGE_BUTTON_BG_COLOR, width=0, height=0) + self.wrapper.grid(row=row, column=column, ipadx=6, ipady=6, sticky="nsew") + setattr(self, f"{row}_{column}", self.wrapper) + + + + self.wrapper.rowconfigure((0,2), weight=1) + selectable_language_name_for_text = selectable_language_name.replace("\n", " ") + label_widget = CTkLabel( + self.wrapper, + text=selectable_language_name_for_text, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=14, weight="normal"), + anchor="w", + text_color=self.settings.ctm.BASIC_TEXT_COLOR, + ) + # setattr(self, f"l", label_widget) + + label_widget.grid(row=1, column=0, padx=(8,0)) + + + + bindEnterAndLeaveColor([self.wrapper, label_widget], self.settings.ctm.LANGUAGE_BUTTON_BG_HOVERED_COLOR, self.settings.ctm.LANGUAGE_BUTTON_BG_COLOR) + bindButtonPressColor([self.wrapper, label_widget], self.settings.ctm.LANGUAGE_BUTTON_BG_CLICKED_COLOR, self.settings.ctm.LANGUAGE_BUTTON_BG_COLOR) + + + + callback = partial(self.callbackSelectableLanguages, selectable_language_name) + bindButtonReleaseFunction([self.wrapper, label_widget], callback) + + if row == 16: + row=0 + column+=1 + else: + row+=1 + + + self.is_created = True \ No newline at end of file diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index a76be24d..d6608324 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -28,8 +28,8 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): switchActiveAndPassivePresetsTabsColor(target_active_widget) switchActiveTabAndPassiveTab(target_active_widget, main_window.current_active_preset_tab, main_window.current_active_preset_tab.passive_function, settings.ctm.SLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR) - main_window.sls__optionmenu_your_language.set(view_variable.VAR_YOUR_LANGUAGE.get()) - main_window.sls__optionmenu_target_language.set(view_variable.VAR_TARGET_LANGUAGE.get()) + # main_window.sls__optionmenu_your_language.set(view_variable.VAR_YOUR_LANGUAGE.get()) + # main_window.sls__optionmenu_target_language.set(view_variable.VAR_TARGET_LANGUAGE.get()) main_window.current_active_preset_tab = target_active_widget @@ -76,7 +76,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - def createLanguageSettingBox(parent_widget, var_title_text, title_text_attr_name, optionmenu_attr_name, dropdown_menu_values, command, variable): + def createLanguageSettingBox(parent_widget, var_title_text, title_text_attr_name, optionmenu_attr_name, dropdown_menu_values, open_selectable_language_window_command, variable): sls__box = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) sls__box.columnconfigure((0,2), weight=1) @@ -97,19 +97,37 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - createOption_DropdownMenu_for_languageSettings( - main_window, - sls__box_wrapper, - optionmenu_attr_name, - dropdown_menu_values=dropdown_menu_values, - command=command, - width=settings.uism.SLS__BOX_DROPDOWN_MENU_WIDTH, - font_size=settings.uism.SLS__BOX_DROPDOWN_MENU_FONT_SIZE, - text_color=settings.ctm.LABELS_TEXT_COLOR, - variable=variable, - # variable=StringVar(value="Chinese, Cantonese\n(Traditional Hong Kong)"), + + sls__selected_language_box = CTkFrame(sls__box_wrapper, corner_radius=0, fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, width=200, height=30) + sls__selected_language_box.grid(row=1, column=0) + + + sls__selected_language_box.columnconfigure(0, minsize=200) + sls__selected_language_box.rowconfigure(0, minsize=30) + sls__selected_language_label = CTkFrame(sls__selected_language_box, corner_radius=0, fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR) + sls__selected_language_label.grid(row=0, column=0) + + sls__selected_language_label = CTkLabel( + sls__selected_language_label, + textvariable=variable, + height=0, + # anchor="center", + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_DROPDOWN_MENU_FONT_SIZE, weight="normal"), + text_color=settings.ctm.SLS__BOX_SECTION_TITLE_TEXT_COLOR ) - getattr(main_window, optionmenu_attr_name).grid(row=1, column=0, padx=0, pady=0) + sls__selected_language_label.grid(row=0, column=0, pady=2) + setattr(main_window, title_text_attr_name, sls__selected_language_label) + open_selectable_language_window_command + + + + + # bindEnterAndLeaveColor([self.wrapper, label_widget], self.settings.ctm.LANGUAGE_BUTTON_BG_HOVERED_COLOR, self.settings.ctm.LANGUAGE_BUTTON_BG_COLOR) + # bindButtonPressColor([self.wrapper, label_widget], self.settings.ctm.LANGUAGE_BUTTON_BG_CLICKED_COLOR, self.settings.ctm.LANGUAGE_BUTTON_BG_COLOR) + + + + bindButtonReleaseFunction([sls__selected_language_box, sls__selected_language_label], open_selectable_language_window_command) return sls__box @@ -204,12 +222,12 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): column+=1 - def selectYourLanguageCommand(value): - callFunctionIfCallable(view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE, value) + def callbackOpenSelectableYourLanguageWindow(value): + callFunctionIfCallable(view_variable.CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW, value) - def selectTargetLanguageCommand(value): - callFunctionIfCallable(view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE, value) + def callbackOpenSelectableTargetLanguageWindow(value): + callFunctionIfCallable(view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW, value) # Language Settings BOX main_window.sls__box_frame = CTkFrame(main_window.sls__container, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) @@ -223,7 +241,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): title_text_attr_name="sls__title_text_your_language", optionmenu_attr_name="sls__optionmenu_your_language", dropdown_menu_values=view_variable.LIST_SELECTABLE_LANGUAGES, - command=selectYourLanguageCommand, + open_selectable_language_window_command=callbackOpenSelectableYourLanguageWindow, variable=view_variable.VAR_YOUR_LANGUAGE ) main_window.sls__box_your_language.grid(row=2, column=0, padx=0, pady=(settings.uism.SLS__BOX_TOP_PADY,0),sticky="ew") @@ -271,7 +289,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): title_text_attr_name="sls__title_text_target_language", optionmenu_attr_name="sls__optionmenu_target_language", dropdown_menu_values=view_variable.LIST_SELECTABLE_LANGUAGES, - command=selectTargetLanguageCommand, + open_selectable_language_window_command=callbackOpenSelectableTargetLanguageWindow, variable=view_variable.VAR_TARGET_LANGUAGE ) main_window.sls__box_target_language.grid(row=4, column=0, padx=0, pady=(0,0),sticky="ew") \ No newline at end of file diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 9189035b..f68e04c0 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -5,6 +5,7 @@ class ColorThemeManager(): print(theme) self.main = SimpleNamespace() self.config_window = SimpleNamespace() + self.selectable_language_window = SimpleNamespace() self.PRIMARY_100_COLOR = "#c4eac1" self.PRIMARY_200_COLOR = "#9cdd98" @@ -151,6 +152,21 @@ class ColorThemeManager(): # self.main.HELP_AND_INFO_BUTTON_DISABLE_COLOR = self.DARK_900_COLOR + + # Selectable Language Window + self.selectable_language_window.BASIC_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + + self.selectable_language_window.MAIN_BG_COLOR = self.DARK_875_COLOR + + self.selectable_language_window.LANGUAGE_BUTTON_BG_COLOR = self.selectable_language_window.MAIN_BG_COLOR + self.selectable_language_window.GO_BACK_BUTTON_BG_COLOR = self.DARK_800_COLOR + + + self.selectable_language_window.LANGUAGE_BUTTON_BG_HOVERED_COLOR = self.DARK_825_COLOR + self.selectable_language_window.LANGUAGE_BUTTON_BG_CLICKED_COLOR = self.DARK_888_COLOR + + + # Common self.config_window.BASIC_TEXT_COLOR = self.main.BASIC_TEXT_COLOR self.config_window.LABELS_TEXT_COLOR = self.config_window.BASIC_TEXT_COLOR diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 61631efb..f472a1e3 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -4,7 +4,8 @@ from customtkinter import CTk # from window_help_and_info import ToplevelWindowInformation -# from .ui_managers import ColorThemeManager, ImageFileManager, UiScalingManager +from ._CreateSelectableLanguagesWindow import _CreateSelectableLanguagesWindow + from ._changeMainWindowWidgetsStatus import _changeMainWindowWidgetsStatus from ._changeConfigWindowWidgetsStatus import _changeConfigWindowWidgetsStatus from ._printToTextbox import _printToTextbox @@ -24,10 +25,25 @@ class VRCT_GUI(CTk): self.settings = settings self._view_variable = view_variable - createMainWindowWidgets(vrct_gui=self, settings=self.settings.main, view_variable=self._view_variable) - self.config_window = ConfigWindow(vrct_gui=self, settings=self.settings.config_window, view_variable=self._view_variable) + createMainWindowWidgets( + vrct_gui=self, + settings=self.settings.main, + view_variable=self._view_variable + ) + + self.config_window = ConfigWindow( + vrct_gui=self, + settings=self.settings.config_window, + view_variable=self._view_variable + ) # self.information_window = ToplevelWindowInformation(self) + self.selectable_languages_window = _CreateSelectableLanguagesWindow( + vrct_gui=self, + settings=self.settings.selectable_language_window, + view_variable=self._view_variable + ) + def startMainLoop(self): self.mainloop() @@ -48,6 +64,18 @@ class VRCT_GUI(CTk): self.config_window.grab_release() + def openSelectableLanguagesWindow(self, selectable_language_window_type): + self.selectable_languages_window.createContainer(selectable_language_window_type) + self.selectable_languages_window.deiconify() + self.selectable_languages_window.focus_set() + self.selectable_languages_window.attributes("-topmost", True) + + + def closeSelectableLanguagesWindow(self): + self.selectable_languages_window.withdraw() + + + def openHelpAndInfoWindow(self, e): self.information_window.deiconify() From 5222f7cc6828a8a3fe305f770a5d5b734503ca65 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 9 Sep 2023 06:24:45 +0900 Subject: [PATCH 114/355] [Chore] remove the code that is no longer in use --- view.py | 7 ------- vrct_gui/_CreateSelectableLanguagesWindow.py | 5 ----- .../_create_sidebar/createSidebarLanguagesSettings.py | 3 --- 3 files changed, 15 deletions(-) diff --git a/view.py b/view.py index 1364e053..91d5f827 100644 --- a/view.py +++ b/view.py @@ -259,11 +259,6 @@ class View(): self.view_variable.CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_YourLanguage self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_TargetLanguage - - - # vrct_gui.sls__title_text_your_language.bind("", self.view_variable.CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW) - # vrct_gui.sls__title_text_target_language.bind("", self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW) - entry_message_box = getattr(vrct_gui, "entry_message_box") entry_message_box.bind("", entry_message_box_commands["bind_Return"]) entry_message_box.bind("", entry_message_box_commands["bind_Any_KeyPress"]) @@ -373,8 +368,6 @@ class View(): def updateList_selectableLanguages(self, new_selectable_language_list:list): self.view_variable.LIST_SELECTABLE_LANGUAGES = new_selectable_language_list - # vrct_gui.sls__optionmenu_your_language.configure(values=new_selectable_language_list) - # vrct_gui.sls__optionmenu_target_language.configure(values=new_selectable_language_list) def printToTextbox_enableTranslation(self): diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index 713a4587..69776125 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -1,4 +1,3 @@ -# from .widgets import createConfigWindowTitle, createSideMenuAndSettingsBoxContainers, createSettingBoxTopBar from functools import partial from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor @@ -36,7 +35,6 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.selectable_language_window_type = None def createContainer(self, selectable_language_window_type): - print(selectable_language_window_type) self.selectable_language_window_type = selectable_language_window_type if self.is_created is True: pass @@ -61,9 +59,6 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): def _createContainer(self): - - - print("create") self.x_pos = self.attach.winfo_rootx() self.y_pos = self.attach.winfo_rooty() self.width_new = self.attach.winfo_width() diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index d6608324..4caaabd2 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -27,9 +27,6 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): def switchPresetTabFunction(target_active_widget): switchActiveAndPassivePresetsTabsColor(target_active_widget) switchActiveTabAndPassiveTab(target_active_widget, main_window.current_active_preset_tab, main_window.current_active_preset_tab.passive_function, settings.ctm.SLS__PRESETS_TAB_BG_HOVERED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_CLICKED_COLOR, settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR) - - # main_window.sls__optionmenu_your_language.set(view_variable.VAR_YOUR_LANGUAGE.get()) - # main_window.sls__optionmenu_target_language.set(view_variable.VAR_TARGET_LANGUAGE.get()) main_window.current_active_preset_tab = target_active_widget From 9f9cd1e64c7da8ae3a3fa2742168eda9e9111eed Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 9 Sep 2023 11:35:24 +0900 Subject: [PATCH 115/355] =?UTF-8?q?[Add]=20Main=20Window:=20Language=20Set?= =?UTF-8?q?tings.=20=E8=A8=80=E8=AA=9E=E9=81=B8=E6=8A=9E=E3=83=9C=E3=82=BF?= =?UTF-8?q?=E3=83=B3=E3=82=92=E6=8A=BC=E3=81=97=E3=81=A6=E8=A8=80=E8=AA=9E?= =?UTF-8?q?=E9=81=B8=E6=8A=9E=E3=82=A6=E3=82=A3=E3=83=B3=E3=83=89=E3=82=A6?= =?UTF-8?q?=E9=96=8B=E9=96=89=E6=99=82=E3=81=AB=E3=80=81=E7=9F=A2=E5=8D=B0?= =?UTF-8?q?=E3=81=AE=E5=90=91=E3=81=8D=E3=81=8C=E5=8F=8D=E8=BB=A2=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=80=82=20[Bugfix]=20?= =?UTF-8?q?=E6=A0=B9=E6=9C=AC=E7=9A=84=E3=81=AA=E8=A7=A3=E6=B1=BA=E3=81=A7?= =?UTF-8?q?=E3=81=AF=E3=81=AA=E3=81=84=E3=81=91=E3=82=8C=E3=81=A9=E3=80=81?= =?UTF-8?q?=E8=A8=80=E8=AA=9E=E9=81=B8=E6=8A=9E=E3=82=A6=E3=82=A3=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=82=A6=E3=81=AE=E9=96=8B=E3=81=8F=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E3=81=8C=E5=9B=BA=E5=AE=9A=E3=81=95=E3=82=8C=E3=81=A6=E3=81=97?= =?UTF-8?q?=E3=81=BE=E3=81=86=E3=81=AE=E3=81=A7=E3=81=9D=E3=81=AE=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=EF=BC=88=E3=82=B5=E3=82=A4=E3=83=89=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=81=AE=E5=B9=85=E3=81=AF=E5=8F=AF=E5=A4=89=E3=81=AA=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E3=80=81=E3=81=9D=E3=82=8C=E3=81=AB=E5=90=88=E3=82=8F?= =?UTF-8?q?=E3=81=9B=E3=81=A6=E3=82=A6=E3=82=A3=E3=83=B3=E3=83=89=E3=82=A6?= =?UTF-8?q?=E7=94=9F=E6=88=90=E6=99=82=E3=81=AB=E6=AF=8E=E5=9B=9E=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E3=82=92=E6=8C=87=E5=AE=9A=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateSelectableLanguagesWindow.py | 23 ++++++----- .../createSidebarLanguagesSettings.py | 40 +++++++++++++------ vrct_gui/vrct_gui.py | 10 ++++- 3 files changed, 49 insertions(+), 24 deletions(-) diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index 69776125..b2701249 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -25,7 +25,7 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.settings = settings self._view_variable = view_variable - self.bind("", lambda e: self.withdraw()) + self.bind("", lambda e: vrct_gui.closeSelectableLanguagesWindow()) @@ -36,6 +36,16 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): def createContainer(self, selectable_language_window_type): self.selectable_language_window_type = selectable_language_window_type + + self.x_pos = self.attach.winfo_rootx() + self.y_pos = self.attach.winfo_rooty() + self.width_new = self.attach.winfo_width() + self.height_new = self.attach.winfo_height() + + + self.geometry('+{}+{}'.format(self.x_pos, self.y_pos)) + + if self.is_created is True: pass else: @@ -52,20 +62,13 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): callFunctionIfCallable(callback, value) target_variable.set(value) - self.withdraw() + self.vrct_gui.closeSelectableLanguagesWindow() def _createContainer(self): - self.x_pos = self.attach.winfo_rootx() - self.y_pos = self.attach.winfo_rooty() - self.width_new = self.attach.winfo_width() - self.height_new = self.attach.winfo_height() - - - self.geometry('+{}+{}'.format(self.x_pos, self.y_pos)) # self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, self.x_pos, self.y_pos)) # self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, self.x_pos, self.y_pos)) @@ -94,7 +97,7 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): ) self.go_back_button_label.grid(row=1, column=0, padx=10, pady=10) - bindButtonReleaseFunction([self.go_back_button_label_wrapper, self.go_back_button_label], lambda _e: self.withdraw()) + bindButtonReleaseFunction([self.go_back_button_label_wrapper, self.go_back_button_label], lambda _e: self.vrct_gui.closeSelectableLanguagesWindow()) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 4caaabd2..c7099530 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -73,13 +73,14 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - def createLanguageSettingBox(parent_widget, var_title_text, title_text_attr_name, optionmenu_attr_name, dropdown_menu_values, open_selectable_language_window_command, variable): + def createLanguageSettingBox(parent_widget, var_title_text, title_text_attr_name, arrow_img_attr_name, dropdown_menu_values, open_selectable_language_window_command, variable): sls__box = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) sls__box.columnconfigure((0,2), weight=1) sls__box_wrapper = CTkFrame(sls__box, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) - sls__box_wrapper.grid(row=2, column=1, padx=0, pady=settings.uism.SLS__BOX_IPADY) + sls__box_wrapper.grid(row=2, column=1, padx=10, pady=settings.uism.SLS__BOX_IPADY) + sls__label = CTkLabel( sls__box_wrapper, @@ -95,7 +96,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - sls__selected_language_box = CTkFrame(sls__box_wrapper, corner_radius=0, fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, width=200, height=30) + sls__selected_language_box = CTkFrame(sls__box_wrapper, corner_radius=0, fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR) sls__selected_language_box.grid(row=1, column=0) @@ -110,11 +111,24 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): height=0, # anchor="center", font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_DROPDOWN_MENU_FONT_SIZE, weight="normal"), - text_color=settings.ctm.SLS__BOX_SECTION_TITLE_TEXT_COLOR + text_color=settings.ctm.LABELS_TEXT_COLOR ) sls__selected_language_label.grid(row=0, column=0, pady=2) setattr(main_window, title_text_attr_name, sls__selected_language_label) - open_selectable_language_window_command + + + sls__selected_language_arrow_img = CTkLabel( + sls__selected_language_box, + text=None, + corner_radius=0, + height=0, + image=CTkImage((settings.image_file.ARROW_LEFT).rotate(180),size=(20,20)) + ) + setattr(main_window, arrow_img_attr_name, sls__selected_language_arrow_img) + + + + sls__selected_language_arrow_img.grid(row=0, column=1, padx=0, pady=0) @@ -124,7 +138,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - bindButtonReleaseFunction([sls__selected_language_box, sls__selected_language_label], open_selectable_language_window_command) + bindButtonReleaseFunction([sls__selected_language_box, sls__selected_language_label, sls__selected_language_arrow_img], open_selectable_language_window_command) return sls__box @@ -236,17 +250,17 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): parent_widget=main_window.sls__box_frame, var_title_text=view_variable.VAR_LABEL_YOUR_LANGUAGE, title_text_attr_name="sls__title_text_your_language", - optionmenu_attr_name="sls__optionmenu_your_language", + arrow_img_attr_name="sls__arrow_img_your_language", dropdown_menu_values=view_variable.LIST_SELECTABLE_LANGUAGES, open_selectable_language_window_command=callbackOpenSelectableYourLanguageWindow, variable=view_variable.VAR_YOUR_LANGUAGE ) - main_window.sls__box_your_language.grid(row=2, column=0, padx=0, pady=(settings.uism.SLS__BOX_TOP_PADY,0),sticky="ew") + main_window.sls__box_your_language.grid(row=2, column=0, pady=(settings.uism.SLS__BOX_TOP_PADY,0),sticky="ew") # Both direction arrow icon main_window.sls__arrow_direction_box = CTkFrame(main_window.sls__box_frame, corner_radius=0, fg_color=settings.ctm.SLS__BG_COLOR, width=0, height=0) - main_window.sls__arrow_direction_box.grid(row=3, column=0, padx=0, pady=settings.uism.SLS__BOX_ARROWS_PADY,sticky="ew") + main_window.sls__arrow_direction_box.grid(row=3, column=0, pady=settings.uism.SLS__BOX_ARROWS_PADY,sticky="ew") main_window.sls__arrow_direction_box.grid_columnconfigure((0,4), weight=1) @@ -266,7 +280,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_ARROWS_DESC_FONT_SIZE, weight="normal"), text_color=settings.ctm.SLS__BOX_ARROWS_TEXT_COLOR, ) - main_window.sls__both_direction_desc.grid(row=0, column=2, padx=settings.uism.SLS__BOX_ARROWS_DESC_PADX, pady=0) + main_window.sls__both_direction_desc.grid(row=0, column=2, padx=settings.uism.SLS__BOX_ARROWS_DESC_PADX) main_window.sls__both_direction_label_down = CTkLabel( main_window.sls__arrow_direction_box, @@ -275,7 +289,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): image=CTkImage((settings.image_file.NARROW_ARROW_DOWN).rotate(0),size=settings.uism.SLS__BOX_ARROWS_IMAGE_SIZE) ) - main_window.sls__both_direction_label_down.grid(row=0, column=3, pady=0) + main_window.sls__both_direction_label_down.grid(row=0, column=3) @@ -284,9 +298,9 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): parent_widget=main_window.sls__box_frame, var_title_text=view_variable.VAR_LABEL_TARGET_LANGUAGE, title_text_attr_name="sls__title_text_target_language", - optionmenu_attr_name="sls__optionmenu_target_language", + arrow_img_attr_name="sls__arrow_img_target_language", dropdown_menu_values=view_variable.LIST_SELECTABLE_LANGUAGES, open_selectable_language_window_command=callbackOpenSelectableTargetLanguageWindow, variable=view_variable.VAR_TARGET_LANGUAGE ) - main_window.sls__box_target_language.grid(row=4, column=0, padx=0, pady=(0,0),sticky="ew") \ No newline at end of file + main_window.sls__box_target_language.grid(row=4, column=0, sticky="ew") \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index f472a1e3..c4720fb6 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -1,6 +1,6 @@ from types import SimpleNamespace -from customtkinter import CTk +from customtkinter import CTk, CTkImage # from window_help_and_info import ToplevelWindowInformation @@ -65,6 +65,12 @@ class VRCT_GUI(CTk): def openSelectableLanguagesWindow(self, selectable_language_window_type): + if selectable_language_window_type == "your_language": + self.sls__arrow_img_your_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT),size=(20,20))) + elif selectable_language_window_type == "target_language": + self.sls__arrow_img_target_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT),size=(20,20))) + + self.sls__arrow_img_target_language self.selectable_languages_window.createContainer(selectable_language_window_type) self.selectable_languages_window.deiconify() self.selectable_languages_window.focus_set() @@ -72,6 +78,8 @@ class VRCT_GUI(CTk): def closeSelectableLanguagesWindow(self): + self.sls__arrow_img_your_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT).rotate(180),size=(20,20))) + self.sls__arrow_img_target_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT).rotate(180),size=(20,20))) self.selectable_languages_window.withdraw() From c841ceda182baf70e8f9a38611951be4d898f4fa Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 10 Sep 2023 07:08:07 +0900 Subject: [PATCH 116/355] =?UTF-8?q?[Update]=20Main=20Window:=20=E8=A8=80?= =?UTF-8?q?=E8=AA=9E=E9=81=B8=E6=8A=9E=E3=82=A6=E3=82=A3=E3=83=B3=E3=83=89?= =?UTF-8?q?=E3=82=A6=E3=81=AB=E3=82=B9=E3=82=AF=E3=83=AD=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=83=95=E3=83=AC=E3=83=BC=E3=83=A0=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=80=82=E3=82=B5=E3=82=A4=E3=82=BA=E3=82=82=E7=84=A1=E7=90=86?= =?UTF-8?q?=E3=82=84=E3=82=8A=E3=81=A7=E3=81=AF=E3=81=82=E3=82=8B=E3=81=91?= =?UTF-8?q?=E3=81=A9=E3=83=A1=E3=82=A4=E3=83=B3=E7=94=BB=E9=9D=A2=E3=81=AB?= =?UTF-8?q?=E5=90=88=E3=82=8F=E3=81=9B=E3=81=A6=E8=AA=BF=E6=95=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateSelectableLanguagesWindow.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index b2701249..ee5a0ea5 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -39,8 +39,8 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.x_pos = self.attach.winfo_rootx() self.y_pos = self.attach.winfo_rooty() - self.width_new = self.attach.winfo_width() - self.height_new = self.attach.winfo_height() + self.width_new = self.attach.winfo_width() - 16 + self.height_new = self.attach.winfo_height() - 50 self.geometry('+{}+{}'.format(self.x_pos, self.y_pos)) @@ -102,16 +102,18 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): - # self.scroll_frame_container = CTkScrollableFrame(self, corner_radius=0, fg_color=self.settings.ctm.MAIN_BG_COLOR) - # self.scroll_frame_container.grid(row=1, column=0, sticky="nsew") + self.scroll_frame_container = CTkScrollableFrame(self, corner_radius=0, fg_color=self.settings.ctm.MAIN_BG_COLOR, width=self.width_new, height=self.height_new) + self.scroll_frame_container.grid(row=1, column=0, sticky="nsew") - self.container = CTkFrame(self, corner_radius=0, fg_color=self.settings.ctm.MAIN_BG_COLOR, width=0, height=0) - self.container.grid(row=1, column=0, sticky="nsew") + self.container = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.settings.ctm.MAIN_BG_COLOR, width=0, height=0) + self.container.grid(row=0, column=0, sticky="nsew") + max_row = int(len(self._view_variable.LIST_SELECTABLE_LANGUAGES)/3) + 1 + max_row+=1 row=0 column=0 for selectable_language_name in self._view_variable.LIST_SELECTABLE_LANGUAGES: @@ -147,7 +149,7 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): callback = partial(self.callbackSelectableLanguages, selectable_language_name) bindButtonReleaseFunction([self.wrapper, label_widget], callback) - if row == 16: + if row == max_row: row=0 column+=1 else: From 7404ea80ea7605d115021cc2a1273a08c8910052 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 10 Sep 2023 07:34:39 +0900 Subject: [PATCH 117/355] =?UTF-8?q?[bugfix]=20Main=20Window:=20Language=20?= =?UTF-8?q?Settings=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AE=E3=82=B9=E3=83=86?= =?UTF-8?q?=E3=83=BC=E3=82=BF=E3=82=B9=E5=A4=89=E6=9B=B4=E6=99=82=E3=81=AB?= =?UTF-8?q?=E5=87=BA=E3=82=8B=E3=82=A8=E3=83=A9=E3=83=BC=E3=82=92=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3(=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=A2?= =?UTF-8?q?=E3=82=A6=E3=83=88=E3=81=AA=E3=81=86=E3=81=88=E3=81=AB=E8=A6=8B?= =?UTF-8?q?=E3=81=9F=E7=9B=AE=E3=81=A0=E3=81=91=E3=81=AEdisabled=E3=81=AA?= =?UTF-8?q?=E3=81=AE=E3=81=A7=E6=8A=BC=E3=81=9B=E3=82=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_changeMainWindowWidgetsStatus.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index 5e02a67c..c2350182 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -87,8 +87,8 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta vrct_gui.sls__title_text_target_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE) - vrct_gui.sls__optionmenu_your_language.configure(state="disabled") - vrct_gui.sls__optionmenu_target_language.configure(state="disabled") + # vrct_gui.sls__optionmenu_your_language.configure(state="disabled") + # vrct_gui.sls__optionmenu_target_language.configure(state="disabled") elif status == "normal": vrct_gui.sls__container_title.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) @@ -97,8 +97,8 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) - vrct_gui.sls__optionmenu_your_language.configure(state="normal") - vrct_gui.sls__optionmenu_target_language.configure(state="normal") + # vrct_gui.sls__optionmenu_your_language.configure(state="normal") + # vrct_gui.sls__optionmenu_target_language.configure(state="normal") case "config_button": From 7baed529995fde899c1b0b025bbe2233ca8a7b98 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 11 Sep 2023 04:22:37 +0900 Subject: [PATCH 118/355] =?UTF-8?q?[Update]=20Main=20Window:=20=E5=85=A8?= =?UTF-8?q?=E4=BD=93=E7=9A=84=E3=81=AB=E8=89=B2=E3=82=92=E6=98=8E=E3=82=8B?= =?UTF-8?q?=E3=81=8F=E3=80=82Textbox=E3=81=A7=E3=81=9F=E3=81=8F=E3=81=95?= =?UTF-8?q?=E3=82=93=E3=81=AE=E6=96=87=E5=AD=97=E3=82=92=E5=B8=B8=E3=81=AB?= =?UTF-8?q?=E8=A6=8B=E3=82=8B=E4=BA=8B=E3=82=92=E8=80=83=E3=81=88=E3=82=8B?= =?UTF-8?q?=E3=81=A8=E3=83=8F=E3=83=AC=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3?= =?UTF-8?q?=E3=82=92=E8=B5=B7=E3=81=93=E3=81=97=E3=81=9D=E3=81=86=E3=81=AA?= =?UTF-8?q?=E3=81=AE=E3=81=A7=E3=80=81=E3=82=B3=E3=83=B3=E3=83=88=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E3=83=88=E6=AF=94=E3=82=92=E4=B8=8B=E3=81=92=E3=81=9F?= =?UTF-8?q?=E3=80=82=E3=81=9D=E3=82=8C=E3=81=AB=E4=BC=B4=E3=81=84=E3=82=B5?= =?UTF-8?q?=E3=82=A4=E3=83=89=E3=83=90=E3=83=BC=E3=81=AE=E8=89=B2=E3=81=AA?= =?UTF-8?q?=E3=81=A9=E3=82=82=E5=A4=89=E3=81=88=E3=81=AA=E3=81=84=E3=81=A8?= =?UTF-8?q?=E3=81=8A=E3=81=8B=E3=81=97=E3=81=84=E3=81=AE=E3=81=A7=E5=85=A8?= =?UTF-8?q?=E4=BD=93=E7=9A=84=E3=81=AB=E8=89=B2=E3=81=8C=E6=98=8E=E3=82=8B?= =?UTF-8?q?=E3=81=8F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../createSidebarLanguagesSettings.py | 2 +- vrct_gui/ui_managers/ColorThemeManager.py | 34 ++++++++++--------- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index c7099530..493092b6 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -20,7 +20,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): tab_buttons=quick_setting_tabs, active_bg_color=settings.ctm.SLS__PRESETS_TAB_BG_ACTIVE_COLOR, active_text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR, - passive_bg_color=settings.ctm.SIDEBAR_BG_COLOR, + passive_bg_color=settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR, passive_text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE ) diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index f68e04c0..75a6b0bc 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -26,7 +26,9 @@ class ColorThemeManager(): self.DARK_500_COLOR = "#a9aaae" self.DARK_600_COLOR = "#7f8084" self.DARK_700_COLOR = "#6a6c6f" + self.DARK_725_COLOR = "#636467" self.DARK_750_COLOR = "#5b5c5f" + self.DARK_775_COLOR = "#535457" self.DARK_800_COLOR = "#4b4c4f" self.DARK_825_COLOR = "#434447" self.DARK_850_COLOR = "#3a3b3e" @@ -74,14 +76,14 @@ class ColorThemeManager(): self.main.LABELS_TEXT_COLOR = self.main.BASIC_TEXT_COLOR # Main - self.main.MAIN_BG_COLOR = self.DARK_900_COLOR + self.main.MAIN_BG_COLOR = self.DARK_888_COLOR - self.main.TEXTBOX_BG_COLOR = self.DARK_950_COLOR + self.main.TEXTBOX_BG_COLOR = self.DARK_900_COLOR self.main.TEXTBOX_TEXT_COLOR = self.main.BASIC_TEXT_COLOR self.main.TEXTBOX_TAB_BG_PASSIVE_COLOR = self.DARK_850_COLOR self.main.TEXTBOX_TAB_BG_ACTIVE_COLOR = self.main.TEXTBOX_BG_COLOR self.main.TEXTBOX_TAB_BG_HOVERED_COLOR = self.DARK_800_COLOR - self.main.TEXTBOX_TAB_BG_CLICKED_COLOR = self.DARK_850_COLOR + self.main.TEXTBOX_TAB_BG_CLICKED_COLOR = self.DARK_925_COLOR self.main.TEXTBOX_TAB_TEXT_ACTIVE_COLOR = self.main.BASIC_TEXT_COLOR self.main.TEXTBOX_TAB_TEXT_PASSIVE_COLOR = self.DARK_500_COLOR @@ -95,20 +97,20 @@ class ColorThemeManager(): # Sidebar - self.main.SIDEBAR_BG_COLOR = self.DARK_875_COLOR + self.main.SIDEBAR_BG_COLOR = self.DARK_850_COLOR # Sidebar Features - self.main.SF__BG_COLOR = self.DARK_850_COLOR + self.main.SF__BG_COLOR = self.DARK_825_COLOR self.main.SF__HOVERED_BG_COLOR = self.DARK_800_COLOR - self.main.SF__CLICKED_BG_COLOR = self.DARK_900_COLOR + self.main.SF__CLICKED_BG_COLOR = self.DARK_875_COLOR self.main.SF__TEXT_DISABLED_COLOR = self.DARK_500_COLOR - self.main.SF__SWITCH_BOX_BG_COLOR = self.DARK_800_COLOR - self.main.SF__SWITCH_BOX_HOVERED_BG_COLOR = self.DARK_750_COLOR - self.main.SF__SWITCH_BOX_CLICKED_BG_COLOR = self.DARK_850_COLOR + self.main.SF__SWITCH_BOX_BG_COLOR = self.DARK_775_COLOR + self.main.SF__SWITCH_BOX_HOVERED_BG_COLOR = self.DARK_725_COLOR + self.main.SF__SWITCH_BOX_CLICKED_BG_COLOR = self.DARK_825_COLOR self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR = self.PRIMARY_650_COLOR self.main.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR = self.PRIMARY_500_COLOR - self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR = self.PRIMARY_700_COLOR + self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR = self.PRIMARY_800_COLOR self.main.SF__SWITCH_BOX_DISABLE_BG_COLOR = self.PRIMARY_900_COLOR self.main.SF__SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR @@ -120,25 +122,25 @@ class ColorThemeManager(): # Sidebar Languages Settings self.main.SLS__TITLE_TEXT_COLOR = self.DARK_400_COLOR - self.main.SLS__BG_COLOR = self.DARK_825_COLOR + self.main.SLS__BG_COLOR = self.DARK_800_COLOR - self.main.SLS__PRESETS_TAB_BG_HOVERED_COLOR = self.DARK_850_COLOR - self.main.SLS__PRESETS_TAB_BG_CLICKED_COLOR = self.DARK_888_COLOR + self.main.SLS__PRESETS_TAB_BG_HOVERED_COLOR = self.DARK_825_COLOR + self.main.SLS__PRESETS_TAB_BG_CLICKED_COLOR = self.DARK_875_COLOR self.main.SLS__PRESETS_TAB_BG_PASSIVE_COLOR = self.main.SIDEBAR_BG_COLOR self.main.SLS__PRESETS_TAB_BG_ACTIVE_COLOR = self.main.SLS__BG_COLOR self.main.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE = self.DARK_600_COLOR self.main.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR = self.main.BASIC_TEXT_COLOR - self.main.SLS__BOX_BG_COLOR = self.DARK_850_COLOR + self.main.SLS__BOX_BG_COLOR = self.DARK_825_COLOR self.main.SLS__BOX_SECTION_TITLE_TEXT_COLOR = self.DARK_400_COLOR self.main.SLS__BOX_ARROWS_TEXT_COLOR = self.DARK_500_COLOR - self.main.SLS__DROPDOWN_MENU_BG_COLOR = self.DARK_900_COLOR + self.main.SLS__DROPDOWN_MENU_BG_COLOR = self.DARK_888_COLOR self.main.CONFIG_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR self.main.CONFIG_BUTTON_HOVERED_BG_COLOR = self.DARK_800_COLOR - self.main.CONFIG_BUTTON_CLICKED_BG_COLOR = self.DARK_900_COLOR + self.main.CONFIG_BUTTON_CLICKED_BG_COLOR = self.DARK_875_COLOR # self.main.CONFIG_BUTTON_DISABLE_COLOR = self.DARK_900_COLOR self.main.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR From 7adc08ed93e5a38eaad7b3f872ca1dd8835c7a1c Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 11 Sep 2023 04:55:18 +0900 Subject: [PATCH 119/355] =?UTF-8?q?[Update]=20VRCT=E3=83=86=E3=83=BC?= =?UTF-8?q?=E3=83=9E=E3=82=AB=E3=83=A9=E3=83=BC=E5=A4=89=E6=9B=B4=E3=81=AB?= =?UTF-8?q?=E3=82=88=E3=82=8APRIMARY=20COLOR=E3=81=AE=E5=A4=89=E6=9B=B4?= =?UTF-8?q?=E3=80=81=E3=81=9D=E3=81=AE=E8=AA=BF=E6=95=B4=E3=81=A8=E9=81=A9?= =?UTF-8?q?=E7=94=A8=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/ui_managers/ColorThemeManager.py | 57 ++++++++++++++--------- 1 file changed, 36 insertions(+), 21 deletions(-) diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 75a6b0bc..85600f13 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -7,16 +7,31 @@ class ColorThemeManager(): self.config_window = SimpleNamespace() self.selectable_language_window = SimpleNamespace() - self.PRIMARY_100_COLOR = "#c4eac1" - self.PRIMARY_200_COLOR = "#9cdd98" - self.PRIMARY_300_COLOR = "#70d16c" - self.PRIMARY_400_COLOR = "#49c649" - self.PRIMARY_500_COLOR = "#0abb1d" - self.PRIMARY_600_COLOR = "#00ac11" - self.PRIMARY_650_COLOR = "#00A309" - self.PRIMARY_700_COLOR = "#009900" - self.PRIMARY_800_COLOR = "#008800" - self.PRIMARY_900_COLOR = "#006900" + # old one. But leave it here for now. + # self.PRIMARY_100_COLOR = "#c4eac1" + # self.PRIMARY_200_COLOR = "#9cdd98" + # self.PRIMARY_300_COLOR = "#70d16c" + # self.PRIMARY_400_COLOR = "#49c649" + # self.PRIMARY_500_COLOR = "#0abb1d" + # self.PRIMARY_600_COLOR = "#00ac11" + # self.PRIMARY_650_COLOR = "#00A309" + # self.PRIMARY_700_COLOR = "#009900" + # self.PRIMARY_800_COLOR = "#008800" + # self.PRIMARY_900_COLOR = "#006900" + + + # new one. + self.PRIMARY_100_COLOR = "#b7ded8" + self.PRIMARY_200_COLOR = "#8acac0" + self.PRIMARY_300_COLOR = "#61b4a7" + self.PRIMARY_400_COLOR = "#48a495" + self.PRIMARY_500_COLOR = "#3b9483" + self.PRIMARY_600_COLOR = "#368777" + self.PRIMARY_650_COLOR = "#347f6f" + self.PRIMARY_700_COLOR = "#317767" + self.PRIMARY_750_COLOR = "#2F6F60" + self.PRIMARY_800_COLOR = "#2c6759" + self.PRIMARY_900_COLOR = "#214b3f" self.DARK_100_COLOR = "#f5f7fb" @@ -108,10 +123,10 @@ class ColorThemeManager(): self.main.SF__SWITCH_BOX_BG_COLOR = self.DARK_775_COLOR self.main.SF__SWITCH_BOX_HOVERED_BG_COLOR = self.DARK_725_COLOR self.main.SF__SWITCH_BOX_CLICKED_BG_COLOR = self.DARK_825_COLOR - self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR = self.PRIMARY_650_COLOR - self.main.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR = self.PRIMARY_500_COLOR - self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR = self.PRIMARY_800_COLOR - self.main.SF__SWITCH_BOX_DISABLE_BG_COLOR = self.PRIMARY_900_COLOR + self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR = self.PRIMARY_500_COLOR + self.main.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR = self.PRIMARY_400_COLOR + self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR = self.PRIMARY_700_COLOR + self.main.SF__SWITCH_BOX_DISABLE_BG_COLOR = self.PRIMARY_800_COLOR self.main.SF__SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR self.main.SF__SELECTED_MARK_ACTIVE_HOVERED_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR @@ -201,8 +216,8 @@ class ColorThemeManager(): self.config_window.SB__CHECKBOX_CHECKED_COLOR = self.PRIMARY_700_COLOR self.config_window.SB__CHECKBOX_CHECKMARK_COLOR = self.config_window.BASIC_TEXT_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_COLOR = self.PRIMARY_700_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_HOVERED_COLOR = self.PRIMARY_500_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_COLOR = self.PRIMARY_600_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_HOVERED_COLOR = self.PRIMARY_400_COLOR self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR = self.DARK_800_COLOR self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR = self.DARK_800_COLOR @@ -210,9 +225,9 @@ class ColorThemeManager(): self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_CLICKED_COLOR = self.DARK_900_COLOR self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR = self.DARK_850_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_COLOR = self.PRIMARY_700_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_HOVERED_COLOR = self.PRIMARY_600_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_CLICKED_COLOR = self.PRIMARY_900_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_COLOR = self.PRIMARY_600_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_HOVERED_COLOR = self.PRIMARY_500_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_CLICKED_COLOR = self.PRIMARY_800_COLOR # self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_DISABLED_COLOR = self.PRIMARY_900_COLOR @@ -222,8 +237,8 @@ class ColorThemeManager(): self.config_window.SIDE_MENU_LABELS_BG_COLOR = self.config_window.SIDE_MENU_BG_COLOR self.config_window.SIDE_MENU_LABELS_BG_FOR_FAKE_BORDER_COLOR = self.config_window.SIDE_MENU_BG_COLOR self.config_window.SIDE_MENU_LABELS_HOVERED_BG_COLOR = self.DARK_850_COLOR - self.config_window.SIDE_MENU_LABELS_CLICKED_BG_COLOR = self.PRIMARY_900_COLOR - self.config_window.SIDE_MENU_LABELS_SELECTED_TEXT_COLOR = self.PRIMARY_300_COLOR + self.config_window.SIDE_MENU_LABELS_CLICKED_BG_COLOR = self.PRIMARY_750_COLOR + self.config_window.SIDE_MENU_LABELS_SELECTED_TEXT_COLOR = self.PRIMARY_200_COLOR self.config_window.SIDE_MENU_SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR From 5a257c93fd1b2838816fff60ff22d9593d4cdc5b Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 12 Sep 2023 07:14:36 +0900 Subject: [PATCH 120/355] =?UTF-8?q?[Add]=20Config=20Window:=20Auto=20Expor?= =?UTF-8?q?t=20Message=20Logs(LOG=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E5=87=BA=E5=8A=9B=E6=A9=9F=E8=83=BD)=E3=82=92=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E9=A0=85=E7=9B=AE=E3=81=AB=E8=BF=BD=E5=8A=A0=E3=80=82?= =?UTF-8?q?=E3=82=AB=E3=83=86=E3=82=B4=E3=83=AA=E3=83=BC=E3=81=AFOthers?= =?UTF-8?q?=E3=81=A8=E3=81=97=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 7 +++++++ .../setting_box_others/createSettingBox_Others.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/view.py b/view.py index 91d5f827..f004f9b8 100644 --- a/view.py +++ b/view.py @@ -219,6 +219,12 @@ class View(): CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY=None, VAR_ENABLE_NOTICE_XSOVERLAY=BooleanVar(value=config.ENABLE_NOTICE_XSOVERLAY), + VAR_LABEL_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=StringVar(value="Auto Export Message Logs"), + VAR_DESC_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=StringVar(value="Automatically export the conversation messages as a text file."), + CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=None, + VAR_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=BooleanVar(value=config.ENABLE_LOGGER), + + VAR_LABEL_MESSAGE_FORMAT=StringVar(value="Message Format"), VAR_DESC_MESSAGE_FORMAT=StringVar(value="You can change the decoration of the message you want to send. (Default: \"[message]([translation])\" )"), CALLBACK_SET_MESSAGE_FORMAT=None, @@ -314,6 +320,7 @@ class View(): # Others Tab self.view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window["callback_set_enable_auto_clear_chatbox"] self.view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window["callback_set_enable_notice_xsoverlay"] + self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS = config_window.get("callback_set_enable_auto_export_message_logs", None) self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window["callback_set_message_format"] # Advanced Settings Tab diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index dd730e92..311fbbbd 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -15,6 +15,9 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v def checkbox_notice_xsoverlay_callback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, checkbox_box_widget.get()) + def checkbox_auto_export_message_logs_callback(checkbox_box_widget): + callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, checkbox_box_widget.get()) + def entry_message_format_callback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, value) @@ -44,6 +47,18 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v row+=1 + config_window.sb__auto_export_message_logs = createSettingBoxCheckbox( + parent_widget=setting_box_wrapper, + for_var_label_text=view_variable.VAR_LABEL_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, + for_var_desc_text=view_variable.VAR_DESC_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, + checkbox_attr_name="sb__checkbox_auto_export_message_logs", + command=lambda: checkbox_auto_export_message_logs_callback(config_window.sb__checkbox_auto_export_message_logs), + variable=view_variable.VAR_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, + ) + config_window.sb__auto_export_message_logs.grid(row=row) + row+=1 + + config_window.sb__message_format = createSettingBoxEntry( parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_MESSAGE_FORMAT, From ac37851c7d820657faa9888c427966267e0a7abb Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 12 Sep 2023 08:49:27 +0900 Subject: [PATCH 121/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20model=20func=20O?= =?UTF-8?q?SC=20Change=20Input=20Voice?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/osc/osc_tools.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/models/osc/osc_tools.py b/models/osc/osc_tools.py index b44e17cb..c2f765ec 100644 --- a/models/osc/osc_tools.py +++ b/models/osc/osc_tools.py @@ -30,8 +30,28 @@ def sendTestAction(ip_address="127.0.0.1", port=9000): sleep(0.01) client.send_message("/input/Vertical", False) +# send Input Voice +def sendInputVoice(flag=False, ip_address="127.0.0.1", port=9000): + input_voice = osc_message_builder.OscMessageBuilder(address="/input/Voice") + input_voice.add_arg(flag) + b_input_voice = input_voice.build() + client = udp_client.SimpleUDPClient(ip_address, port) + client.send(b_input_voice) + +def sendChangeVoice(ip_address="127.0.0.1", port=9000): + sendInputVoice(flag=0, ip_address=ip_address, port=port) + sleep(0.05) + sendInputVoice(flag=1, ip_address=ip_address, port=port) + sleep(0.05) + sendInputVoice(flag=0, ip_address=ip_address, port=port) + sleep(0.05) + def receiveOscParameters(target, filter="/*", ip_address="127.0.0.1", port=9001): _dispatcher = dispatcher.Dispatcher() _dispatcher.map(filter, target) server = osc_server.ThreadingOSCUDPServer((ip_address, port), _dispatcher) - server.serve_forever() \ No newline at end of file + server.serve_forever() + +if __name__ == "__main__": + sendChangeVoice() + sendChangeVoice() \ No newline at end of file From 9b3a347c2ac4a894dcf6dee3b94499887ac65a88 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 12 Sep 2023 09:00:36 +0900 Subject: [PATCH 122/355] =?UTF-8?q?[bugfix]=20view.py=20register=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E5=86=85=E3=81=A7=E3=80=81=E5=BC=95=E6=95=B0=E3=81=8C?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=81=95=E3=82=8C=E3=81=AA=E3=81=8B=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E5=A0=B4=E5=90=88=E3=81=A7=E3=82=82=E3=82=A8=E3=83=A9?= =?UTF-8?q?=E3=83=BC=E3=81=8C=E3=81=A7=E3=81=AA=E3=81=84=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E5=87=A6=E7=90=86=E3=82=92=E5=A4=89=E6=9B=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 111 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/view.py b/view.py index f004f9b8..06545ac8 100644 --- a/view.py +++ b/view.py @@ -245,29 +245,36 @@ class View(): - def register(self, sidebar_features, language_presets, entry_message_box_commands, config_window): + def register(self, sidebar_features=None, language_presets=None, entry_message_box_commands=None, config_window=None): self.view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = self._toggleMainWindowSidebarCompactMode - self.view_variable.CALLBACK_TOGGLE_TRANSLATION = sidebar_features["callback_toggle_translation"] - self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features["callback_toggle_transcription_send"] - self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features["callback_toggle_transcription_receive"] - self.view_variable.CALLBACK_TOGGLE_FOREGROUND = sidebar_features["callback_toggle_foreground"] + if sidebar_features is not None: + self.view_variable.CALLBACK_TOGGLE_TRANSLATION = sidebar_features.get("callback_toggle_translation", None) + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features.get("callback_toggle_transcription_send", None) + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features.get("callback_toggle_transcription_receive", None) + self.view_variable.CALLBACK_TOGGLE_FOREGROUND = sidebar_features.get("callback_toggle_foreground", None) + + if language_presets is not None: + self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE = language_presets.get("callback_your_language", None) + self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE = language_presets.get("callback_target_language", None) + language_presets.get("values", None) and self.updateList_selectableLanguages(language_presets["values"]) + + self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets.get("callback_selected_language_preset_tab", None) - self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE = language_presets["callback_your_language"] - self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE = language_presets["callback_target_language"] - self.updateList_selectableLanguages(language_presets["values"]) self.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) - - self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets["callback_selected_language_preset_tab"] vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) + + self.view_variable.CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_YourLanguage self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_TargetLanguage entry_message_box = getattr(vrct_gui, "entry_message_box") - entry_message_box.bind("", entry_message_box_commands["bind_Return"]) - entry_message_box.bind("", entry_message_box_commands["bind_Any_KeyPress"]) + if entry_message_box_commands is not None: + entry_message_box.bind("", entry_message_box_commands.get("bind_Return")) + entry_message_box.bind("", entry_message_box_commands.get("bind_Any_KeyPress")) + entry_message_box.bind("", self._foregroundOffForcefully) entry_message_box.bind("", self._foregroundOnForcefully) @@ -275,57 +282,59 @@ class View(): # Config Window # Compact Mode Switch - self.view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window["callback_disable_config_window_compact_mode"] - self.view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window["callback_enable_config_window_compact_mode"] + if config_window is not None: + + self.view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window.get("callback_disable_config_window_compact_mode", None) + self.view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window.get("callback_enable_config_window_compact_mode", None) - # Appearance Tab - self.view_variable.CALLBACK_SET_TRANSPARENCY = config_window["callback_set_transparency"] + # Appearance Tab + self.view_variable.CALLBACK_SET_TRANSPARENCY = config_window.get("callback_set_transparency", None) - self.view_variable.CALLBACK_SET_APPEARANCE = config_window["callback_set_appearance"] - self.view_variable.CALLBACK_SET_UI_SCALING = config_window["callback_set_ui_scaling"] - self.view_variable.CALLBACK_SET_FONT_FAMILY = config_window["callback_set_font_family"] - self.view_variable.CALLBACK_SET_UI_LANGUAGE = config_window["callback_set_ui_language"] + self.view_variable.CALLBACK_SET_APPEARANCE = config_window.get("callback_set_appearance", None) + self.view_variable.CALLBACK_SET_UI_SCALING = config_window.get("callback_set_ui_scaling", None) + self.view_variable.CALLBACK_SET_FONT_FAMILY = config_window.get("callback_set_font_family", None) + self.view_variable.CALLBACK_SET_UI_LANGUAGE = config_window.get("callback_set_ui_language", None) - # Translation Tab - self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window["callback_set_deepl_authkey"] + # Translation Tab + self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window.get("callback_set_deepl_authkey", None) - # Transcription Tab (Mic) - self.view_variable.CALLBACK_SET_MIC_HOST = config_window["callback_set_mic_host"] - self.updateList_MicHost(config_window["list_mic_host"]) + # Transcription Tab (Mic) + self.view_variable.CALLBACK_SET_MIC_HOST = config_window.get("callback_set_mic_host", None) + config_window.get("list_mic_host", None) and self.updateList_MicHost(config_window["list_mic_host"]) - self.view_variable.CALLBACK_SET_MIC_DEVICE = config_window["callback_set_mic_device"] - self.updateList_MicDevice(config_window["list_mic_device"]) + self.view_variable.CALLBACK_SET_MIC_DEVICE = config_window.get("callback_set_mic_device", None) + config_window.get("list_mic_device", None) and self.updateList_MicDevice(config_window["list_mic_device"]) - self.view_variable.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window["callback_set_mic_energy_threshold"] - self.view_variable.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_mic_dynamic_energy_threshold"] - self.view_variable.CALLBACK_CHECK_MIC_THRESHOLD = config_window["callback_check_mic_threshold"] - self.view_variable.CALLBACK_SET_MIC_RECORD_TIMEOUT = config_window["callback_set_mic_record_timeout"] - self.view_variable.CALLBACK_SET_MIC_PHRASE_TIMEOUT = config_window["callback_set_mic_phrase_timeout"] - self.view_variable.CALLBACK_SET_MIC_MAX_PHRASES = config_window["callback_set_mic_max_phrases"] - self.view_variable.CALLBACK_SET_MIC_WORD_FILTER = config_window["callback_set_mic_word_filter"] + self.view_variable.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window.get("callback_set_mic_energy_threshold", None) + self.view_variable.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window.get("callback_set_mic_dynamic_energy_threshold", None) + self.view_variable.CALLBACK_CHECK_MIC_THRESHOLD = config_window.get("callback_check_mic_threshold", None) + self.view_variable.CALLBACK_SET_MIC_RECORD_TIMEOUT = config_window.get("callback_set_mic_record_timeout", None) + self.view_variable.CALLBACK_SET_MIC_PHRASE_TIMEOUT = config_window.get("callback_set_mic_phrase_timeout", None) + self.view_variable.CALLBACK_SET_MIC_MAX_PHRASES = config_window.get("callback_set_mic_max_phrases", None) + self.view_variable.CALLBACK_SET_MIC_WORD_FILTER = config_window.get("callback_set_mic_word_filter", None) - # Transcription Tab (Speaker) - self.view_variable.CALLBACK_SET_SPEAKER_DEVICE = config_window["callback_set_speaker_device"] - self.updateList_SpeakerDevice(config_window["list_speaker_device"]) + # Transcription Tab (Speaker) + self.view_variable.CALLBACK_SET_SPEAKER_DEVICE = config_window.get("callback_set_speaker_device", None) + config_window.get("list_speaker_device", None) and self.updateList_SpeakerDevice(config_window["list_speaker_device"]) - self.view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window["callback_set_speaker_energy_threshold"] - self.view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_speaker_dynamic_energy_threshold"] - self.view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window["callback_check_speaker_threshold"] - self.view_variable.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = config_window["callback_set_speaker_record_timeout"] - self.view_variable.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = config_window["callback_set_speaker_phrase_timeout"] - self.view_variable.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window["callback_set_speaker_max_phrases"] + self.view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window.get("callback_set_speaker_energy_threshold", None) + self.view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window.get("callback_set_speaker_dynamic_energy_threshold", None) + self.view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window.get("callback_check_speaker_threshold", None) + self.view_variable.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = config_window.get("callback_set_speaker_record_timeout", None) + self.view_variable.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = config_window.get("callback_set_speaker_phrase_timeout", None) + self.view_variable.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window.get("callback_set_speaker_max_phrases", None) - # Others Tab - self.view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window["callback_set_enable_auto_clear_chatbox"] - self.view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window["callback_set_enable_notice_xsoverlay"] - self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS = config_window.get("callback_set_enable_auto_export_message_logs", None) - self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window["callback_set_message_format"] + # Others Tab + self.view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window.get("callback_set_enable_auto_clear_chatbox", None) + self.view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window.get("callback_set_enable_notice_xsoverlay", None) + self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS = config_window.get("callback_set_enable_auto_export_message_logs", None) + self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window.get("callback_set_message_format", None) - # Advanced Settings Tab - self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window["callback_set_osc_ip_address"] - self.view_variable.CALLBACK_SET_OSC_PORT = config_window["callback_set_osc_port"] + # Advanced Settings Tab + self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window.get("callback_set_osc_ip_address", None) + self.view_variable.CALLBACK_SET_OSC_PORT = config_window.get("callback_set_osc_port", None) From cd78cadbbb1e8900ccdeceefb60f1c0616f3cc36 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 12 Sep 2023 09:03:17 +0900 Subject: [PATCH 123/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20config=20=20ENAB?= =?UTF-8?q?LE=5FOSC=5FERROR=5FLOG=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/config.py b/config.py index 42c8b151..a47cd8b6 100644 --- a/config.py +++ b/config.py @@ -388,6 +388,15 @@ class Config: if type(value) is bool: self._ENABLE_OSC = value + @property + def ENABLE_OSC_ERROR_LOG(self): + return self._ENABLE_OSC_ERROR_LOG + + @ENABLE_OSC_ERROR_LOG.setter + def ENABLE_OSC_ERROR_LOG(self, value): + if type(value) is bool: + self._ENABLE_OSC_ERROR_LOG = value + @property def UPDATE_FLAG(self): return self._UPDATE_FLAG @@ -507,6 +516,7 @@ class Config: self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = False self._ENABLE_NOTICE_XSOVERLAY = False self._ENABLE_OSC = False + self._ENABLE_OSC_ERROR_LOG = True self._UPDATE_FLAG = False self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" # self._BREAK_KEYSYM_LIST = [ From cd46a16178ac04c189d1c28f55895b9c0b9b285f Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 12 Sep 2023 09:04:24 +0900 Subject: [PATCH 124/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20model=20view.pri?= =?UTF-8?q?ntToTextbox=5FOSCError()=E3=81=AE=E5=8B=95=E4=BD=9C=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E3=81=ABENABLE=5FOSC=5FERROR=5FLOG=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index ea563ec2..b86fc746 100644 --- a/main.py +++ b/main.py @@ -28,7 +28,8 @@ def sendMicMessage(message): osc_message = message model.oscSendMessage(osc_message) else: - view.printToTextbox_OSCError() + if config.ENABLE_OSC_ERROR_LOG is True: + view.printToTextbox_OSCError() view.printToTextbox_SentMessage(message, translation) if config.ENABLE_LOGGER is True: @@ -90,7 +91,8 @@ def sendChatMessage(message): osc_message = message model.oscSendMessage(osc_message) else: - view.printToTextbox_OSCError() + if config.ENABLE_OSC_ERROR_LOG is True: + view.printToTextbox_OSCError() # update textbox message log view.printToTextbox_SentMessage(message, translation) From 7a5860442cc5a0434bbf561c6a31137dfd7a1215 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 12 Sep 2023 09:16:18 +0900 Subject: [PATCH 125/355] =?UTF-8?q?[Chore]=20view.py=20register=E5=BC=95?= =?UTF-8?q?=E6=95=B0=E3=81=AE=E5=A4=89=E6=95=B0=E5=90=8D=E5=A4=89=E6=9B=B4?= =?UTF-8?q?.=20config=5Fwindow=E3=81=AA=E3=81=A9=E7=89=B9=E3=81=AB?= =?UTF-8?q?=E3=80=81=E5=A4=89=E6=95=B0=E5=90=8D=E8=A2=AB=E3=82=8A=E3=82=92?= =?UTF-8?q?=E9=98=B2=E3=81=90=E3=81=9F=E3=82=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 8 ++--- view.py | 102 ++++++++++++++++++++++++++++++-------------------------- 2 files changed, 58 insertions(+), 52 deletions(-) diff --git a/main.py b/main.py index ea563ec2..87c45e74 100644 --- a/main.py +++ b/main.py @@ -424,14 +424,14 @@ if config.ENABLE_LOGGER is True: # set UI and callback view.register( - sidebar_features={ + sidebar_features_registers={ "callback_toggle_translation": callbackToggleTranslation, "callback_toggle_transcription_send": callbackToggleTranscriptionSend, "callback_toggle_transcription_receive": callbackToggleTranscriptionReceive, "callback_toggle_foreground": callbackToggleForeground, }, - language_presets={ + language_presets_registers={ "callback_your_language": setYourLanguageAndCountry, "callback_target_language": setTargetLanguageAndCountry, "values": model.getListLanguageAndCountry(), @@ -439,12 +439,12 @@ view.register( "callback_selected_language_preset_tab": callbackSelectedLanguagePresetTab, }, - entry_message_box_commands={ + entry_message_box_registers={ "bind_Return": messageBoxPressKeyEnter, "bind_Any_KeyPress": messageBoxPressKeyAny, }, - config_window={ + config_window_registers={ # Compact Mode Switch "callback_disable_config_window_compact_mode": callbackEnableConfigWindowCompactMode, "callback_enable_config_window_compact_mode": callbackDisableConfigWindowCompactMode, diff --git a/view.py b/view.py index 06545ac8..4d24063f 100644 --- a/view.py +++ b/view.py @@ -245,22 +245,28 @@ class View(): - def register(self, sidebar_features=None, language_presets=None, entry_message_box_commands=None, config_window=None): + def register( + self, + sidebar_features_registers=None, + language_presets_registers=None, + entry_message_box_registers=None, + config_window_registers=None + ): self.view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = self._toggleMainWindowSidebarCompactMode - if sidebar_features is not None: - self.view_variable.CALLBACK_TOGGLE_TRANSLATION = sidebar_features.get("callback_toggle_translation", None) - self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features.get("callback_toggle_transcription_send", None) - self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features.get("callback_toggle_transcription_receive", None) - self.view_variable.CALLBACK_TOGGLE_FOREGROUND = sidebar_features.get("callback_toggle_foreground", None) + if sidebar_features_registers is not None: + self.view_variable.CALLBACK_TOGGLE_TRANSLATION = sidebar_features_registers.get("callback_toggle_translation", None) + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features_registers.get("callback_toggle_transcription_send", None) + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features_registers.get("callback_toggle_transcription_receive", None) + self.view_variable.CALLBACK_TOGGLE_FOREGROUND = sidebar_features_registers.get("callback_toggle_foreground", None) - if language_presets is not None: - self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE = language_presets.get("callback_your_language", None) - self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE = language_presets.get("callback_target_language", None) - language_presets.get("values", None) and self.updateList_selectableLanguages(language_presets["values"]) + if language_presets_registers is not None: + self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE = language_presets_registers.get("callback_your_language", None) + self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE = language_presets_registers.get("callback_target_language", None) + language_presets_registers.get("values", None) and self.updateList_selectableLanguages(language_presets_registers["values"]) - self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets.get("callback_selected_language_preset_tab", None) + self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets_registers.get("callback_selected_language_preset_tab", None) self.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) @@ -271,9 +277,9 @@ class View(): self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_TargetLanguage entry_message_box = getattr(vrct_gui, "entry_message_box") - if entry_message_box_commands is not None: - entry_message_box.bind("", entry_message_box_commands.get("bind_Return")) - entry_message_box.bind("", entry_message_box_commands.get("bind_Any_KeyPress")) + if entry_message_box_registers is not None: + entry_message_box.bind("", entry_message_box_registers.get("bind_Return")) + entry_message_box.bind("", entry_message_box_registers.get("bind_Any_KeyPress")) entry_message_box.bind("", self._foregroundOffForcefully) @@ -282,59 +288,59 @@ class View(): # Config Window # Compact Mode Switch - if config_window is not None: + if config_window_registers is not None: - self.view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window.get("callback_disable_config_window_compact_mode", None) - self.view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window.get("callback_enable_config_window_compact_mode", None) + self.view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window_registers.get("callback_disable_config_window_compact_mode", None) + self.view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window_registers.get("callback_enable_config_window_compact_mode", None) # Appearance Tab - self.view_variable.CALLBACK_SET_TRANSPARENCY = config_window.get("callback_set_transparency", None) + self.view_variable.CALLBACK_SET_TRANSPARENCY = config_window_registers.get("callback_set_transparency", None) - self.view_variable.CALLBACK_SET_APPEARANCE = config_window.get("callback_set_appearance", None) - self.view_variable.CALLBACK_SET_UI_SCALING = config_window.get("callback_set_ui_scaling", None) - self.view_variable.CALLBACK_SET_FONT_FAMILY = config_window.get("callback_set_font_family", None) - self.view_variable.CALLBACK_SET_UI_LANGUAGE = config_window.get("callback_set_ui_language", None) + self.view_variable.CALLBACK_SET_APPEARANCE = config_window_registers.get("callback_set_appearance", None) + self.view_variable.CALLBACK_SET_UI_SCALING = config_window_registers.get("callback_set_ui_scaling", None) + self.view_variable.CALLBACK_SET_FONT_FAMILY = config_window_registers.get("callback_set_font_family", None) + self.view_variable.CALLBACK_SET_UI_LANGUAGE = config_window_registers.get("callback_set_ui_language", None) # Translation Tab - self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window.get("callback_set_deepl_authkey", None) + self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window_registers.get("callback_set_deepl_authkey", None) # Transcription Tab (Mic) - self.view_variable.CALLBACK_SET_MIC_HOST = config_window.get("callback_set_mic_host", None) - config_window.get("list_mic_host", None) and self.updateList_MicHost(config_window["list_mic_host"]) + self.view_variable.CALLBACK_SET_MIC_HOST = config_window_registers.get("callback_set_mic_host", None) + config_window_registers.get("list_mic_host", None) and self.updateList_MicHost(config_window_registers["list_mic_host"]) - self.view_variable.CALLBACK_SET_MIC_DEVICE = config_window.get("callback_set_mic_device", None) - config_window.get("list_mic_device", None) and self.updateList_MicDevice(config_window["list_mic_device"]) + self.view_variable.CALLBACK_SET_MIC_DEVICE = config_window_registers.get("callback_set_mic_device", None) + config_window_registers.get("list_mic_device", None) and self.updateList_MicDevice(config_window_registers["list_mic_device"]) - self.view_variable.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window.get("callback_set_mic_energy_threshold", None) - self.view_variable.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window.get("callback_set_mic_dynamic_energy_threshold", None) - self.view_variable.CALLBACK_CHECK_MIC_THRESHOLD = config_window.get("callback_check_mic_threshold", None) - self.view_variable.CALLBACK_SET_MIC_RECORD_TIMEOUT = config_window.get("callback_set_mic_record_timeout", None) - self.view_variable.CALLBACK_SET_MIC_PHRASE_TIMEOUT = config_window.get("callback_set_mic_phrase_timeout", None) - self.view_variable.CALLBACK_SET_MIC_MAX_PHRASES = config_window.get("callback_set_mic_max_phrases", None) - self.view_variable.CALLBACK_SET_MIC_WORD_FILTER = config_window.get("callback_set_mic_word_filter", None) + self.view_variable.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window_registers.get("callback_set_mic_energy_threshold", None) + self.view_variable.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window_registers.get("callback_set_mic_dynamic_energy_threshold", None) + self.view_variable.CALLBACK_CHECK_MIC_THRESHOLD = config_window_registers.get("callback_check_mic_threshold", None) + self.view_variable.CALLBACK_SET_MIC_RECORD_TIMEOUT = config_window_registers.get("callback_set_mic_record_timeout", None) + self.view_variable.CALLBACK_SET_MIC_PHRASE_TIMEOUT = config_window_registers.get("callback_set_mic_phrase_timeout", None) + self.view_variable.CALLBACK_SET_MIC_MAX_PHRASES = config_window_registers.get("callback_set_mic_max_phrases", None) + self.view_variable.CALLBACK_SET_MIC_WORD_FILTER = config_window_registers.get("callback_set_mic_word_filter", None) # Transcription Tab (Speaker) - self.view_variable.CALLBACK_SET_SPEAKER_DEVICE = config_window.get("callback_set_speaker_device", None) - config_window.get("list_speaker_device", None) and self.updateList_SpeakerDevice(config_window["list_speaker_device"]) + self.view_variable.CALLBACK_SET_SPEAKER_DEVICE = config_window_registers.get("callback_set_speaker_device", None) + config_window_registers.get("list_speaker_device", None) and self.updateList_SpeakerDevice(config_window_registers["list_speaker_device"]) - self.view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window.get("callback_set_speaker_energy_threshold", None) - self.view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window.get("callback_set_speaker_dynamic_energy_threshold", None) - self.view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window.get("callback_check_speaker_threshold", None) - self.view_variable.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = config_window.get("callback_set_speaker_record_timeout", None) - self.view_variable.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = config_window.get("callback_set_speaker_phrase_timeout", None) - self.view_variable.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window.get("callback_set_speaker_max_phrases", None) + self.view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window_registers.get("callback_set_speaker_energy_threshold", None) + self.view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window_registers.get("callback_set_speaker_dynamic_energy_threshold", None) + self.view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window_registers.get("callback_check_speaker_threshold", None) + self.view_variable.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = config_window_registers.get("callback_set_speaker_record_timeout", None) + self.view_variable.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = config_window_registers.get("callback_set_speaker_phrase_timeout", None) + self.view_variable.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window_registers.get("callback_set_speaker_max_phrases", None) # Others Tab - self.view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window.get("callback_set_enable_auto_clear_chatbox", None) - self.view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window.get("callback_set_enable_notice_xsoverlay", None) - self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS = config_window.get("callback_set_enable_auto_export_message_logs", None) - self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window.get("callback_set_message_format", None) + self.view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window_registers.get("callback_set_enable_auto_clear_chatbox", None) + self.view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window_registers.get("callback_set_enable_notice_xsoverlay", None) + self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS = config_window_registers.get("callback_set_enable_auto_export_message_logs", None) + self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window_registers.get("callback_set_message_format", None) # Advanced Settings Tab - self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window.get("callback_set_osc_ip_address", None) - self.view_variable.CALLBACK_SET_OSC_PORT = config_window.get("callback_set_osc_port", None) + self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window_registers.get("callback_set_osc_ip_address", None) + self.view_variable.CALLBACK_SET_OSC_PORT = config_window_registers.get("callback_set_osc_port", None) From 73c9ea4e03b612172b834e8cfb83fa2cda32cc9f Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 12 Sep 2023 09:42:23 +0900 Subject: [PATCH 126/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20model=20INPUT=5F?= =?UTF-8?q?MIC=5FRECORD=5FTIMEOUT=E3=81=A8INPUT=5FMIC=5FPHRASE=5FTIMEOUT?= =?UTF-8?q?=E3=81=AE=E5=A4=A7=E5=B0=8F=E3=81=AB=E3=82=88=E3=82=8B=E6=96=87?= =?UTF-8?q?=E5=AD=97=E8=B5=B7=E3=81=93=E3=81=97=E3=81=AE=E3=83=90=E3=82=B0?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/model.py b/model.py index 99923c2c..6eb1e5a6 100644 --- a/model.py +++ b/model.py @@ -215,17 +215,23 @@ class Model: def startMicTranscript(self, fnc): mic_audio_queue = Queue() + device = [device for device in getInputDevices()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] + record_timeout = config.INPUT_MIC_RECORD_TIMEOUT + phase_timeout = config.INPUT_MIC_PHRASE_TIMEOUT + if record_timeout > phase_timeout: + record_timeout = phase_timeout + self.mic_audio_recorder = SelectedMicRecorder( - [device for device in getInputDevices()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0], - config.INPUT_MIC_ENERGY_THRESHOLD, - config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, - config.INPUT_MIC_RECORD_TIMEOUT, + device=device, + energy_threshold=config.INPUT_MIC_ENERGY_THRESHOLD, + dynamic_energy_threshold=config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, + record_timeout=record_timeout, ) self.mic_audio_recorder.recordIntoQueue(mic_audio_queue) mic_transcriber = AudioTranscriber( speaker=False, source=self.mic_audio_recorder.source, - phrase_timeout=config.INPUT_MIC_PHRASE_TIMEOUT, + phrase_timeout=phase_timeout, max_phrases=config.INPUT_MIC_MAX_PHRASES, ) def sendMicTranscript(): @@ -274,17 +280,23 @@ class Model: def startSpeakerTranscript(self, fnc): spk_audio_queue = Queue() spk_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] + + record_timeout = config.INPUT_SPEAKER_RECORD_TIMEOUT + phase_timeout = config.INPUT_SPEAKER_PHRASE_TIMEOUT + if record_timeout > phase_timeout: + record_timeout = phase_timeout + self.spk_audio_recorder = SelectedSpeakerRecorder( - spk_device, - config.INPUT_SPEAKER_ENERGY_THRESHOLD, - config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, - config.INPUT_SPEAKER_RECORD_TIMEOUT, + device=spk_device, + energy_threshold=config.INPUT_SPEAKER_ENERGY_THRESHOLD, + dynamic_energy_threshold=config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, + record_timeout=record_timeout, ) self.spk_audio_recorder.recordIntoQueue(spk_audio_queue) spk_transcriber = AudioTranscriber( speaker=True, source=self.spk_audio_recorder.source, - phrase_timeout=config.INPUT_SPEAKER_PHRASE_TIMEOUT, + phrase_timeout=phase_timeout, max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, ) def sendSpkTranscript(): From 80a1ad5d1aa8f0a99d664b8c617011033bdc48fd Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 12 Sep 2023 07:14:36 +0900 Subject: [PATCH 127/355] =?UTF-8?q?[Add]=20Config=20Window:=20Auto=20Expor?= =?UTF-8?q?t=20Message=20Logs(LOG=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB?= =?UTF-8?q?=E5=87=BA=E5=8A=9B=E6=A9=9F=E8=83=BD)=E3=82=92=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E9=A0=85=E7=9B=AE=E3=81=AB=E8=BF=BD=E5=8A=A0=E3=80=82?= =?UTF-8?q?=E3=82=AB=E3=83=86=E3=82=B4=E3=83=AA=E3=83=BC=E3=81=AFOthers?= =?UTF-8?q?=E3=81=A8=E3=81=97=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 7 +++++++ .../setting_box_others/createSettingBox_Others.py | 15 +++++++++++++++ 2 files changed, 22 insertions(+) diff --git a/view.py b/view.py index 91d5f827..f004f9b8 100644 --- a/view.py +++ b/view.py @@ -219,6 +219,12 @@ class View(): CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY=None, VAR_ENABLE_NOTICE_XSOVERLAY=BooleanVar(value=config.ENABLE_NOTICE_XSOVERLAY), + VAR_LABEL_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=StringVar(value="Auto Export Message Logs"), + VAR_DESC_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=StringVar(value="Automatically export the conversation messages as a text file."), + CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=None, + VAR_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=BooleanVar(value=config.ENABLE_LOGGER), + + VAR_LABEL_MESSAGE_FORMAT=StringVar(value="Message Format"), VAR_DESC_MESSAGE_FORMAT=StringVar(value="You can change the decoration of the message you want to send. (Default: \"[message]([translation])\" )"), CALLBACK_SET_MESSAGE_FORMAT=None, @@ -314,6 +320,7 @@ class View(): # Others Tab self.view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window["callback_set_enable_auto_clear_chatbox"] self.view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window["callback_set_enable_notice_xsoverlay"] + self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS = config_window.get("callback_set_enable_auto_export_message_logs", None) self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window["callback_set_message_format"] # Advanced Settings Tab diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index dd730e92..311fbbbd 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -15,6 +15,9 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v def checkbox_notice_xsoverlay_callback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, checkbox_box_widget.get()) + def checkbox_auto_export_message_logs_callback(checkbox_box_widget): + callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, checkbox_box_widget.get()) + def entry_message_format_callback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, value) @@ -44,6 +47,18 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v row+=1 + config_window.sb__auto_export_message_logs = createSettingBoxCheckbox( + parent_widget=setting_box_wrapper, + for_var_label_text=view_variable.VAR_LABEL_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, + for_var_desc_text=view_variable.VAR_DESC_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, + checkbox_attr_name="sb__checkbox_auto_export_message_logs", + command=lambda: checkbox_auto_export_message_logs_callback(config_window.sb__checkbox_auto_export_message_logs), + variable=view_variable.VAR_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, + ) + config_window.sb__auto_export_message_logs.grid(row=row) + row+=1 + + config_window.sb__message_format = createSettingBoxEntry( parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_MESSAGE_FORMAT, From e15b711464978b40e8bb5df5c81a855d7b1324ff Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 12 Sep 2023 09:00:36 +0900 Subject: [PATCH 128/355] =?UTF-8?q?[bugfix]=20view.py=20register=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E5=86=85=E3=81=A7=E3=80=81=E5=BC=95=E6=95=B0=E3=81=8C?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=81=95=E3=82=8C=E3=81=AA=E3=81=8B=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E5=A0=B4=E5=90=88=E3=81=A7=E3=82=82=E3=82=A8=E3=83=A9?= =?UTF-8?q?=E3=83=BC=E3=81=8C=E3=81=A7=E3=81=AA=E3=81=84=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E5=87=A6=E7=90=86=E3=82=92=E5=A4=89=E6=9B=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 111 ++++++++++++++++++++++++++++++-------------------------- 1 file changed, 60 insertions(+), 51 deletions(-) diff --git a/view.py b/view.py index f004f9b8..06545ac8 100644 --- a/view.py +++ b/view.py @@ -245,29 +245,36 @@ class View(): - def register(self, sidebar_features, language_presets, entry_message_box_commands, config_window): + def register(self, sidebar_features=None, language_presets=None, entry_message_box_commands=None, config_window=None): self.view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = self._toggleMainWindowSidebarCompactMode - self.view_variable.CALLBACK_TOGGLE_TRANSLATION = sidebar_features["callback_toggle_translation"] - self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features["callback_toggle_transcription_send"] - self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features["callback_toggle_transcription_receive"] - self.view_variable.CALLBACK_TOGGLE_FOREGROUND = sidebar_features["callback_toggle_foreground"] + if sidebar_features is not None: + self.view_variable.CALLBACK_TOGGLE_TRANSLATION = sidebar_features.get("callback_toggle_translation", None) + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features.get("callback_toggle_transcription_send", None) + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features.get("callback_toggle_transcription_receive", None) + self.view_variable.CALLBACK_TOGGLE_FOREGROUND = sidebar_features.get("callback_toggle_foreground", None) + + if language_presets is not None: + self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE = language_presets.get("callback_your_language", None) + self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE = language_presets.get("callback_target_language", None) + language_presets.get("values", None) and self.updateList_selectableLanguages(language_presets["values"]) + + self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets.get("callback_selected_language_preset_tab", None) - self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE = language_presets["callback_your_language"] - self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE = language_presets["callback_target_language"] - self.updateList_selectableLanguages(language_presets["values"]) self.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) - - self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets["callback_selected_language_preset_tab"] vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) + + self.view_variable.CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_YourLanguage self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_TargetLanguage entry_message_box = getattr(vrct_gui, "entry_message_box") - entry_message_box.bind("", entry_message_box_commands["bind_Return"]) - entry_message_box.bind("", entry_message_box_commands["bind_Any_KeyPress"]) + if entry_message_box_commands is not None: + entry_message_box.bind("", entry_message_box_commands.get("bind_Return")) + entry_message_box.bind("", entry_message_box_commands.get("bind_Any_KeyPress")) + entry_message_box.bind("", self._foregroundOffForcefully) entry_message_box.bind("", self._foregroundOnForcefully) @@ -275,57 +282,59 @@ class View(): # Config Window # Compact Mode Switch - self.view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window["callback_disable_config_window_compact_mode"] - self.view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window["callback_enable_config_window_compact_mode"] + if config_window is not None: + + self.view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window.get("callback_disable_config_window_compact_mode", None) + self.view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window.get("callback_enable_config_window_compact_mode", None) - # Appearance Tab - self.view_variable.CALLBACK_SET_TRANSPARENCY = config_window["callback_set_transparency"] + # Appearance Tab + self.view_variable.CALLBACK_SET_TRANSPARENCY = config_window.get("callback_set_transparency", None) - self.view_variable.CALLBACK_SET_APPEARANCE = config_window["callback_set_appearance"] - self.view_variable.CALLBACK_SET_UI_SCALING = config_window["callback_set_ui_scaling"] - self.view_variable.CALLBACK_SET_FONT_FAMILY = config_window["callback_set_font_family"] - self.view_variable.CALLBACK_SET_UI_LANGUAGE = config_window["callback_set_ui_language"] + self.view_variable.CALLBACK_SET_APPEARANCE = config_window.get("callback_set_appearance", None) + self.view_variable.CALLBACK_SET_UI_SCALING = config_window.get("callback_set_ui_scaling", None) + self.view_variable.CALLBACK_SET_FONT_FAMILY = config_window.get("callback_set_font_family", None) + self.view_variable.CALLBACK_SET_UI_LANGUAGE = config_window.get("callback_set_ui_language", None) - # Translation Tab - self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window["callback_set_deepl_authkey"] + # Translation Tab + self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window.get("callback_set_deepl_authkey", None) - # Transcription Tab (Mic) - self.view_variable.CALLBACK_SET_MIC_HOST = config_window["callback_set_mic_host"] - self.updateList_MicHost(config_window["list_mic_host"]) + # Transcription Tab (Mic) + self.view_variable.CALLBACK_SET_MIC_HOST = config_window.get("callback_set_mic_host", None) + config_window.get("list_mic_host", None) and self.updateList_MicHost(config_window["list_mic_host"]) - self.view_variable.CALLBACK_SET_MIC_DEVICE = config_window["callback_set_mic_device"] - self.updateList_MicDevice(config_window["list_mic_device"]) + self.view_variable.CALLBACK_SET_MIC_DEVICE = config_window.get("callback_set_mic_device", None) + config_window.get("list_mic_device", None) and self.updateList_MicDevice(config_window["list_mic_device"]) - self.view_variable.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window["callback_set_mic_energy_threshold"] - self.view_variable.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_mic_dynamic_energy_threshold"] - self.view_variable.CALLBACK_CHECK_MIC_THRESHOLD = config_window["callback_check_mic_threshold"] - self.view_variable.CALLBACK_SET_MIC_RECORD_TIMEOUT = config_window["callback_set_mic_record_timeout"] - self.view_variable.CALLBACK_SET_MIC_PHRASE_TIMEOUT = config_window["callback_set_mic_phrase_timeout"] - self.view_variable.CALLBACK_SET_MIC_MAX_PHRASES = config_window["callback_set_mic_max_phrases"] - self.view_variable.CALLBACK_SET_MIC_WORD_FILTER = config_window["callback_set_mic_word_filter"] + self.view_variable.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window.get("callback_set_mic_energy_threshold", None) + self.view_variable.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window.get("callback_set_mic_dynamic_energy_threshold", None) + self.view_variable.CALLBACK_CHECK_MIC_THRESHOLD = config_window.get("callback_check_mic_threshold", None) + self.view_variable.CALLBACK_SET_MIC_RECORD_TIMEOUT = config_window.get("callback_set_mic_record_timeout", None) + self.view_variable.CALLBACK_SET_MIC_PHRASE_TIMEOUT = config_window.get("callback_set_mic_phrase_timeout", None) + self.view_variable.CALLBACK_SET_MIC_MAX_PHRASES = config_window.get("callback_set_mic_max_phrases", None) + self.view_variable.CALLBACK_SET_MIC_WORD_FILTER = config_window.get("callback_set_mic_word_filter", None) - # Transcription Tab (Speaker) - self.view_variable.CALLBACK_SET_SPEAKER_DEVICE = config_window["callback_set_speaker_device"] - self.updateList_SpeakerDevice(config_window["list_speaker_device"]) + # Transcription Tab (Speaker) + self.view_variable.CALLBACK_SET_SPEAKER_DEVICE = config_window.get("callback_set_speaker_device", None) + config_window.get("list_speaker_device", None) and self.updateList_SpeakerDevice(config_window["list_speaker_device"]) - self.view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window["callback_set_speaker_energy_threshold"] - self.view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window["callback_set_speaker_dynamic_energy_threshold"] - self.view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window["callback_check_speaker_threshold"] - self.view_variable.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = config_window["callback_set_speaker_record_timeout"] - self.view_variable.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = config_window["callback_set_speaker_phrase_timeout"] - self.view_variable.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window["callback_set_speaker_max_phrases"] + self.view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window.get("callback_set_speaker_energy_threshold", None) + self.view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window.get("callback_set_speaker_dynamic_energy_threshold", None) + self.view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window.get("callback_check_speaker_threshold", None) + self.view_variable.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = config_window.get("callback_set_speaker_record_timeout", None) + self.view_variable.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = config_window.get("callback_set_speaker_phrase_timeout", None) + self.view_variable.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window.get("callback_set_speaker_max_phrases", None) - # Others Tab - self.view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window["callback_set_enable_auto_clear_chatbox"] - self.view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window["callback_set_enable_notice_xsoverlay"] - self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS = config_window.get("callback_set_enable_auto_export_message_logs", None) - self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window["callback_set_message_format"] + # Others Tab + self.view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window.get("callback_set_enable_auto_clear_chatbox", None) + self.view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window.get("callback_set_enable_notice_xsoverlay", None) + self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS = config_window.get("callback_set_enable_auto_export_message_logs", None) + self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window.get("callback_set_message_format", None) - # Advanced Settings Tab - self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window["callback_set_osc_ip_address"] - self.view_variable.CALLBACK_SET_OSC_PORT = config_window["callback_set_osc_port"] + # Advanced Settings Tab + self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window.get("callback_set_osc_ip_address", None) + self.view_variable.CALLBACK_SET_OSC_PORT = config_window.get("callback_set_osc_port", None) From 73d8df91668da1b87510619d79b8f4d70ff4cd67 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 12 Sep 2023 09:16:18 +0900 Subject: [PATCH 129/355] =?UTF-8?q?[Chore]=20view.py=20register=E5=BC=95?= =?UTF-8?q?=E6=95=B0=E3=81=AE=E5=A4=89=E6=95=B0=E5=90=8D=E5=A4=89=E6=9B=B4?= =?UTF-8?q?.=20config=5Fwindow=E3=81=AA=E3=81=A9=E7=89=B9=E3=81=AB?= =?UTF-8?q?=E3=80=81=E5=A4=89=E6=95=B0=E5=90=8D=E8=A2=AB=E3=82=8A=E3=82=92?= =?UTF-8?q?=E9=98=B2=E3=81=90=E3=81=9F=E3=82=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 8 ++--- view.py | 102 ++++++++++++++++++++++++++++++-------------------------- 2 files changed, 58 insertions(+), 52 deletions(-) diff --git a/main.py b/main.py index b86fc746..7e049c3f 100644 --- a/main.py +++ b/main.py @@ -426,14 +426,14 @@ if config.ENABLE_LOGGER is True: # set UI and callback view.register( - sidebar_features={ + sidebar_features_registers={ "callback_toggle_translation": callbackToggleTranslation, "callback_toggle_transcription_send": callbackToggleTranscriptionSend, "callback_toggle_transcription_receive": callbackToggleTranscriptionReceive, "callback_toggle_foreground": callbackToggleForeground, }, - language_presets={ + language_presets_registers={ "callback_your_language": setYourLanguageAndCountry, "callback_target_language": setTargetLanguageAndCountry, "values": model.getListLanguageAndCountry(), @@ -441,12 +441,12 @@ view.register( "callback_selected_language_preset_tab": callbackSelectedLanguagePresetTab, }, - entry_message_box_commands={ + entry_message_box_registers={ "bind_Return": messageBoxPressKeyEnter, "bind_Any_KeyPress": messageBoxPressKeyAny, }, - config_window={ + config_window_registers={ # Compact Mode Switch "callback_disable_config_window_compact_mode": callbackEnableConfigWindowCompactMode, "callback_enable_config_window_compact_mode": callbackDisableConfigWindowCompactMode, diff --git a/view.py b/view.py index 06545ac8..4d24063f 100644 --- a/view.py +++ b/view.py @@ -245,22 +245,28 @@ class View(): - def register(self, sidebar_features=None, language_presets=None, entry_message_box_commands=None, config_window=None): + def register( + self, + sidebar_features_registers=None, + language_presets_registers=None, + entry_message_box_registers=None, + config_window_registers=None + ): self.view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = self._toggleMainWindowSidebarCompactMode - if sidebar_features is not None: - self.view_variable.CALLBACK_TOGGLE_TRANSLATION = sidebar_features.get("callback_toggle_translation", None) - self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features.get("callback_toggle_transcription_send", None) - self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features.get("callback_toggle_transcription_receive", None) - self.view_variable.CALLBACK_TOGGLE_FOREGROUND = sidebar_features.get("callback_toggle_foreground", None) + if sidebar_features_registers is not None: + self.view_variable.CALLBACK_TOGGLE_TRANSLATION = sidebar_features_registers.get("callback_toggle_translation", None) + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features_registers.get("callback_toggle_transcription_send", None) + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features_registers.get("callback_toggle_transcription_receive", None) + self.view_variable.CALLBACK_TOGGLE_FOREGROUND = sidebar_features_registers.get("callback_toggle_foreground", None) - if language_presets is not None: - self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE = language_presets.get("callback_your_language", None) - self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE = language_presets.get("callback_target_language", None) - language_presets.get("values", None) and self.updateList_selectableLanguages(language_presets["values"]) + if language_presets_registers is not None: + self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE = language_presets_registers.get("callback_your_language", None) + self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE = language_presets_registers.get("callback_target_language", None) + language_presets_registers.get("values", None) and self.updateList_selectableLanguages(language_presets_registers["values"]) - self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets.get("callback_selected_language_preset_tab", None) + self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets_registers.get("callback_selected_language_preset_tab", None) self.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) @@ -271,9 +277,9 @@ class View(): self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_TargetLanguage entry_message_box = getattr(vrct_gui, "entry_message_box") - if entry_message_box_commands is not None: - entry_message_box.bind("", entry_message_box_commands.get("bind_Return")) - entry_message_box.bind("", entry_message_box_commands.get("bind_Any_KeyPress")) + if entry_message_box_registers is not None: + entry_message_box.bind("", entry_message_box_registers.get("bind_Return")) + entry_message_box.bind("", entry_message_box_registers.get("bind_Any_KeyPress")) entry_message_box.bind("", self._foregroundOffForcefully) @@ -282,59 +288,59 @@ class View(): # Config Window # Compact Mode Switch - if config_window is not None: + if config_window_registers is not None: - self.view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window.get("callback_disable_config_window_compact_mode", None) - self.view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window.get("callback_enable_config_window_compact_mode", None) + self.view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window_registers.get("callback_disable_config_window_compact_mode", None) + self.view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window_registers.get("callback_enable_config_window_compact_mode", None) # Appearance Tab - self.view_variable.CALLBACK_SET_TRANSPARENCY = config_window.get("callback_set_transparency", None) + self.view_variable.CALLBACK_SET_TRANSPARENCY = config_window_registers.get("callback_set_transparency", None) - self.view_variable.CALLBACK_SET_APPEARANCE = config_window.get("callback_set_appearance", None) - self.view_variable.CALLBACK_SET_UI_SCALING = config_window.get("callback_set_ui_scaling", None) - self.view_variable.CALLBACK_SET_FONT_FAMILY = config_window.get("callback_set_font_family", None) - self.view_variable.CALLBACK_SET_UI_LANGUAGE = config_window.get("callback_set_ui_language", None) + self.view_variable.CALLBACK_SET_APPEARANCE = config_window_registers.get("callback_set_appearance", None) + self.view_variable.CALLBACK_SET_UI_SCALING = config_window_registers.get("callback_set_ui_scaling", None) + self.view_variable.CALLBACK_SET_FONT_FAMILY = config_window_registers.get("callback_set_font_family", None) + self.view_variable.CALLBACK_SET_UI_LANGUAGE = config_window_registers.get("callback_set_ui_language", None) # Translation Tab - self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window.get("callback_set_deepl_authkey", None) + self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window_registers.get("callback_set_deepl_authkey", None) # Transcription Tab (Mic) - self.view_variable.CALLBACK_SET_MIC_HOST = config_window.get("callback_set_mic_host", None) - config_window.get("list_mic_host", None) and self.updateList_MicHost(config_window["list_mic_host"]) + self.view_variable.CALLBACK_SET_MIC_HOST = config_window_registers.get("callback_set_mic_host", None) + config_window_registers.get("list_mic_host", None) and self.updateList_MicHost(config_window_registers["list_mic_host"]) - self.view_variable.CALLBACK_SET_MIC_DEVICE = config_window.get("callback_set_mic_device", None) - config_window.get("list_mic_device", None) and self.updateList_MicDevice(config_window["list_mic_device"]) + self.view_variable.CALLBACK_SET_MIC_DEVICE = config_window_registers.get("callback_set_mic_device", None) + config_window_registers.get("list_mic_device", None) and self.updateList_MicDevice(config_window_registers["list_mic_device"]) - self.view_variable.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window.get("callback_set_mic_energy_threshold", None) - self.view_variable.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window.get("callback_set_mic_dynamic_energy_threshold", None) - self.view_variable.CALLBACK_CHECK_MIC_THRESHOLD = config_window.get("callback_check_mic_threshold", None) - self.view_variable.CALLBACK_SET_MIC_RECORD_TIMEOUT = config_window.get("callback_set_mic_record_timeout", None) - self.view_variable.CALLBACK_SET_MIC_PHRASE_TIMEOUT = config_window.get("callback_set_mic_phrase_timeout", None) - self.view_variable.CALLBACK_SET_MIC_MAX_PHRASES = config_window.get("callback_set_mic_max_phrases", None) - self.view_variable.CALLBACK_SET_MIC_WORD_FILTER = config_window.get("callback_set_mic_word_filter", None) + self.view_variable.CALLBACK_SET_MIC_ENERGY_THRESHOLD = config_window_registers.get("callback_set_mic_energy_threshold", None) + self.view_variable.CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD = config_window_registers.get("callback_set_mic_dynamic_energy_threshold", None) + self.view_variable.CALLBACK_CHECK_MIC_THRESHOLD = config_window_registers.get("callback_check_mic_threshold", None) + self.view_variable.CALLBACK_SET_MIC_RECORD_TIMEOUT = config_window_registers.get("callback_set_mic_record_timeout", None) + self.view_variable.CALLBACK_SET_MIC_PHRASE_TIMEOUT = config_window_registers.get("callback_set_mic_phrase_timeout", None) + self.view_variable.CALLBACK_SET_MIC_MAX_PHRASES = config_window_registers.get("callback_set_mic_max_phrases", None) + self.view_variable.CALLBACK_SET_MIC_WORD_FILTER = config_window_registers.get("callback_set_mic_word_filter", None) # Transcription Tab (Speaker) - self.view_variable.CALLBACK_SET_SPEAKER_DEVICE = config_window.get("callback_set_speaker_device", None) - config_window.get("list_speaker_device", None) and self.updateList_SpeakerDevice(config_window["list_speaker_device"]) + self.view_variable.CALLBACK_SET_SPEAKER_DEVICE = config_window_registers.get("callback_set_speaker_device", None) + config_window_registers.get("list_speaker_device", None) and self.updateList_SpeakerDevice(config_window_registers["list_speaker_device"]) - self.view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window.get("callback_set_speaker_energy_threshold", None) - self.view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window.get("callback_set_speaker_dynamic_energy_threshold", None) - self.view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window.get("callback_check_speaker_threshold", None) - self.view_variable.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = config_window.get("callback_set_speaker_record_timeout", None) - self.view_variable.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = config_window.get("callback_set_speaker_phrase_timeout", None) - self.view_variable.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window.get("callback_set_speaker_max_phrases", None) + self.view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window_registers.get("callback_set_speaker_energy_threshold", None) + self.view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window_registers.get("callback_set_speaker_dynamic_energy_threshold", None) + self.view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window_registers.get("callback_check_speaker_threshold", None) + self.view_variable.CALLBACK_SET_SPEAKER_RECORD_TIMEOUT = config_window_registers.get("callback_set_speaker_record_timeout", None) + self.view_variable.CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT = config_window_registers.get("callback_set_speaker_phrase_timeout", None) + self.view_variable.CALLBACK_SET_SPEAKER_MAX_PHRASES = config_window_registers.get("callback_set_speaker_max_phrases", None) # Others Tab - self.view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window.get("callback_set_enable_auto_clear_chatbox", None) - self.view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window.get("callback_set_enable_notice_xsoverlay", None) - self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS = config_window.get("callback_set_enable_auto_export_message_logs", None) - self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window.get("callback_set_message_format", None) + self.view_variable.CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX = config_window_registers.get("callback_set_enable_auto_clear_chatbox", None) + self.view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY = config_window_registers.get("callback_set_enable_notice_xsoverlay", None) + self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS = config_window_registers.get("callback_set_enable_auto_export_message_logs", None) + self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window_registers.get("callback_set_message_format", None) # Advanced Settings Tab - self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window.get("callback_set_osc_ip_address", None) - self.view_variable.CALLBACK_SET_OSC_PORT = config_window.get("callback_set_osc_port", None) + self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window_registers.get("callback_set_osc_ip_address", None) + self.view_variable.CALLBACK_SET_OSC_PORT = config_window_registers.get("callback_set_osc_port", None) From ae4e77d3902c71ff3e6c04a01b4217122bd61e9d Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 12 Sep 2023 09:42:23 +0900 Subject: [PATCH 130/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20model=20INPUT=5F?= =?UTF-8?q?MIC=5FRECORD=5FTIMEOUT=E3=81=A8INPUT=5FMIC=5FPHRASE=5FTIMEOUT?= =?UTF-8?q?=E3=81=AE=E5=A4=A7=E5=B0=8F=E3=81=AB=E3=82=88=E3=82=8B=E6=96=87?= =?UTF-8?q?=E5=AD=97=E8=B5=B7=E3=81=93=E3=81=97=E3=81=AE=E3=83=90=E3=82=B0?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 32 ++++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/model.py b/model.py index 99923c2c..6eb1e5a6 100644 --- a/model.py +++ b/model.py @@ -215,17 +215,23 @@ class Model: def startMicTranscript(self, fnc): mic_audio_queue = Queue() + device = [device for device in getInputDevices()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] + record_timeout = config.INPUT_MIC_RECORD_TIMEOUT + phase_timeout = config.INPUT_MIC_PHRASE_TIMEOUT + if record_timeout > phase_timeout: + record_timeout = phase_timeout + self.mic_audio_recorder = SelectedMicRecorder( - [device for device in getInputDevices()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0], - config.INPUT_MIC_ENERGY_THRESHOLD, - config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, - config.INPUT_MIC_RECORD_TIMEOUT, + device=device, + energy_threshold=config.INPUT_MIC_ENERGY_THRESHOLD, + dynamic_energy_threshold=config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD, + record_timeout=record_timeout, ) self.mic_audio_recorder.recordIntoQueue(mic_audio_queue) mic_transcriber = AudioTranscriber( speaker=False, source=self.mic_audio_recorder.source, - phrase_timeout=config.INPUT_MIC_PHRASE_TIMEOUT, + phrase_timeout=phase_timeout, max_phrases=config.INPUT_MIC_MAX_PHRASES, ) def sendMicTranscript(): @@ -274,17 +280,23 @@ class Model: def startSpeakerTranscript(self, fnc): spk_audio_queue = Queue() spk_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] + + record_timeout = config.INPUT_SPEAKER_RECORD_TIMEOUT + phase_timeout = config.INPUT_SPEAKER_PHRASE_TIMEOUT + if record_timeout > phase_timeout: + record_timeout = phase_timeout + self.spk_audio_recorder = SelectedSpeakerRecorder( - spk_device, - config.INPUT_SPEAKER_ENERGY_THRESHOLD, - config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, - config.INPUT_SPEAKER_RECORD_TIMEOUT, + device=spk_device, + energy_threshold=config.INPUT_SPEAKER_ENERGY_THRESHOLD, + dynamic_energy_threshold=config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, + record_timeout=record_timeout, ) self.spk_audio_recorder.recordIntoQueue(spk_audio_queue) spk_transcriber = AudioTranscriber( speaker=True, source=self.spk_audio_recorder.source, - phrase_timeout=config.INPUT_SPEAKER_PHRASE_TIMEOUT, + phrase_timeout=phase_timeout, max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, ) def sendSpkTranscript(): From 6c88a541e13483dd11533b1f22956e9f91448387 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 12 Sep 2023 10:34:48 +0900 Subject: [PATCH 131/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20model=20logger?= =?UTF-8?q?=E3=81=AE=E5=86=8D=E5=8B=95=E4=BD=9C=E6=99=82=E3=81=AB=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=8C=E7=94=9F=E6=88=90=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=81logs=E3=83=95=E3=82=A9=E3=83=AB=E3=83=80=E3=81=B8?= =?UTF-8?q?=E3=81=AE=E3=83=91=E3=82=B9=E3=82=82=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/model.py b/model.py index 6eb1e5a6..559306de 100644 --- a/model.py +++ b/model.py @@ -1,4 +1,6 @@ +import sys from os import makedirs +from os import path as os_path from datetime import datetime from logging import getLogger, FileHandler, Formatter, INFO from time import sleep @@ -82,15 +84,16 @@ class Model: return result def startLogger(self): - makedirs("./logs", exist_ok=True) + makedirs(os_path.join(os_path.dirname(sys.argv[0]), "logs"), exist_ok=True) logger = getLogger() logger.setLevel(INFO) - file_name = f"./logs/{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log" + file_name = os_path.join(os_path.dirname(sys.argv[0]), "logs", f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log") file_handler = FileHandler(file_name, encoding="utf-8", delay=True) formatter = Formatter("[%(asctime)s] %(message)s") file_handler.setFormatter(formatter) logger.addHandler(file_handler) self.logger = logger + self.logger.disabled = False def stopLogger(self): self.logger.disabled = True From 9975dc6ba0398d2e402e0d295c25b6b7e85ea0eb Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 12 Sep 2023 10:36:00 +0900 Subject: [PATCH 132/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20main=20logger?= =?UTF-8?q?=E3=81=AEcallback=E3=82=92=E8=A8=AD=E5=AE=9A/=E7=BF=BB=E8=A8=B3?= =?UTF-8?q?=E3=82=92=E4=BD=BF=E3=82=8F=E3=81=AA=E3=81=84=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AB=E7=BF=BB=E8=A8=B3=E9=83=A8=E5=88=86=E3=81=AE()?= =?UTF-8?q?=E3=82=92log=E3=81=AB=E6=AE=8B=E3=81=95=E3=81=AA=E3=81=84?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index 7e049c3f..e830b054 100644 --- a/main.py +++ b/main.py @@ -33,7 +33,9 @@ def sendMicMessage(message): view.printToTextbox_SentMessage(message, translation) if config.ENABLE_LOGGER is True: - model.logger.info(f"[SEND] {message} ({translation})") + if len(translation) > 0: + translation = f" ({translation})" + model.logger.info(f"[SEND] {message}{translation}") def startTranscriptionSendMessage(): model.startMicTranscript(sendMicMessage) @@ -61,7 +63,9 @@ def receiveSpeakerMessage(message): model.notificationXSOverlay(xsoverlay_message) view.printToTextbox_ReceivedMessage(message, translation) if config.ENABLE_LOGGER is True: - model.logger.info(f"[RECEIVE] {message} ({translation})") + if len(translation) > 0: + translation = f" ({translation})" + model.logger.info(f"[RECEIVE] {message}{translation}") def startTranscriptionReceiveMessage(): model.startSpeakerTranscript(receiveSpeakerMessage) @@ -97,7 +101,9 @@ def sendChatMessage(message): # update textbox message log view.printToTextbox_SentMessage(message, translation) if config.ENABLE_LOGGER is True: - model.logger.info(f"[SEND] {message} ({translation})") + if len(translation) > 0: + translation = f" ({translation})" + model.logger.info(f"[SEND] {message}{translation}") # delete message in entry message box if config.ENABLE_AUTO_CLEAR_MESSAGE_BOX is True: @@ -386,6 +392,15 @@ def callbackSetEnableNoticeXsoverlay(value): print("callbackSetEnableNoticeXsoverlay", value) config.ENABLE_NOTICE_XSOVERLAY = value +def callbackSetEnableAutoExportMessageLogs(value): + print("callbackSetEnableAutoExportMessageLogs", value) + config.ENABLE_LOGGER = value + + if config.ENABLE_LOGGER is True: + model.startLogger() + else: + model.stopLogger() + def callbackSetMessageFormat(value): print("callbackSetMessageFormat", value) if len(value) > 0: @@ -487,6 +502,7 @@ view.register( # Others Tab "callback_set_enable_auto_clear_chatbox": callbackSetEnableAutoClearMessageBox, "callback_set_enable_notice_xsoverlay": callbackSetEnableNoticeXsoverlay, + "callback_set_enable_auto_export_message_logs": callbackSetEnableAutoExportMessageLogs, "callback_set_message_format": callbackSetMessageFormat, # Advanced Settings Tab From 658cddc9d6693ccbf236340a66938c1620183a09 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 12 Sep 2023 11:31:22 +0900 Subject: [PATCH 133/355] =?UTF-8?q?=F0=9F=90=9B[bigfix]=20DeepL=20Auth=20K?= =?UTF-8?q?ey=E3=81=AE=E5=88=A9=E7=94=A8=E4=B8=8A=E9=99=90=E3=81=AB?= =?UTF-8?q?=E9=81=94=E3=81=97=E3=81=9F=E5=A0=B4=E5=90=88=E3=81=AE=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 12 ++++++++++++ model.py | 30 ++++++++++++++++++------------ view.py | 2 +- 3 files changed, 31 insertions(+), 13 deletions(-) diff --git a/main.py b/main.py index e830b054..ef3ac904 100644 --- a/main.py +++ b/main.py @@ -19,6 +19,10 @@ def sendMicMessage(message): else: translation = model.getInputTranslate(message) + if translation == None: + view.printToTextbox_AuthenticationError() + translation = "" + if config.ENABLE_TRANSCRIPTION_SEND is True: if config.ENABLE_OSC is True: if len(translation) > 0: @@ -56,6 +60,10 @@ def receiveSpeakerMessage(message): else: translation = model.getOutputTranslate(message) + if translation == None: + view.printToTextbox_AuthenticationError() + translation = "" + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: if config.ENABLE_NOTICE_XSOVERLAY is True: xsoverlay_message = config.MESSAGE_FORMAT.replace("[message]", message) @@ -86,6 +94,10 @@ def sendChatMessage(message): else: translation = model.getInputTranslate(message) + if translation == None: + view.printToTextbox_AuthenticationError() + translation = "" + # send OSC message if config.ENABLE_OSC is True: if len(translation) > 0: diff --git a/model.py b/model.py index 559306de..46ac13ea 100644 --- a/model.py +++ b/model.py @@ -135,21 +135,27 @@ class Model: return list(self.translator.translator_status.keys()) def getInputTranslate(self, message): - translation = self.translator.translate( - translator_name=config.CHOICE_TRANSLATOR, - source_language=config.SOURCE_LANGUAGE, - target_language=config.TARGET_LANGUAGE, - message=message - ) + try: + translation = self.translator.translate( + translator_name=config.CHOICE_TRANSLATOR, + source_language=config.SOURCE_LANGUAGE, + target_language=config.TARGET_LANGUAGE, + message=message + ) + except: + translation = None return translation def getOutputTranslate(self, message): - translation = self.translator.translate( - translator_name=config.CHOICE_TRANSLATOR, - source_language=config.TARGET_LANGUAGE, - target_language=config.SOURCE_LANGUAGE, - message=message - ) + try: + translation = self.translator.translate( + translator_name=config.CHOICE_TRANSLATOR, + source_language=config.TARGET_LANGUAGE, + target_language=config.SOURCE_LANGUAGE, + message=message + ) + except: + translation = None return translation def addKeywords(self): diff --git a/view.py b/view.py index 4d24063f..fa986a52 100644 --- a/view.py +++ b/view.py @@ -416,7 +416,7 @@ class View(): self._printToTextbox_Info("Auth key update completed") def printToTextbox_AuthenticationError(self): - self._printToTextbox_Info("Auth Key or language setting is incorrect") + self._printToTextbox_Info("Auth Key is incorrect or Usage limit reached") def printToTextbox_OSCError(self): self._printToTextbox_Info("OSC is not enabled, please enable OSC and rejoin") From 60e73760a7a8391bec8567ac880212aec92e1db8 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 12 Sep 2023 23:24:05 +0900 Subject: [PATCH 134/355] =?UTF-8?q?[bugfix]=20CTkEntry=E7=B3=BB=E3=81=A7?= =?UTF-8?q?=E3=80=81=E5=85=A5=E5=8A=9B=E5=80=A4=E3=81=AB=E7=B0=A1=E6=98=93?= =?UTF-8?q?=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC=E3=82=B7=E3=83=A7=E3=83=B3?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E3=80=82=20Tkinter=E3=81=AEVariable=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E3=81=A3=E3=81=A6=E3=81=84=E3=82=8BCTkEntry=E3=81=AF?= =?UTF-8?q?=E3=80=81=E5=9E=8B=E3=82=92=E5=8F=AF=E8=83=BD=E3=81=AA=E3=82=82?= =?UTF-8?q?=E3=81=AE=E3=81=AFStrings=E5=9E=8B=E3=81=AB=E3=80=82(=E7=A9=BA?= =?UTF-8?q?=E6=96=87=E5=AD=97=E3=81=AA=E3=81=A9int=E5=9E=8B=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=8F=9B=E3=81=A7=E3=81=8D=E3=81=9A=E3=82=A8=E3=83=A9?= =?UTF-8?q?=E3=83=BC=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=A6=E3=81=97=E3=81=BE?= =?UTF-8?q?=E3=81=86=E3=81=AE=E3=81=A7)=20UI=E5=81=B4=E3=81=A7=E3=81=AF?= =?UTF-8?q?=E3=81=A9=E3=82=93=E3=81=AA=E5=9E=8B=E3=81=A7=E3=81=82=E3=82=8C?= =?UTF-8?q?=E5=80=A4=E3=81=A7=E3=81=82=E3=82=8C=E3=82=B3=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=83=A9=E3=81=B8=E5=80=A4=E3=82=92=E6=B8=A1?= =?UTF-8?q?=E3=81=97=E3=80=81=E3=82=B3=E3=83=B3=E3=83=88=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=A9=E3=81=8C=E5=88=A4=E6=96=AD=E3=81=97=E3=81=A6=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB(CTkEntry?= =?UTF-8?q?=E3=81=AB=E3=82=BB=E3=83=83=E3=83=88=E3=81=97=E3=81=9F=E3=82=8A?= =?UTF-8?q?=E5=80=A4=E3=82=92=E5=89=8A=E9=99=A4=E3=81=97=E3=81=9F=E3=82=8A?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 78 +++++++++++++++++-- view.py | 73 ++++++++++++++--- .../_SettingBoxGenerator.py | 24 ++---- .../createSettingBox_Mic.py | 4 +- .../createSettingBox_Speaker.py | 4 +- 5 files changed, 146 insertions(+), 37 deletions(-) diff --git a/main.py b/main.py index ef3ac904..0d90847a 100644 --- a/main.py +++ b/main.py @@ -293,8 +293,21 @@ def callbackSetMicDevice(value): view.replaceConfigWindowMicThresholdCheckButtonToPassive() def callbackSetMicEnergyThreshold(value): - print("callbackSetMicEnergyThreshold", int(value)) + print("callbackSetMicEnergyThreshold", value) + try: + if 0 > int(value) or int(value) > config.MAX_MIC_ENERGY_THRESHOLD: raise ValueError() + except: + view.setGuiVariable_MicEnergyThreshold( + slider_value=config.INPUT_MIC_ENERGY_THRESHOLD, + entry_value=None, + ) + return config.INPUT_MIC_ENERGY_THRESHOLD = int(value) + view.setGuiVariable_MicEnergyThreshold( + slider_value=config.INPUT_MIC_ENERGY_THRESHOLD, + entry_value=str(config.INPUT_MIC_ENERGY_THRESHOLD), + ) + def callbackSetMicDynamicEnergyThreshold(value): print("callbackSetMicDynamicEnergyThreshold", value) @@ -321,16 +334,34 @@ def callbackCheckMicThreshold(is_turned_on): # view.setConfigWindowCompactModeSwitchStatusToNormal() def callbackSetMicRecordTimeout(value): - print("callbackSetMicRecordTimeout", int(value)) + print("callbackSetMicRecordTimeout", value) + try: + if int(value) < 0: raise ValueError() + except: + view.setGuiVariable_MicRecordTimeout(delete=True) + return config.INPUT_MIC_RECORD_TIMEOUT = int(value) + view.setGuiVariable_MicRecordTimeout(str(config.INPUT_MIC_RECORD_TIMEOUT)) def callbackSetMicPhraseTimeout(value): - print("callbackSetMicPhraseTimeout", int(value)) + print("callbackSetMicPhraseTimeout", value) + try: + if int(value) < 0: raise ValueError() + except: + view.setGuiVariable_MicPhraseTimeout(delete=True) + return config.INPUT_MIC_PHRASE_TIMEOUT = int(value) + view.setGuiVariable_MicPhraseTimeout(str(config.INPUT_MIC_PHRASE_TIMEOUT)) def callbackSetMicMaxPhrases(value): - print("callbackSetMicMaxPhrases", int(value)) + print("callbackSetMicMaxPhrases", value) + try: + if int(value) < 0: raise ValueError() + except: + view.setGuiVariable_MicMaxPhrases(delete=True) + return config.INPUT_MIC_MAX_PHRASES = int(value) + view.setGuiVariable_MicMaxPhrases(str(config.INPUT_MIC_MAX_PHRASES)) def callbackSetMicWordFilter(value): print("callbackSetMicWordFilter", value) @@ -354,8 +385,21 @@ def callbackSetSpeakerDevice(value): view.replaceConfigWindowSpeakerThresholdCheckButtonToPassive() def callbackSetSpeakerEnergyThreshold(value): - print("callbackSetSpeakerEnergyThreshold", int(value)) + print("callbackSetSpeakerEnergyThreshold", value) + try: + if 0 > int(value) or int(value) > config.MAX_SPEAKER_ENERGY_THRESHOLD: raise ValueError() + except: + view.setGuiVariable_SpeakerEnergyThreshold( + slider_value=config.INPUT_SPEAKER_ENERGY_THRESHOLD, + entry_value=None, + ) + return config.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) + view.setGuiVariable_SpeakerEnergyThreshold( + slider_value=config.INPUT_SPEAKER_ENERGY_THRESHOLD, + entry_value=str(config.INPUT_SPEAKER_ENERGY_THRESHOLD), + ) + def callbackSetSpeakerDynamicEnergyThreshold(value): print("callbackSetSpeakerDynamicEnergyThreshold", value) @@ -383,16 +427,34 @@ def callbackCheckSpeakerThreshold(is_turned_on): # view.setConfigWindowCompactModeSwitchStatusToNormal() def callbackSetSpeakerRecordTimeout(value): - print("callbackSetSpeakerRecordTimeout", int(value)) + print("callbackSetSpeakerRecordTimeout", value) + try: + if int(value) < 0: raise ValueError() + except: + view.setGuiVariable_SpeakerRecordTimeout(delete=True) + return config.INPUT_SPEAKER_RECORD_TIMEOUT = int(value) + view.setGuiVariable_SpeakerRecordTimeout(str(config.INPUT_SPEAKER_RECORD_TIMEOUT)) def callbackSetSpeakerPhraseTimeout(value): - print("callbackSetSpeakerPhraseTimeout", int(value)) + print("callbackSetSpeakerPhraseTimeout", value) + try: + if int(value) < 0: raise ValueError() + except: + view.setGuiVariable_SpeakerPhraseTimeout(delete=True) + return config.INPUT_SPEAKER_PHRASE_TIMEOUT = int(value) + view.setGuiVariable_SpeakerPhraseTimeout(str(config.INPUT_SPEAKER_PHRASE_TIMEOUT)) def callbackSetSpeakerMaxPhrases(value): - print("callbackSetSpeakerMaxPhrases", int(value)) + print("callbackSetSpeakerMaxPhrases", value) + try: + if int(value) < 0: raise ValueError() + except: + view.setGuiVariable_SpeakerMaxPhrases(delete=True) + return config.INPUT_SPEAKER_MAX_PHRASES = int(value) + view.setGuiVariable_SpeakerMaxPhrases(str(config.INPUT_SPEAKER_MAX_PHRASES)) # Others Tab diff --git a/view.py b/view.py index fa986a52..f3e783e1 100644 --- a/view.py +++ b/view.py @@ -1,3 +1,4 @@ +from typing import Union from types import SimpleNamespace from tkinter import font as tk_font from languages import selectable_languages @@ -146,7 +147,8 @@ class View(): VAR_DESC_MIC_ENERGY_THRESHOLD=StringVar(value="Slider to modify the threshold for activating voice input.\nPress the microphone button to start input and speak something, so you can adjust it while monitoring the actual volume. 0 to 2000 (Default: 300)"), SLIDER_RANGE_MIC_ENERGY_THRESHOLD=(0, config.MAX_MIC_ENERGY_THRESHOLD), CALLBACK_CHECK_MIC_THRESHOLD=None, - VAR_MIC_ENERGY_THRESHOLD=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), + VAR_MIC_ENERGY_THRESHOLD__SLIDER=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), + VAR_MIC_ENERGY_THRESHOLD__ENTRY=StringVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value="Mic Dynamic Energy Threshold"), VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value="When this feature is selected, it will automatically adjust in a way that works well, based on the set Mic Energy Threshold."), @@ -156,17 +158,17 @@ class View(): VAR_LABEL_MIC_RECORD_TIMEOUT=StringVar(value="Mic Record Timeout"), VAR_DESC_MIC_RECORD_TIMEOUT=StringVar(value="(Default: 3)"), CALLBACK_SET_MIC_RECORD_TIMEOUT=None, - VAR_MIC_RECORD_TIMEOUT=IntVar(value=config.INPUT_MIC_RECORD_TIMEOUT), + VAR_MIC_RECORD_TIMEOUT=StringVar(value=config.INPUT_MIC_RECORD_TIMEOUT), VAR_LABEL_MIC_PHRASE_TIMEOUT=StringVar(value="Mic Phrase Timeout"), VAR_DESC_MIC_PHRASE_TIMEOUT=StringVar(value="(Default: 3)"), CALLBACK_SET_MIC_PHRASE_TIMEOUT=None, - VAR_MIC_PHRASE_TIMEOUT=IntVar(value=config.INPUT_MIC_PHRASE_TIMEOUT), + VAR_MIC_PHRASE_TIMEOUT=StringVar(value=config.INPUT_MIC_PHRASE_TIMEOUT), VAR_LABEL_MIC_MAX_PHRASES=StringVar(value="Mic Max Phrases"), VAR_DESC_MIC_MAX_PHRASES=StringVar(value="It will stop recording and send the recordings when the set count of phrase(s) is reached. (Default: 10)"), CALLBACK_SET_MIC_MAX_PHRASES=None, - VAR_MIC_MAX_PHRASES=IntVar(value=config.INPUT_MIC_MAX_PHRASES), + VAR_MIC_MAX_PHRASES=StringVar(value=config.INPUT_MIC_MAX_PHRASES), VAR_LABEL_MIC_WORD_FILTER=StringVar(value="Mic Word Filter"), VAR_DESC_MIC_WORD_FILTER=StringVar(value="It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC"), @@ -185,7 +187,8 @@ class View(): VAR_DESC_SPEAKER_ENERGY_THRESHOLD=StringVar(value="Slider to modify the threshold for activating voice input.\nPress the headphones mark button to start input and speak something, so you can adjust it while monitoring the actual volume. 0 to 4000 (Default: 300)"), SLIDER_RANGE_SPEAKER_ENERGY_THRESHOLD=(0, config.MAX_SPEAKER_ENERGY_THRESHOLD), CALLBACK_CHECK_SPEAKER_THRESHOLD=None, - VAR_SPEAKER_ENERGY_THRESHOLD=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), + VAR_SPEAKER_ENERGY_THRESHOLD__SLIDER=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), + VAR_SPEAKER_ENERGY_THRESHOLD__ENTRY=StringVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value="Speaker Dynamic Energy Threshold"), VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value="When this feature is selected, it will automatically adjust in a way that works well, based on the set Speaker Energy Threshold."), @@ -195,17 +198,17 @@ class View(): VAR_LABEL_SPEAKER_RECORD_TIMEOUT=StringVar(value="Speaker Record Timeout"), VAR_DESC_SPEAKER_RECORD_TIMEOUT=StringVar(value="(Default: 3)"), CALLBACK_SET_SPEAKER_RECORD_TIMEOUT=None, - VAR_SPEAKER_RECORD_TIMEOUT=IntVar(value=config.INPUT_SPEAKER_RECORD_TIMEOUT), + VAR_SPEAKER_RECORD_TIMEOUT=StringVar(value=config.INPUT_SPEAKER_RECORD_TIMEOUT), VAR_LABEL_SPEAKER_PHRASE_TIMEOUT=StringVar(value="Speaker Phrase Timeout"), VAR_DESC_SPEAKER_PHRASE_TIMEOUT=StringVar(value="It will stop recording and receive the recordings when the set second(s) is reached. (Default: 3)"), CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT=None, - VAR_SPEAKER_PHRASE_TIMEOUT=IntVar(value=config.INPUT_SPEAKER_PHRASE_TIMEOUT), + VAR_SPEAKER_PHRASE_TIMEOUT=StringVar(value=config.INPUT_SPEAKER_PHRASE_TIMEOUT), VAR_LABEL_SPEAKER_MAX_PHRASES=StringVar(value="Speaker Max Phrases"), VAR_DESC_SPEAKER_MAX_PHRASES=StringVar(value="It will stop recording and receive the recordings when the set count of phrase(s) is reached. (Default: 10)"), CALLBACK_SET_SPEAKER_MAX_PHRASES=None, - VAR_SPEAKER_MAX_PHRASES=IntVar(value=config.INPUT_SPEAKER_MAX_PHRASES), + VAR_SPEAKER_MAX_PHRASES=StringVar(value=config.INPUT_SPEAKER_MAX_PHRASES), # Others Tab @@ -240,7 +243,7 @@ class View(): VAR_LABEL_OSC_PORT=StringVar(value="OSC Port"), VAR_DESC_OSC_PORT=StringVar(value="(Default: 9000)"), CALLBACK_SET_OSC_PORT=None, - VAR_OSC_PORT=IntVar(value=config.OSC_PORT), + VAR_OSC_PORT=StringVar(value=config.OSC_PORT), ) @@ -555,4 +558,56 @@ class View(): def updateSetProgressBar_SpeakerEnergy(self, new_speaker_energy): vrct_gui.config_window.sb__progressbar_x_slider__progressbar_speaker_energy_threshold.set(new_speaker_energy/config.MAX_SPEAKER_ENERGY_THRESHOLD) + + + def setGuiVariable_MicEnergyThreshold(self, slider_value:int, entry_value:Union[None, str]=None): + self.view_variable.VAR_MIC_ENERGY_THRESHOLD__SLIDER.set(slider_value) + if entry_value is None: + self._clearEntryBox(vrct_gui.config_window.sb__progressbar_x_slider__entry_mic_energy_threshold) + else: + self.view_variable.VAR_MIC_ENERGY_THRESHOLD__ENTRY.set(entry_value) + + def setGuiVariable_SpeakerEnergyThreshold(self, slider_value:int, entry_value:Union[None, str]=None): + self.view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__SLIDER.set(slider_value) + if entry_value is None: + self._clearEntryBox(vrct_gui.config_window.sb__progressbar_x_slider__entry_speaker_energy_threshold) + else: + self.view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__ENTRY.set(entry_value) + + + + def setGuiVariable_MicRecordTimeout(self, value:str="", delete=False): + if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_mic_record_timeout) + self.view_variable.VAR_MIC_RECORD_TIMEOUT.set(value) + + def setGuiVariable_MicPhraseTimeout(self, value:str="", delete=False): + if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_mic_phrase_timeout) + self.view_variable.VAR_MIC_PHRASE_TIMEOUT.set(value) + + def setGuiVariable_MicMaxPhrases(self, value:str="", delete=False): + if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_mic_max_phrases) + self.view_variable.VAR_MIC_MAX_PHRASES.set(value) + + + + def setGuiVariable_SpeakerRecordTimeout(self, value:str="", delete=False): + if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_speaker_record_timeout) + self.view_variable.VAR_SPEAKER_RECORD_TIMEOUT.set(value) + + def setGuiVariable_SpeakerPhraseTimeout(self, value:str="", delete=False): + if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_speaker_phrase_timeout) + self.view_variable.VAR_SPEAKER_PHRASE_TIMEOUT.set(value) + + def setGuiVariable_SpeakerMaxPhrases(self, value:str="", delete=False): + if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_speaker_max_phrases) + self.view_variable.VAR_SPEAKER_MAX_PHRASES.set(value) + + + @staticmethod + def _clearEntryBox(entry_widget): + entry_widget.delete(0, CTK_END) + + + + view = View() \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 20b43474..89f947e1 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -186,7 +186,8 @@ class _SettingBoxGenerator(): passive_button_attr_name, passive_button_command, active_button_attr_name, active_button_command, button_image_file, - variable, + entry_variable, + slider_variable, slider_number_of_steps: Union[int, None] = None, ): @@ -205,28 +206,15 @@ class _SettingBoxGenerator(): BUTTON_PADDING = int(BAR_WIDTH + BAR_PADDING + self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_RIGHT_PADX) def adjusted_command__for_entry_bind__Any_KeyRelease(e): - # try: - # int(e.widget.get()) - # except: - # e.widget.delete(0, CTK_END) - # return - # print(int(e.widget.get())) - - - i = int(e.widget.get()) - if i < 0 or i > slider_range[1]: - e.widget.delete(0, CTK_END) - i = max(0, min(int(e.widget.get()), slider_range[1])) - # e.widget.insert(0, i) - command(i) + command(e.widget.get()) def adjusted_command__for_slider(value): - command(int(value)) + command(value) entry_widget = CTkEntry( setting_box_progressbar_x_slider_frame, width=ENTRY_WIDTH, height=self.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT, - textvariable=variable, + textvariable=entry_variable, font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__ENTRY_FONT_SIZE, weight="normal"), ) @@ -244,7 +232,7 @@ class _SettingBoxGenerator(): to=slider_range[1], number_of_steps=slider_number_of_steps, command=adjusted_command__for_slider, - variable=variable, + variable=slider_variable, height=self.uism.SB__PROGRESSBAR_X_SLIDER__SLIDER_HEIGHT, width=BAR_WIDTH, border_width=0, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 689452ca..82731054 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -74,12 +74,14 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_label_text=view_variable.VAR_LABEL_MIC_ENERGY_THRESHOLD, for_var_desc_text=view_variable.VAR_DESC_MIC_ENERGY_THRESHOLD, command=slider_input_mic_energy_threshold_callback, - variable=view_variable.VAR_MIC_ENERGY_THRESHOLD, + entry_attr_name="sb__progressbar_x_slider__entry_mic_energy_threshold", + entry_variable=view_variable.VAR_MIC_ENERGY_THRESHOLD__ENTRY, slider_attr_name="progressbar_x_slider__slider_mic_energy_threshold", slider_range=view_variable.SLIDER_RANGE_MIC_ENERGY_THRESHOLD, + slider_variable=view_variable.VAR_MIC_ENERGY_THRESHOLD__SLIDER, progressbar_attr_name="sb__progressbar_x_slider__progressbar_mic_energy_threshold", diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 5742292a..60d6d168 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -55,12 +55,14 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ for_var_label_text=view_variable.VAR_LABEL_SPEAKER_ENERGY_THRESHOLD, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_ENERGY_THRESHOLD, command=slider_input_speaker_energy_threshold_callback, - variable=view_variable.VAR_SPEAKER_ENERGY_THRESHOLD, + + entry_variable=view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__ENTRY, entry_attr_name="sb__progressbar_x_slider__entry_speaker_energy_threshold", slider_attr_name="progressbar_x_slider__slider_speaker_energy_threshold", slider_range=view_variable.SLIDER_RANGE_SPEAKER_ENERGY_THRESHOLD, + slider_variable=view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__SLIDER, progressbar_attr_name="sb__progressbar_x_slider__progressbar_speaker_energy_threshold", From 3714162340195972eb1d9af2854755983a4d89c9 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 13 Sep 2023 10:14:57 +0900 Subject: [PATCH 135/355] =?UTF-8?q?[Update]=20Main=20Window:=20Sidebar=20L?= =?UTF-8?q?anguage=20Settings=20DropdownButton=20=E3=83=9B=E3=83=90?= =?UTF-8?q?=E3=83=BC=E6=99=82=E3=81=A8=E3=82=AF=E3=83=AA=E3=83=83=E3=82=AF?= =?UTF-8?q?=E6=99=82=E3=81=AE=E8=89=B2=E3=82=92=E8=BF=BD=E5=8A=A0=E3=80=82?= =?UTF-8?q?=20=E3=81=9D=E3=81=AE=E4=BB=96=E8=A6=81=E3=82=89=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=82=B3=E3=83=BC=E3=83=89=E3=81=AE=E5=89=8A=E9=99=A4?= =?UTF-8?q?=E3=82=84=E3=80=81widget=E3=81=AE=E8=A6=AAframe=E3=81=AE?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E5=BF=98=E3=82=8C(=E8=A2=AB=E3=82=8A)?= =?UTF-8?q?=E3=81=AE=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../createSidebarLanguagesSettings.py | 38 ++++--------------- vrct_gui/ui_managers/ColorThemeManager.py | 2 + 2 files changed, 9 insertions(+), 31 deletions(-) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 493092b6..08dd9c70 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -51,29 +51,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - - - def createOption_DropdownMenu_for_languageSettings(setattr_obj, parent_widget, optionmenu_attr_name, dropdown_menu_values, command, width:int = 200, font_size:int = 10, text_color="white", variable=""): - setattr(setattr_obj, optionmenu_attr_name, CTkOptionMenu( - parent_widget, - height=30, - width=width, - values=dropdown_menu_values, - button_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, - fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, - text_color=text_color, - font=CTkFont(family=settings.FONT_FAMILY, size=font_size, weight="normal"), - variable=variable, - anchor="center", - command=command, - )) - target_optionmenu_attr = getattr(setattr_obj, optionmenu_attr_name) - target_optionmenu_attr.grid(row=0, column=0, sticky="e") - - - - - def createLanguageSettingBox(parent_widget, var_title_text, title_text_attr_name, arrow_img_attr_name, dropdown_menu_values, open_selectable_language_window_command, variable): + def createLanguageSettingBox(parent_widget, var_title_text, title_text_attr_name, arrow_img_attr_name, open_selectable_language_window_command, variable): sls__box = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) sls__box.columnconfigure((0,2), weight=1) @@ -102,11 +80,11 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): sls__selected_language_box.columnconfigure(0, minsize=200) sls__selected_language_box.rowconfigure(0, minsize=30) - sls__selected_language_label = CTkFrame(sls__selected_language_box, corner_radius=0, fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR) - sls__selected_language_label.grid(row=0, column=0) + sls__selected_language_label_frame = CTkFrame(sls__selected_language_box, corner_radius=0, fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR) + sls__selected_language_label_frame.grid(row=0, column=0) sls__selected_language_label = CTkLabel( - sls__selected_language_label, + sls__selected_language_label_frame, textvariable=variable, height=0, # anchor="center", @@ -133,12 +111,12 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - # bindEnterAndLeaveColor([self.wrapper, label_widget], self.settings.ctm.LANGUAGE_BUTTON_BG_HOVERED_COLOR, self.settings.ctm.LANGUAGE_BUTTON_BG_COLOR) - # bindButtonPressColor([self.wrapper, label_widget], self.settings.ctm.LANGUAGE_BUTTON_BG_CLICKED_COLOR, self.settings.ctm.LANGUAGE_BUTTON_BG_COLOR) + bindEnterAndLeaveColor([sls__selected_language_label_frame, sls__selected_language_box, sls__selected_language_label], settings.ctm.SLS__DROPDOWN_MENU_HOVERED_BG_COLOR, settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR) + bindButtonPressColor([sls__selected_language_label_frame, sls__selected_language_box, sls__selected_language_label], settings.ctm.SLS__DROPDOWN_MENU_CLICKED_BG_COLOR, settings.ctm.SLS__DROPDOWN_MENU_HOVERED_BG_COLOR) - bindButtonReleaseFunction([sls__selected_language_box, sls__selected_language_label, sls__selected_language_arrow_img], open_selectable_language_window_command) + bindButtonReleaseFunction([sls__selected_language_label_frame, sls__selected_language_box, sls__selected_language_label, sls__selected_language_arrow_img], open_selectable_language_window_command) return sls__box @@ -251,7 +229,6 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): var_title_text=view_variable.VAR_LABEL_YOUR_LANGUAGE, title_text_attr_name="sls__title_text_your_language", arrow_img_attr_name="sls__arrow_img_your_language", - dropdown_menu_values=view_variable.LIST_SELECTABLE_LANGUAGES, open_selectable_language_window_command=callbackOpenSelectableYourLanguageWindow, variable=view_variable.VAR_YOUR_LANGUAGE ) @@ -299,7 +276,6 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): var_title_text=view_variable.VAR_LABEL_TARGET_LANGUAGE, title_text_attr_name="sls__title_text_target_language", arrow_img_attr_name="sls__arrow_img_target_language", - dropdown_menu_values=view_variable.LIST_SELECTABLE_LANGUAGES, open_selectable_language_window_command=callbackOpenSelectableTargetLanguageWindow, variable=view_variable.VAR_TARGET_LANGUAGE ) diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 85600f13..029d34b8 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -151,6 +151,8 @@ class ColorThemeManager(): self.main.SLS__BOX_ARROWS_TEXT_COLOR = self.DARK_500_COLOR self.main.SLS__DROPDOWN_MENU_BG_COLOR = self.DARK_888_COLOR + self.main.SLS__DROPDOWN_MENU_HOVERED_BG_COLOR = self.DARK_875_COLOR + self.main.SLS__DROPDOWN_MENU_CLICKED_BG_COLOR = self.DARK_900_COLOR self.main.CONFIG_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR From 0b1e731246d83c18cc0f07c0bb214e098229e06f Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 13 Sep 2023 16:31:44 +0900 Subject: [PATCH 136/355] =?UTF-8?q?[Update]=20Main=20Window:=20Sidebar=20L?= =?UTF-8?q?anguage=20Settings=20DropdownMenu=E9=96=8B=E9=96=89=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=82=92=E3=83=88=E3=82=B0=E3=83=AB=E5=BC=8F=E3=81=AB?= =?UTF-8?q?=E3=80=82=20Selectable=20Languages=20Window:=20=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=88=E3=83=AB=E8=BF=BD=E5=8A=A0=E3=80=82=E4=BB=8A?= =?UTF-8?q?=E9=96=8B=E3=81=84=E3=81=A6=E3=81=84=E3=82=8B=E3=82=82=E3=81=AE?= =?UTF-8?q?=E3=81=8CYour=20Language=E3=81=AA=E3=81=AE=E3=81=8BTarget=20Lan?= =?UTF-8?q?guage=E3=81=AA=E3=81=AE=E3=81=8B=E5=88=86=E3=81=8B=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E3=80=82=20=E3=83=A1=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E7=94=BB=E9=9D=A2=E3=81=AE=E3=82=B5=E3=82=A4=E3=82=BA?= =?UTF-8?q?=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=81=A6=E9=96=8B=E3=81=8F?= =?UTF-8?q?=E3=82=A6=E3=82=A4=E3=83=B3=E3=83=89=E3=82=A6=E3=81=AE=E3=82=B5?= =?UTF-8?q?=E3=82=A4=E3=82=BA=E3=82=82=E5=A4=89=E3=82=8F=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 7 ++++ vrct_gui/_CreateSelectableLanguagesWindow.py | 39 +++++++++++++++----- vrct_gui/ui_managers/ColorThemeManager.py | 2 + vrct_gui/vrct_gui.py | 25 +++++++++++-- 4 files changed, 59 insertions(+), 14 deletions(-) diff --git a/view.py b/view.py index f3e783e1..568a4bae 100644 --- a/view.py +++ b/view.py @@ -70,6 +70,7 @@ class View(): VAR_LABEL_YOUR_LANGUAGE=StringVar(value="Your Language"), # JA: あなたの言語 VAR_YOUR_LANGUAGE = StringVar(value="Japanese\n(Japan)"), CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW=None, + IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW=False, CALLBACK_SELECTED_YOUR_LANGUAGE=None, VAR_LABEL_BOTH_DIRECTION_DESC=StringVar(value="Translate Each Other"), # JA: 双方向に翻訳 @@ -77,6 +78,7 @@ class View(): VAR_LABEL_TARGET_LANGUAGE=StringVar(value="Target Language"), # JA: 相手の言語 VAR_TARGET_LANGUAGE = StringVar(value="English\n(United States)"), CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW=None, + IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW=False, CALLBACK_SELECTED_TARGET_LANGUAGE=None, @@ -86,6 +88,9 @@ class View(): VAR_LABEL_TEXTBOX_SYSTEM=StringVar(value="System"), # JA: システム + # Selectable Language Window + VAR_TITLE_LABEL_SELECTABLE_LANGUAGE=StringVar(value=""), + VAR_GO_BACK_LABEL_SELECTABLE_LANGUAGE=StringVar(value="Go Back"), @@ -381,8 +386,10 @@ class View(): vrct_gui.recreateMainWindowSidebar() def openSelectableLanguagesWindow_YourLanguage(self, _e): + self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set("Your Language") vrct_gui.openSelectableLanguagesWindow("your_language") def openSelectableLanguagesWindow_TargetLanguage(self, _e): + self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set("Target Language") vrct_gui.openSelectableLanguagesWindow("target_language") diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index ee5a0ea5..e0c8d6ce 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -37,13 +37,15 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): def createContainer(self, selectable_language_window_type): self.selectable_language_window_type = selectable_language_window_type + self.attach.update_idletasks() self.x_pos = self.attach.winfo_rootx() self.y_pos = self.attach.winfo_rooty() - self.width_new = self.attach.winfo_width() - 16 - self.height_new = self.attach.winfo_height() - 50 + self.width_new = self.attach.winfo_width() + self.height_new = self.attach.winfo_height() - self.geometry('+{}+{}'.format(self.x_pos, self.y_pos)) + self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, self.x_pos, self.y_pos)) + if self.is_created is True: @@ -69,16 +71,15 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): def _createContainer(self): - # self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, self.x_pos, self.y_pos)) - # self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, self.x_pos, self.y_pos)) - - self.rowconfigure(0, minsize=50) - self.top_container = CTkFrame(self, corner_radius=0, fg_color=self.settings.ctm.MAIN_BG_COLOR, width=0, height=0) + self.rowconfigure(1, weight=1) + self.columnconfigure(0, weight=1) + self.top_container = CTkFrame(self, corner_radius=0, fg_color=self.settings.ctm.TOP_BG_COLOR, width=0, height=0) self.top_container.grid(row=0, column=0, sticky="nsew") self.top_container.rowconfigure((0,2), weight=1) + self.top_container.columnconfigure(1, weight=1) self.go_back_button_container = CTkFrame(self.top_container, corner_radius=0, fg_color=self.settings.ctm.GO_BACK_BUTTON_BG_COLOR, width=0, height=0) self.go_back_button_container.grid(row=1, column=0) @@ -88,19 +89,37 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.go_back_button_label = CTkLabel( self.go_back_button_label_wrapper, - text="Go back", + textvariable=self._view_variable.VAR_GO_BACK_LABEL_SELECTABLE_LANGUAGE, height=0, corner_radius=0, font=CTkFont(family=self.settings.FONT_FAMILY, size=14, weight="normal"), anchor="w", text_color=self.settings.ctm.BASIC_TEXT_COLOR, ) - self.go_back_button_label.grid(row=1, column=0, padx=10, pady=10) + self.go_back_button_label.grid(row=0, column=0, padx=10, pady=8) bindButtonReleaseFunction([self.go_back_button_label_wrapper, self.go_back_button_label], lambda _e: self.vrct_gui.closeSelectableLanguagesWindow()) + self.title_container = CTkFrame(self.top_container, corner_radius=0, fg_color=self.settings.ctm.TOP_BG_COLOR, width=0, height=0) + self.title_container.grid(row=1, column=1, sticky="nsew") + + self.title_container.columnconfigure((0,2), weight=1) + self.title_container.rowconfigure((0,2), weight=1) + self.title_label = CTkLabel( + self.title_container, + textvariable=self._view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=18, weight="normal"), + anchor="w", + text_color=self.settings.ctm.TITLE_TEXT_COLOR, + ) + self.title_label.grid(row=1, column=1) + + + self.scroll_frame_container = CTkScrollableFrame(self, corner_radius=0, fg_color=self.settings.ctm.MAIN_BG_COLOR, width=self.width_new, height=self.height_new) self.scroll_frame_container.grid(row=1, column=0, sticky="nsew") diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 029d34b8..2f980de4 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -181,6 +181,8 @@ class ColorThemeManager(): self.selectable_language_window.GO_BACK_BUTTON_BG_COLOR = self.DARK_800_COLOR + self.selectable_language_window.TOP_BG_COLOR = self.main.SIDEBAR_BG_COLOR + self.selectable_language_window.TITLE_TEXT_COLOR = self.DARK_400_COLOR self.selectable_language_window.LANGUAGE_BUTTON_BG_HOVERED_COLOR = self.DARK_825_COLOR self.selectable_language_window.LANGUAGE_BUTTON_BG_CLICKED_COLOR = self.DARK_888_COLOR diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index c4720fb6..36287308 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -64,13 +64,28 @@ class VRCT_GUI(CTk): self.config_window.grab_release() + + def openSelectableLanguagesWindow(self, selectable_language_window_type): if selectable_language_window_type == "your_language": - self.sls__arrow_img_your_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT),size=(20,20))) - elif selectable_language_window_type == "target_language": - self.sls__arrow_img_target_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT),size=(20,20))) + if self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW is False: + self.sls__arrow_img_your_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT),size=(20,20))) + self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW = True + self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW = False + else: + self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW = False + return + + elif selectable_language_window_type == "target_language": + if self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW is False: + self.sls__arrow_img_target_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT),size=(20,20))) + self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW = True + self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW = False + else: + self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW = False + return + - self.sls__arrow_img_target_language self.selectable_languages_window.createContainer(selectable_language_window_type) self.selectable_languages_window.deiconify() self.selectable_languages_window.focus_set() @@ -81,6 +96,8 @@ class VRCT_GUI(CTk): self.sls__arrow_img_your_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT).rotate(180),size=(20,20))) self.sls__arrow_img_target_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT).rotate(180),size=(20,20))) self.selectable_languages_window.withdraw() + self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW = False + self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW = False From ecfd278f96df4aed1a0c23b4bb19d8650b2ed8b5 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 14 Sep 2023 02:21:17 +0900 Subject: [PATCH 137/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20main=20PHRASE=5F?= =?UTF-8?q?TIMEOUT=E3=81=A8RECORD=5FTIMEOUT=E3=81=AE=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E5=80=A4=E3=81=A7=E4=B8=8A=E4=B8=8B=E9=99=90=E3=81=AE=E6=9D=A1?= =?UTF-8?q?=E4=BB=B6=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index 0d90847a..d02f00a3 100644 --- a/main.py +++ b/main.py @@ -336,7 +336,7 @@ def callbackCheckMicThreshold(is_turned_on): def callbackSetMicRecordTimeout(value): print("callbackSetMicRecordTimeout", value) try: - if int(value) < 0: raise ValueError() + if int(value) < 0 or int(value) > config.INPUT_MIC_PHRASE_TIMEOUT: raise ValueError() except: view.setGuiVariable_MicRecordTimeout(delete=True) return @@ -346,7 +346,7 @@ def callbackSetMicRecordTimeout(value): def callbackSetMicPhraseTimeout(value): print("callbackSetMicPhraseTimeout", value) try: - if int(value) < 0: raise ValueError() + if int(value) < 0 or int(value) < config.INPUT_MIC_RECORD_TIMEOUT: raise ValueError() except: view.setGuiVariable_MicPhraseTimeout(delete=True) return @@ -429,7 +429,7 @@ def callbackCheckSpeakerThreshold(is_turned_on): def callbackSetSpeakerRecordTimeout(value): print("callbackSetSpeakerRecordTimeout", value) try: - if int(value) < 0: raise ValueError() + if int(value) < 0 or int(value) > config.INPUT_SPEAKER_PHRASE_TIMEOUT: raise ValueError() except: view.setGuiVariable_SpeakerRecordTimeout(delete=True) return @@ -439,7 +439,7 @@ def callbackSetSpeakerRecordTimeout(value): def callbackSetSpeakerPhraseTimeout(value): print("callbackSetSpeakerPhraseTimeout", value) try: - if int(value) < 0: raise ValueError() + if int(value) < 0 or int(value) < config.INPUT_SPEAKER_RECORD_TIMEOUT: raise ValueError() except: view.setGuiVariable_SpeakerPhraseTimeout(delete=True) return From 7b15aff02eb8d9348ae79c716555c5a8ff8baa1d Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 14 Sep 2023 02:23:11 +0900 Subject: [PATCH 138/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20=E3=83=95?= =?UTF-8?q?=E3=82=A1=E3=83=93=E3=82=B3=E3=83=B3=E3=82=92=E6=96=B0=E3=81=97?= =?UTF-8?q?=E3=81=84=E3=82=82=E3=81=AE=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- img/vrct_logo_mark_black.ico | Bin 0 -> 248222 bytes img/vrct_logo_mark_black_icon.png | Bin 0 -> 12757 bytes img/xsoverlay2.png | Bin 0 -> 3783 bytes models/xsoverlay/notification.py | 3 ++- vrct_gui/config_window/ConfigWindow.py | 3 +++ .../main_window/createMainWindowWidgets.py | 2 +- 6 files changed, 6 insertions(+), 2 deletions(-) create mode 100644 img/vrct_logo_mark_black.ico create mode 100644 img/vrct_logo_mark_black_icon.png create mode 100644 img/xsoverlay2.png diff --git a/img/vrct_logo_mark_black.ico b/img/vrct_logo_mark_black.ico new file mode 100644 index 0000000000000000000000000000000000000000..7cb59735a50de000fcc0c3dda597ca313b0b0540 GIT binary patch literal 248222 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 SKn17(6>wF7sp$)L*8c+|_-z6J literal 0 HcmV?d00001 diff --git a/img/vrct_logo_mark_black_icon.png b/img/vrct_logo_mark_black_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..1d73b8a81c3cf50b4ecbc5d8154fdef043bfe2a9 GIT binary patch literal 12757 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 G_x}KY%#pzW literal 0 HcmV?d00001 diff --git a/img/xsoverlay2.png b/img/xsoverlay2.png new file mode 100644 index 0000000000000000000000000000000000000000..20b4021ab8983bb97014491c612e8b22ea450b9e GIT binary patch literal 3783 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 int: title="VRCT", content=content, useBase64Icon=True, - icon="./img/xsoverlay.png", + icon=os_path.join(os_path.dirname(__file__), "img", "xsoverlay2.png"), sourceApp="VRCT" ) return response diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 8ca90445..cbfcea6e 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -3,6 +3,8 @@ from .widgets import createConfigWindowTitle, createSideMenuAndSettingsBoxContai from customtkinter import CTkToplevel +from ..ui_utils import getImagePath + class ConfigWindow(CTkToplevel): def __init__(self, vrct_gui, settings, view_variable): super().__init__() @@ -10,6 +12,7 @@ class ConfigWindow(CTkToplevel): # configure window + self.after(200, lambda: self.iconbitmap(getImagePath("vrct_logo_mark_black.ico"))) self.title("test config_window.py") self.geometry(f"{1080}x{680}") diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 4e65aa31..2a6f6e84 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -9,7 +9,7 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): vrct_gui.protocol("WM_DELETE_WINDOW", vrct_gui.quitVRCT) - vrct_gui.iconbitmap(getImagePath("app.ico")) + vrct_gui.iconbitmap(getImagePath("vrct_logo_mark_black.ico")) vrct_gui.title("VRCT") vrct_gui.geometry(f"{880}x{640}") vrct_gui.minsize(400, 175) From fbd940310ad5117e67824312518e5d18225831ee Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 14 Sep 2023 02:25:29 +0900 Subject: [PATCH 139/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20gitignore?= =?UTF-8?q?=E3=81=ABlogs=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 22ce9ed3..7ef8f9f7 100644 --- a/.gitignore +++ b/.gitignore @@ -16,4 +16,5 @@ translators/ test/ *.pyc .vscode/ -lib/ \ No newline at end of file +lib/ +logs/ \ No newline at end of file From 504cd6e80240b59841c3e0937210bea3aa8ab73b Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 14 Sep 2023 02:40:01 +0900 Subject: [PATCH 140/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20favicon=E3=82=92?= =?UTF-8?q?=E3=83=9E=E3=83=AB=E3=83=81=E3=82=A2=E3=82=A4=E3=82=B3=E3=83=B3?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- img/vrct_logo_mark_black.ico | Bin 248222 -> 117512 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/img/vrct_logo_mark_black.ico b/img/vrct_logo_mark_black.ico index 7cb59735a50de000fcc0c3dda597ca313b0b0540..7b711af3dffc3a19de24f99ad83dbb1356585d8a 100644 GIT binary patch literal 117512 zcmeD^2V73y`)_+MmCzJz4WgkmlwFd{(x8;lA{yF-l07S_gt9Br-a=>++Dm&c?Q#F- zd3k-)A78%4&v@VadERr+z4x5+j5D6|oO7Rx!;#>qaLmj&6!PJ=k>hZ-I2>-ul(%tj z8XT?yUE||>8^4C0GiAi#q@>=(1#mbEaRy^B+m;3^KH4ABqr zCsoAZD36oigi#!2g|584jshGGmGh||2Rl2Cf{YAqlkoBI?BV6+-Y}D&|5J~Da2?XZ zFCroWH(OFt*WS*qOIbUAH*c`^_IB*?$-QC@IfP9*po3WOb7W_T9KOP{>iQ~sdPMFllwh-^q%d|?R_xZTTkq3{g@s|P zEG(GJ+__jqMa3u6m!kA@rKPbclP6=hZ{L2O{@T^67^-8KysYeJ(?j{FF8#mqucGqV zCQbTm`9RO>*RL^yO`9-vH8m^^)dzyS5)%_KHC0t?vyl;2TU-18@F&O~@__nRUtf=b z{D3C_a1Pht9y|j8*N+`LHezIG*mv&iS?uhYGkAdW@$t`HN4R!>tWAB*wPzR(cLzZ= zg0KByC=K*8s4K&v?h4=k;PXIz+OP0~ehJAPP$5P2Z|7t-HWP>w`m1ljk&w5Xj11(r z3Dub%)E`}kvc5HcLf$-FTsS5MhL!qydQErl+`;C~nRD&G=06$nf&i#};(0(%ORJ)_ za%I(v7ca2v>}+Ve@AD@-`=z0nhzRapR1_{HIT^Qp?OGf)CFR`3>gumhKZ^DD_hZnf zOG-#U{vlO~i2*ithB0@s`BErIcLV|++Xnc5?l9GaNqtKQL z3k$I&ix!n58Q`CR=e`7lQJ&nKoO?7iG_d&icnrpYRFp691llQ~zl1jY{Mj@7J^VB9 z+`jc@sjo2KRwq0EA~h7sB|me%-nuebjHkIU%o4 zg)k-+6%}E{2)^X`#>Per$@7cyC&*#NvSrw+l`FBbva%0&_%WRREq_9PIbIepE+ReQ zecm5?hEUeO^!e}%@Q#o#A^cSSfD`ojTTM(b zB}GO2_yKJgWJ$mm&QV*yR8>^)&+Oc><9)ebl0Wbl^$FM{W@d~P!Ksrc@na?-E#&X* z<%Pcw&&-#X$EvHV-}4Qg`yBp|zs$UO`0*6RbkJ2m9~hrM$O6VzxW7O_;jhd8NAiDs z{aJ|gk2~+`&v^dh>yNTBwgAwhptBbGi zZEbDXL;&3rVB&Q`{B!rA-L2Kp8AkJpCeXi3jE%o5ApHxI6c?}l7G2;=(qc$i8dAn^ zeY_a16~O?4P6XfM2YG`Y#)m)!0n7vOKbQdiW<{^;&=?209*mutj~?O6r=_N59sm5doFn;CAbGN&JSPFrP*Jfk(9ww_ z9rU5s!GriYI`pgf@)7+=pDjes3BYrNH2)=pGEv#&Q#m*S1!vC8M|{Nt(tq=j?q2Qc z;)1odw!YU50EhDOa!gxGYd}yy;3eVt{}?_)a?L|!pBA4r3kyec`^Sj3M~J_V9zMj+ zJ;s+!7<(WD9PrP@a7htYHu-O2gKcfed=>%63QMQ66OZ~hVS?^gmXxno16Ph<-bBW6ZjAF2SVOo5`Js>kRJL3RBu6s zcv-@=FDVABjJ`3EAmD({e;nN~AA-940zD4^^CZHv~|8qjTCXEg$+CnD2nC66qcIc@we^f3$p{ zZwJ!1CowVMpM^Ba3l`w#i4*1UrR7(k@{v6WKbJyd3?2_VTiZ{T5A#JR7oLSVm@1kF z)FOSESpJ7?^LODt)W83={J};KwgPrkpIA`c{LQBuM}Lqk7W|3Rh~0s`=5!n2SD+6v4m$M-#O{w4A!qyd{4^z{Hh3)qAR_a=rg zH-`Bs;d#(uCO!l4iRDAT3gbAm(}@7jPK1fi5aJV`gE-;d_z-lRyu3X8_%9_Xi6Ofx zUau-GEyaEYK)r^s3+b5yT+^n#ggT};fBsK}c}VZ$oI3T%m&^nIGf{;2`y)8;es0XhMuN`e5hx=dhwH!k z4{#g@tC0>^i{gz4RN(rr{2PZi(4dZRuYtJ|>hnN9-SR8r54f>Sol4-nEYf95K$n1Z zaAbo6efXEfe*)jd5bk->vuESS*o^!4F=SKvW%1{Dw+{S{hUp0R`!M&qd-pDW{V@&c z3BM}-6Zk#_)sbjqW5U9azSx8GAkeRVRs5%X*w3&a+{4hGE6fkgbqV_8uZ%x|_lOR< z$*iotNTYFguYyD0xU%F{pkJx%$Epj)dbuL{Leu9w)pw@p#M3Jo>!TW$HY9pM_e7@ zG#3Fif?xz)h`$2}`X>UaGh?P`Z5-xLFmD-;JDh_oU|xmR7BQ4|cw+h=6dyo1w<9=$ z=D(vG*RRK}UAuL*aHT-9$MG^ zU@hmr;ygaxkH#O$0{iMVGc&xc71qhem;bHjKLda0e_;K$4B0+mp75~rYoEIQrFl%ulaTL!8NZ+&gj~Fct0=`%y61%la|> zdVJZC2kfha{f{5Z1MRW?=(^9~K z{Qq$6l~{kiZ2kp(0i>_fWn0KAh3dmP~%@ZSaC z-Dx!MXh!wgCTToKOw2zT#ad9D9%!0qjRAsp^~ILd@hQGEDa*!M21 zeHT7{7oK<*^1ThmRNjSd@2=zChJOPOzlO&5e8!)=eXm~yW#ES3{XQ+YK>QIo@B=x3 z`rfzufF@6da0&u0)Gy<&fiHqT`S+gow>(&a;yDOjB3Mhf_ovbi^r5krum?;L#Vb&p zV9!iMaoAJvN0JY8jRQ$^{u}|`mksRSL+1}r-KYB#>;E{qfd?}YJOVuo)(~KSKk`Eb zI$6e_NI%Cv^&M!Rh9C}T2VXT5td_;`Bw-~oQ#78?_T z{i*aLdI@bF_Idfi{76bd0*jA}!><#=TJfJs|2Wzy(YY6*eH8il!LRcX_EY_-^uMQ_ zjSc)n*`dB|5ccQ=1qFSuzV;{5PoRA=2M5%BQ$+h4Xy3>W9^M8C_9uev>rbPfK>NFJ z9iqK$+LS4nhr9cGe|-enA^ctFN3Jn&0BmS1`xLOkIQP$3w;1uD4gqGfdpF*v(s;QO zcBd>G=K2h+j=8?I{F@K9m3F z%KqN>4kA3X5KtjFhX8Nq1pVc60JLkN{kh;v;d8VD{m>?0&w}xmEujB>Hs8;c`J?V3 zntKsABisgHKbxh61%AIV^k*Lf*vkv=^MH@ef1{ny27m{!whZ$CNdK{XfA%$4Q!w4S z75`opqQ4eoiu_06_mq8!&%TfKbp(1p7Q*w;7yK#!>nY&71^EVsdfAPqAL*_C#iz-~ z=>65NfgHdu5WH(dq@O5%V*N+`-%oD;&?kNjen#K%zi$7q#{v98g0GzM0M}r@!;j{{ z|B`;t8<0PCOk??S@b!x=S-cnzi_tmQ3&Dr+m+BV(l~4am`k~H)kEK;;Z_fCA#&8aP zF=5{eJo8^^`;v73OZovnC_g$n8vkB6=*7_gf)6Mdli}W%(C{tK{IBT;+`(U}`OcmA zIRWqhbmMQK>1)#d3iLx8fBN((Uhc5(9sJS#=yp%w(XT*1-~#O&-noXk(a)kC%Keq- z2mf<09{%V)k0ARWK|kOQJpB}WPPYF>H>CYkzHt5D(vPQ`a1QCd*Zc>{2me#xw|wIK z9^!|*z46}-_%C<@zTlt#!2bRJ&u##DgI_^-zx-?S{+{$hKMpash2Jkxy z<$fuSun!a7vwXL9{yx9)WdL8`U9&dSe|%}a@ZA4`{)fm%?$^?PCo=N0^h2G1{oc@j z{*mI6Ki1EG>e~0}{~>>{0l;@jz;8SF0)%}P?`B*#Gj+GT%4WpeEk}`3Y7Of>4*G4R)7P@VImOje~!-2-GjX0 z{=|GC4rP8V?U2{^rvG#J|BCmB^nXA5@2@D|f5Yix@`ru5@xBdDybJl>hGQ!4Lbtc!DDG`I^e%k!E^K`l7QGE!JMrNdR`oXig=i8V`$i66 zpZLM|eB2;!l5u__AVT~RIY8t9kpn~y5II2P0FeVk4iGu;8*u>Hwcftl{m*;rIPkwS z1^F&OcAwvf5+bT!%YiBIUjJMFW8k*{e3bAb5JYmPMU+s$NQ-u$ex{j z29oz>1g{a)BZx<^5ZONQb$}h!1>*0|;lLE+(*wzu0KDit1;NC<3FYV-_-Mny7(x8~ z861HA9Ax?qI8b;O!9>{;;vO(Q5as`8$bLe*XGU_r@)_Af9SEjmV8Hu3`ZGB2cT<7n zNoe=9===a-W)*kB*-50eDLcu@=nJ1^=u`y zKXJ?E&HqpSv_$#;-hxNi6J$T-tuIDf6o>Es;$;o{8Q>e4DJdy<|AY`H+(Th7QU1TT z_`zQuk|S;=7Z-$P2zn5_m%W&%Ccum@NuZ)?e_$`K0EsYBzLgoyq7)Kv}u^np+i_lN5}iU@kIHLW8iOm`hiWKoqaZv zd%<|w!yaCECj{Qf8UJ1cQU1TN(BGksZ+idqyPp;PJAD_c%0HtiWEO_qIMId!qb*U-=_h6WTp|H{k?=_cncyy{d`|-d`;A z@8kRXiRZ-r|M#l{?`6-%MuE=V5DX*0>w6%3cu(&^M#dNUR3pm&_mV$B_U~-^yHFgy z=Zlv;jQ8-aZrr_vrzvD>$9eRjN`SOe_U{|T~RgydfHUiN5y58n_y zbNcjG`?`VpK$QQlEq_?g;rhp>58qcS883VA0SrE%$J_Kj*3Lh5jVS+LSN>CB4fmgU zo;Z?w{&?AgO@E(*!#8~wgDC%BQ~q4&9gT6ahkg1P1lbcp^Bp_z-^dxip7W`8{;BIk z`F|hzbFi=w>@nXvgzsj@5@Ze`%=3&44KeW5^Eunzr({0<`X}Unaq2(shfIdq5!9IPEJ^1y8?@D~D?4cfjzboH!=kVX) zL}d}y3=7e@={Mm0Ej0fQX|)k=gxTNjs~lgS#nAQZ2w=Yz?6U=%?6(3zCiwlj%a$(1 z3JMCoi|j%E(EiiX(g@$Xd|wvA8_vHK_dhi~*jsB696@9BWE4Uj5JB)5!9*T@mpIsU zl@t~6--QC1jqk(1^&IwuY~Qx+lYVM{6it6fzVI#$6M_%kseyi)4f&x3xqSQhz%Koo-|EcK+ z@(2zJ!oc=IkU0vg2ls{4a#Qy*9tpmjN|M$wC*#H0CvM0))DF45?|A#fG{{jTt$)6DZ zt@4L;$mh?WV_8q1Vp&;PUj$Flv#_t=Pmnu-hQC$*u%-*&8V28C!a_p$HEvkv{u~IQ z`zO)fg`cr@O_0xz4*yp91Kseg>KW)gGuZzE>ztnh*uSGVfBwJvANbMo`ri5ft?~yx z!WaO4geL5N8ov(y{v7s^1)M+sN40(6!QU)@=*M$&a`1j*KUW8!?XT9>#_uQlmUmOW zcia6Q`TfoE2mXOP!C&ON?<2o&@8kEs%$PnMyLay%zO8%@p8raD5ar*4|Ni-sMTeZ_l4;R-5`(R;Am@O%ZpBw&3IzK`71ls@h z^&c1mnwy(3gH4mS3f7V|FpLR}^|3C7FF&^G?keNFd+qhu^ z*3{JWNB8|umx=QKNB+PM&;_m`T@cML9w|@ye#Pq42x-2g|N9Zo5X%2jeBn8u5z_q#d_I>al=&;lA9zwUWes()oz;|MvMbw11-fiS7Se+tN?(|B3DYyNp@H_D>xDiTyvZ|0nkU->ysj+gyOC z|9>AIiSvKr{GT}g z|2}im&)EQo^Z)O|W8(b(`|uI!DslWLj{n5*pE&*#$N%rwr+!KYAddgvhsVV6|NHQf zIQ~z%{T@`;1$}@&EhqkvRSn$A6;zhiLyH+JA`lAEN!| z`;Doe(iw^R-}m7$QUCise1v)m@6j9T>wnYxzu?1>kB0|KN=*DI@6v+&PaHq~pWpo^ zyfX~%fP+73$nR5a_IqCc727}H6nNnRHkE_p1An{(9(_p&@Bc4bvIMKHuKp?S|AW2e z(W6J0n25-Keg_m}FDfjIWn^Uh)$#{_4ULVBn4PUH20k%>FJA*tKfw1xa&q!ddyg90 zB=`@#5D$Ai zAI6TLe(~P``p%2N!B}Bo;ZJGz6L|}=1K&e_zP?|KGtlOH?i}{`@nd`)nwaO0jQ^_g zC(sP|eJ%9$_5IX#Pr#KB!dL(}d=1|JE1Z9I`4f0b48Na#J)W0D`TrQcj_2{Od`^@< zQU1T8|Hogs&HPEY5!*kp{S({&-&+TW?f>tUJ&a>S`4i<&?EnAXI`D7pA8gnX;^O%4 zBEhp@mnFiV!vnB8Mny$oT+^nF_gnm$u*L@K5<&t3SW;3F{(D&9lb;BG4iDfPXRuZQ zYpn#Cqp%V|$GA8)cg`Ga>EgxM5+eLrJV4()hBaqs=i}s^jLt8Ni{sZ{VLuiT{yZK` zkbOUryDi=_&qtAr{U)=s)1eTDKoCJBf?))IBE3YK{*h}plJQXl zlMvvDKOzT+93XOl$N?e;h#VktfXD$N2Z$W_dpLmoh+j|Mhq>0uei(nu`Cy?viofUghzg30`;oehi=b1I52M5w zkpn~y5II2P!1v;S+lLvEd@%6+fXJa$E0-|PPNPM|GAvuFzWQw!Pl98nL|5ov?-9V^ z6rL^ITw+y@o{_ZD8w-7`7+xEMOPC~gWxO*bIzi?mf!;?{1-@X+^u9& zf1$(8`^$eL2Q->^ZzQd$3#~n*{w%2P*5x8avmj&E$g{VdN;}D1s;FW{#SSQTdvCAw zQJ-`uyolM0u3r`9uYScX@gylN^~f4`9Epzs&mubAxNzQf6UCaGfWfoLB?cCS$2MT} z7`^nV`!CL27uUJzaeZBi?qDrvS7J%HOeU*!P*3X=oo%_Y4US_ir#9s7&URyazH~pr2ecJ!UzAGaBCR>>Hj2sBzzEQ)$ z!Xh$kehurRW=>;zz+!f+!~SgXcH8s4fm)Xp>ogf6j~H|H%(oEmq$kz%lq-|eAv?s- ze-a7fF^gFIqAFp*^V>v+>|SSTImOO8wRHE4o8>iT6kbXBPO0(TBg0hf zxYg)qSM7+`ZI`2VnKm+aK%jGyWY-*K&AHF2m0Va@PF~*Rh9i-3)-<%?D0&)mSCU18 zLC15No0px@@+wS{)+>W-(z3ald5Z2Pr^+)q1cwhzKj?gvcLtW`>@r};?Rw4F?rMTg zdDAMTt4=Eid8@M{@c>_;Q)i`^8+J#YGbSje z_n!FG^pcpJrQt0MmWNAU8{l*@U(DXiXza@9r5Wc*KB@D3UZ0K%iNQ!sh^tcV*;L1j z%deS#1sr2MjNVjw(V>yrOP&=>^Lyoiarp{73wt5={|Vh zX>+(o;2i~HyA-DWU^~o#DT(6nYK~BeT)9pQulmg6yxAi4Yy~w}tVU8rYHO5V^hrE= z7$5xYFs z{*lR%*P1WgI2^pdgL?`+W0`2Ij}J}T@ub7Kf&u1IhjX?}CR@K-pt!0zAt9V6uG%t= zlc&94CaOK=yAGG}_j3>2L`Q(Ln^d5^c>4l5*2mznWQ2(XxMz;o@k`n(v3^tk~crChF-y|dlV%FL47KuG&6CG zUUYUdFgnk4oI~wNDvhn8I_Wvq(q;4){kHgIlrJ4DJ)jg3cI>)wGfuzpzIa;Gg_UY9 zgJoxW^LnSfilm&9vu&|wX{}7WX6lgLNlcYQWlZM;pQ@C&23-M{`MI3CQ&j{+wMl%! zMH0dq_X!`9JSG{A3qRdb8(!_px9U~xsTC4_*G+Hu(`qwZnx-CzduDN|RNat%po^8v z;Jj~r8%6)^;aAR_LkVMcTnft-Mv_e5RlaTW!$PqXgI%JvL1!J;RkT&{*fKfZQJM2( z4O74B2uEUZj-?b?Hg1@H<_nE$Q!(ia&&H1Q9lcw9iBlq>`qD=0ZbzY1yWKXCO)}+S zo;O=X6Q8Qk&wj9fn6K9RtnI*B&9yr08bME_H=fWJPt@%T?JU=?Ebny|%~sf3alL2p z{qQUL%Mz_TU#CCxJiLFOLC6;cr~BCsTDpZvB|#vfSF7Hm8P=Q z&>#iJ!aAcmqtMZ+9SP^dmEy`RV@oxQ8DzR+SL3|*gox2No=$#Jfs;RLBiiu7^A7u? zbyE_lALVe$c8tnyZg{B>QQ^R3kM)hU?IpYW__f+ah4SkQi+MzE)7&x`0 z8VphxI@aI6`zULgq$9OKQ~&bRYTgWw>2wEY$+33V*X3jxQfX|Ny@vI z6)2^r@4cU4{5v?{_J<_7gll(Raocs zGRMxeTvoQlT2o)gj$~`U&#LA7l?q?!?j-BXh*DNelw#nCZ#;RJr-`P8XG4G5P*l2^ z^Q(Tv0jwJHa+MRXQ6)`Vy109J9Mfw%dr#R;)h^D5R&1#hHXRon8h_hURTaI zk*hYJ$%Q1B*M2Om$0p4o*M?_Eaf#e$QA{!H17JohI(LV3YXGm=u@jE2@I zdsdmH=Xo4X3y*DS+rf8CZc9dav$A$0M{v1WfAJn+9}yoBCCU+iexj5FmY z6$bT$aq7Z0r{kZDu&!>`jF+vGpQ5m{^;Z9*^fA`{;J8!)zgefZ&XMagd(vU1Je)q$ zf2IxzZ6b%QBo|3Xj)PV1aIor*!57`v#nbjkmuHJtTi)VYBb@KQjnzWYuj|PXEhQ_G z!wup=u>uZ zp7ZKBPO<7sIOdSn#6JBV!4bvwhFxN_%pEMVZ1h&AuZw+YF)7m}6VnUfGqQ?`Eee*#SM~D3ba&qby7rA0%!~M#* z06WWycn9gY6=edJu5o%oRK_;~n!~1<EW@{d!A>nkZw@fvIZS}ca-_~vB1SR{v*qzXaq?%%_w{vNV1W4 ztMi$&6bAP=+SXn%Np=mjj-7Lal$_e_W=C@(>t@4ODYu-X(*o;P+8n)KE+>D@wM(+y z^S%vTUU?4XJYt^J?1Me$j;6bxs1CF#CS_`aQu6!deshGEp5TjnX&J4TA=$9v zX$+q#>6wwFO>vw$2dB_eB6U6#msqSxYjnrYT6X3AMI?OB<+vEHsSAjbAf?Kj@9dh@ zgLNE>t|%Puk>haELkFEZ<=m<(*0RePpBhEFG4Z-s>y)nv8|&`(j`iejCGUwJEOVi^ zJr-!JL=h7e$X#uDVgF%LmHoToB?{|2inrSO>z`ON@A{6qL8IV%S|wEOd!8?2vJ=Y? z_7T36_P}3`mF_InTAnq7)1?lC<+JTi+_igE|87alV_>Xnv}TN^j`NJwDi@)+73HDf zd*e7cT*3o)HiRGCzmKN(_0EO&&+nf{ljSm)pe{(^wp3?+tt+LOHRXk|=S`CH%t^X- zzAk*=N@jV2v-aTr#FN!E?Kb`K!_!mNI^7&NUA4bjh+a8_#!oNjc72R>>`KN*ZMKQ} zv}>C6uiJ&*tgdZ5eVNmVv0cAEoHN7Sh-25tK$mA}wQZdKs;ka{eS2v`UZ?qxiB;f0 zWsb9!T@Ack7+(?|qrfw5k*trU+~jEz+v95AgjFS1Cs!XO-Ev4kujgnVTp+kM!*p?m+$uepS z-x&RU4mQ@;C0=eNjjuh$iS$P3pKdIq6(R2rb1XP|aGk^LZrugscJ&qhr8bgh923e4 z>cZWsuBYpG9!)=LQ&TpqLPEi2{YHP;*%&+J;bJD3Z8?&8yhO*|kdcNuWl4u;hb%>zX>73tjonSm?YY?KZOfabv=yXQ?y=NK^D3rUabK`2aF7j*#qq?~ zo?O8r&nuc2u9(=pq4SkPOA-T@Gj_aAD|GPA$>u_}{gQ5&Bi29G5i*?Lu4ctcp4lrO zp0{g7_0d7AY}d;Qx7y-jY7!PjrZ$a*4F`DKY-vu=ai4kW8dtlgJpD$KlN6>N{zgh3 zMb&pQjEB>&3tiZEU&QKuTe4M$^RRS&Uw4eJ;TX?=a+mAtVET4*yBGzk(MyWD>a3b` z?c)u?*Ha7WJ3oIZwAPcOFD24$gM)odj%%Y@sTuHuE_rg87?Nay3i)|a~IMTSI+poQJ#=TI8uXxfs z&C~9b-6>ysHXV&!aZC&EYoe~@fZ&q&u<+|UX~(2T%mz3!URJy`NHn!>*KDYGF_Ybe zeq_pq`OGdug<9B96Z_+2q5hyUJ2&T6V@UzErmbD;RA)bA)1LGC4(}u(!Ic_ZOlIrk zqn`TnrWI=ij;^WcI*}ygpw)e}Dk0u7Trke2Og^0Eq=G~1HV)nP=6OBm9mFs8T`4)e zL(47m>4J6PsZ8^2TZ_69*duH23vftWr_c+t@Q>-;Nd1cDRTokY&6>59%c#1P8LguA zazs0O-}K%~zL?v;Hzh)v^8BX4+5|zpsu$a@u2cvWf3S($X0L`tXY;`uvaY<{&dO<7U zvy=Belt244HO~*`$_YC*42@@=SdD=gaC8hBVKuiH=rhg7T4MJ(rMR3*10w;UY{We&Q&ZRS9vwM6R9@KOf@`%_C`-wX5Uulmd}ld{O6`U<^J|q0vgaD6#~76E zvvA~jG>@yWGnv10OY(*Y+FP6Ac30mJlFPrR8kFoHrevkO4KZaVu*S! zPYvUOqPYi+dW?Fi4oIcE8qno6_uOk2k?2@%85w9U-AIW2ElXF3D#9`|{? z;e*ziEJcfBPA;I(WMq>+2-&OvB{KP0#WsbEZi%eKS*}ciwn0S>&o@@VP)K4l?fk*| zJ&}f`5pDEE264&OnR}%Y>Ko4+pHr!RaR1POU=N2PMROj;e$_*d4N)Dh-N1Z4*(%IW z#B@$Ab^=oyTRo+8hxyH2N=s*p2!?VO^|uY%&P^?=kz6AG^gxh%ZB^6~Wyd{_j!L=q zjTF;KP&kGrKaW{xL#lpE6{O*jKjgCW)}2LCJ4%{*;#>b`S6f4A8x9J9QSe*)^h=*&%}Ta1=RiR6T}x;<2w+_9m1 zR*_>{a*QlhJtfy}*W&@1nyy94o3Z_)Vw*Sh?8?auq%XZ`8Ih1I;O zL{?+TYd7qeV`NjA<1A}I!Wl_&IS+w*U=iD$y7!z>zy%9us@|g zZ&TR%X^M(9{``-X^;N z5(*uguzHQrDUB*rC7#ze@9>*et+btgw;aOpsZ>GQTq%aMz_wi5qefefA6g)|_CTHH zIoZMO(~Gj&>S6?3OYbV$1jOg;Iq*6g9o?MH5;sh)ma7&0YAQif;V#)ga}8WgL84E^3d4q z!i-n6dSd2|IP-i;3!cr1&t;H>X@{F*adK;g`?6%g-t#Y5&zVZ(YH#ZNJ7j%r>n%*fA+$~SV^O{vA)P^;oNTP4?(nPto z*Kfw8>YHY39hax-ehr5jlhFA|?WaZgv7{sk$+OsjORX@rur-?2mZE}(Mz47kIGe>B*g-=7`75SAlebA<{!Ap42>xDLXVvl`rFE)rFJ~N zHvMw@{(Jh{6Q9$jS8TDmBYT;TZ#DEX`*p6V&2`q`wUKRYk4(&HPu$$fWb8Vne?D7` zh`CM?d-f{n7xXlC#+cXhtsc%6hNY}7T-`M%LMMl<3(_4teLOvSkGOu;szcSR8RZ*T zNIJa|N%&7@U$DrPG4m`eZfVu0t0_Hhg{eAhs4#U1sN+lzP297dI{m7r7F95%(||W4 zQbN7-;trJZF|&Hpobh~SyyQ4{0jb3N>PxH=`=(CYR8sZ)bjmCRJ1KR3f5b<*=&K9b zLbho>JAWyfc?$Ow?g62DMa5gp4vVD~J}-PO!{pqtx7xCGGxdUfk8^fMZJ-_t84F1{ z7ira-@^Yh@$F4{I6={3IdKRH+#H3;*=KGHsY(2}3ybZXL<@U`ki|PzI;OsM9PDW0q zXiDSKC714(Kdsy>tR~OfsO74wvd3wU(>;<7dvv6rxu&`biOS5R4Id3z4Nyim=WF+tC;;|yhn3{XDDkkB!<<2-xA^wXvzST1OJ=`2iliMm7?Tp7r zlN%RxSj#R{chS80(+vEG@UJ&w98<*E+spACnt95NRsJL^iB|?)e>GZh;JW-&`&f6H zr{ZHzqe~iW&r*$*8NEU-VBWGCg8n9it|iIGuzz{ws=pc%syMc+(sb(nC(3>(N*rWBi%L} zEQ6Pqy9UoZwUpnl*ED%>Dy<_&pF}HzL|$EdgZou`A9t^KEwv{dcNBxVcn20f31Igc zW80f=5x7ebx6vl5-n1^G!C9%JXHeo=MfASHT3zP7dM_-r_1(8?80$9T_?I;?-BPR* zp-N$O5*lb$eS9g~^KI_RZxd;uh?3e0{uXtZgiZ(=8)@sSi%ogWP>ST7Bc2uYeAw z8$o?Ud|OCLcUnaxCySXgSTa~TvJLFl@}pc6Lc10%3#xjsMLp59 zD=6zK2)=Efs|%)Ai~^+_Uj+XM1r*$)^nx!6rVw_!_q?_TF}=c-Y3h?Ci+CRQ#` zSw^*PA06K$uY?7PibjexQ<=)r820SmHD7siX~zu`MaNGiQ(avpb72 z%|cdV%SXk^84Oq>4%sPHWq3)JQQ6Rop;a!a(>W&6G)d}`&klPYh?<*%9i!E8+mK$anl&!B; zYByfFZCho|E}ODVo$KtylJ+vc6uYthMRb05@wq&MyNp9Zy;{u;s-wQmscTZ{$dzeb z*e=kkvyLVTIzDz?uQB_fQHpG3KnI`o`l@ght0}Vk${PFUXY8y@?HlzLIMHWcQnOvz zW5JUkljBsUx2(=SW-V)=UK`2oMXGY9v`XfLjKZ-3xt#LKJEymDmc|)Bz1(W1%x-E& zna`eW$af|`dr>`q0hd$%KzmoOTU8`Fga>w4`VOZIaSchfq_*C{nJ1gH2O{bZZN@f03cskE4a7;E~Dl`2>&D#kX%FUVW(^+vnt^7E*)^-tD6 z;fOe9sD5ld365SH6&!BOvSMTN?!^Yi5wV2<9DT>4*|ckAwjSMUm>^j^xQ1l7$g4bN zK-T63S+&wB+%%rF6d+F4xe8{0R$zn07wEvUB$gTP3RQ1BWoE`hy%KH~w=zro6 zd_jTZb;{9U8B4~vbK#*o51rHFnU0i7*%rRx!>y8cIWnqT>BllpIBtHq<(b>q4<<(Q^LvRi>l(CY9(eVp?sSJzz}%w+^5tkr+}uZV_RG7Ev=8fs(cC#F zIrzNyp|A7ezNslQuj!`j7<^56+AFTtazTZK>|NjF){TBF=NAl6<0vat*WJ0vmfx{< z-{Fh5x9^moGN@)Z@LHC#Eal1U&evx+t|kqz)$Yw|IcLXNWE&HhJgF$py+qK%$49bRepMtD}mJ^O-FmQ^hsEggkT*e#(Nq2r3X`vR+9CH7k8 zu8=OXYgeG`bIRQ#TYhv!@wOZOJHlquB;~fWucc)&*L7G^r~M>ibI#>@?Vz+MwYF8c zRIv{&cKN=XRYe+F?atp7wyqYmIn;z9L1A*q#L7S3QH86nagDptlpI4*FY!hD%mp& z7S9YY0VAG;XZmf**lD}xA34G~wcl2)^L95n_Giq>JEL<~=P8N6o%Ls>7S%?qE2(Zc zt#vW(P}bgf`^3b%$DJjdE$I!r*gKAtR3DM92(>w_iRQj8o61U$%r?$uoJ49yl`Fxs z%%EO*4t6JYAaz#$&DEjH&+d3Bl|Gt2+OBk=V{>Ij{p8Njfz=wU)D@GG9`0S5;ImqZS6G=_O`ULk<_VRIYmdSXUfPtn0Er=AKW%6O%y(#bBW&D*l@=Hq74Rf&<(oJDCAi8p6^$=@>2FWkF3 z(0)%AL(EYN;~SH;_Gn8yO=Q@%R82ad@rcM&o#$iDMh;kntrPWG!`mY}cF&`?>K3Gt z8x9%G@1+^7QBHp|+c{e-t#^~v9C!LT-u2cISB@=UTrxNEs71T3^-KM|TXr&5sq)l@ z$!g8c^siZ(dZUNMm-N_-V{)DCaCmf!KaCX*raIncZ@vU;xLDKAotoNCDYacN$HWuA zCq?Bfh1!w4fg?p4%SsJmlA_tOI6}%dElO{hUnmpvnrEq=ZsWpiBgi{)}ShICyTm0BlomolRje# zofAjCzg)-cj@j5&wm1`C^PuaM1x_YPuUle;Se9^c>W1;`s`h)%*U8kuYp~>6$(0?a zZz!jyZ6~|Bpo@IdywMv?>!V2v)8<(VdATUr92ziVDw1xU8pCqMZh6O<3WkJR&3A9? z6x*kHY;Cdxb@8R z}NtFCVjJs2$!!e^;o=6!4NfX^kLOOxhXxdhHWGJC-gJzua>JFgAN*=zfEj+Ae< z%co#lg|qbu+@gH&9>;TorIjRFy-qh8@|Rq6S~$4Bn@Q#HBJs)5_I{_9YtQRt=D#?z z{oJ`7jLdS8=jxWqzwYWRvU;`p&~h#&$MA9+i$`ml2Nu;zo=~Nns-m^;c~4&HZAveU z-(PIb`qoy)hlT8h>+iT-l+GP!Cl9ZgZ(uMxP1BmSEO9}HV6c<#-RAVlJ=tFE=ND$Z zaO!pLwGb~-G9C8gj_)iA%amE5O*UC#Q=IiP8Y8Rd*b+{6``3q<`ZtP@Ezj!-X4!uv zOwZ@>vlS#&lIMbgf>LaR{A|Ml?HC^+HDD97TwuY`*v`eVCAVc7Ds04ti?h%(FE=f)=GgDJn ze@aW?mbz>)`t1O)z#8%I=`ak!PxN=D_~FEcc%D zdhNUOt1bP`%@^N0L~lQs;@jaEKehHDItJ5a?jALt!A6eeTKh>=rk+AnqA$ST zG;9UmtMZO^p#=NuvX41UY!w%z-L@9HHrl)zH)4>OZ~82x&@*BhwYZ~nvZj@dkU&-? zm(zx}j2*mZJy}YZm}C7*g*7BJByN!kmYM`Ot6OqYAyI_jRy|@<)*aclkdrnix?b0Y zdsn@TO#tV~$}_x!&#oxlahrAbnq2s(dvB}2^7RZ{PTc#pGY>p+GB#CxHe=CowqzzN zD@Vstg)XU>nxbPhllbJM<)jDL{hCY@{11A#c|V@LoaEw>Cr_U~ExAkKBkd!tK}unI zYiicQN!~&gZBLKw1x5ET6MH< zaUzx#`UQ{q55LIKSF0fj&7!1Z9Hb0or_dYL9hPus2(}#9Q`6OH-(C~1bpfaRa^!lw zR&ttwsbrKdPRWTXjL*mWv9=mXm2k9KWstMT?Omop{tZf*abjicWw=Mvb{Yl@9w=?- zi@)HQ8g^pgrRReCgJv^xl#Jv@V`y7VRbslGXUF^v&$ce|WiQ=qbkqGdWwg8acD?5@ z_8}MZCkbC_bX_BW#64#*D#AC&Kgj>off~n@tKzS2cD%IfojyZpMv6hji<0&Q{XFw{ zbTyR*)y-V605f_n)QOzmb}8MoL?e-I@*;5`-8VG!I~1<9Rf!p`?+SmdUzgT?H?fs* zxurnkR5E`4twE$|v-xMBpE$$u<~6d~1E*sIEJ|%@mDwl`d!#JB?7Bx@!mFNaIpu&w z_4SaPr}p=h{|Y{^6%VxzmP_{XLDnX8n5fnuE$(wQ$Wekz&>;A;CI9QVrwtiKMP?q@U%H%f^Y6Jqdh}I(A5}b#h{;mgEc-x1>v5 zs0h!r^c7>sqJkgbgHCxdzC5@+Ps7o4h^({hOhcF=Mq^SL?Cjd7W7KiiuGrw(y+~9_ zC@Q6-V5e5Zi!Sm;Ri-}H6_Wenm-qxVuqy3p_pe5}Mjw6Z6t9egp^%(Q3nSb;+vDzC zsxp!;Qlv4Ui#Sk7sdTMJHIRvQv;FPrXoH*s_c`bcXE{yQICE%@%iJX@>Q|0v+lC&?YqF(188wTZRxL6H2mD81@+tG2$Vk>fgedSSqzS_D+fgw87ovXSX*r3ES#iaSWsBziMpqyej+$^ze{b4O53oLR;UG9&cAan z^!CjQyG{nQ)Zgtfj4E3hThKPP&v;w0(RDRtLT4@FIAIc z-y`9-IPB#1{i3u!-Xl3|IQ}&-R4=EsS4*1SPSV#gyuDKE&}h1%3D2eUgs8H^?Tgyu zN*hquqkek><;c(wmd`|;JL9-hWM*-V`J5{*Cx%pY=PT}grDL{WFUzbXhw2=MDQcT#c+TKdJ^VNmQ!V`ZWrAzx<_@z}tDrs$dNJ!jUVzH3F1YbBYkjHW}?RN2^C8T2sZO*M*I0 zoZxv=C3wl4qg0?M(|OXZskffc&5(&-YsoIebX~;_7qe}TF!h6jGRgzdj@y~zvNxIP zYOfZNB2m&Do9i&NtJA5z$3Mg6V5`F2j>qwwtvH1=rCj&AgZ%v6nTVLpPqv+VHmi5C z&oY6t3=an856*u!OX`Tyo9YpcsB+U-|JL=q!Xks5WgI251mEoITuT-jdVqH>(|K3- z!lygy^qF`g#_W4D1FEA%A~YS^)(UUf5E*GC@3%bc|0=icw{1mMTS1NR-f1z|cWL?`=Q!KiHYy z=X<`l%*=bvnY0S5Dfe6K5kPsOjnwC!^qb^&M=4qL@j{XmH2p_KM&|Tb7y?fS_92>H zK$v5j`U$Onu!xU|8$IC`kOKkyp?j&pRB^jWN9h`=A-JI>m$Mok+R`UE`#$8w`UrEt zRWysk{T!E)AW(i%tw1T0(CKuX#o&PzAZgr?zwod(-aU+T3~26s6m)kQsLTe-$8JA; z-)-+3RCBp3Tq1sghboYt7+R$lDHz**>8-vSrL|$W*GT)bQHV3_H5OmgAWD^U(5b@0Lx2uT8 zSgBk$)E}7w11Q~D(%5Z7T4~yqY{3vPrbYU)fz6c zM=w~hpQeeS^pjjCa(Ct~s`4TXDFPy3AweB>4$wa8$x7cu9h9v(0;aE00D+Oe*Cn=Z zX}9P^twj1HI5JyXF%Z6cBO7MXo46Rm;9;Q2D0jU+WmGaTJHaSU> zH-Ul{4IehwLh+t87;%=M6n)#Uu&@JCzAKfw!`K%i^eb;7IkoG?o9hDzj!W+b%gHM0 zeOKHm=Rvd>RlkM5Pk>Vd;3*5s?9?;@)OVFT&PD2onKC~$pQ!J9q$A-~pV+{Oy*$_M zz<%}#HeiWhf2scYO^D4!j`kc}k@t9Gfpbz^t3*Q;FgNiEC*MtDAh3;;O zuK-bN%5e#BP-UUR@lXzBYTYS$Rl<^QB(jyY85rI^N(G!vSrRO`&kHP&SxFgSE1Y(d z!ye}w_BNQ`BmG^kj+?~1()bu8t`bL)d5+-I0eeJAPux)33R@WbQrjiO?VkO>{at6; zf!PvloIUOn=TlyjSJD=&5cE8kD}c<6yJzT@)NJ)}NC*m~o-YN-MZfLO$<{(uGP48H zNr8LKXlI=^E=D}yh}B*H?{qYLGnE(;GBbOZ%{8gHzGnaD(KFEwc=seYT0@3&M`kR1 ziaSMJ?`g!6P>rH?H_M1DCXk9P0|Hm#uBsQ@*GZPE?IKdK4aZ*k!qXq!uHFiboRRja za{QiPVW%Ws10!CoAODpp>I2*_?sgkBTXT^;CKIM|GT2cw*O|j_77Nh4Fly}LlRw9o zErD{7V1MEVzYRh4WZ~32=dEo5eTQ3YcLi4_8nt_5^QKiWnLmTZxc_;?8z%(QL?BSs zGSA{W1hxivyk*X}oSYU;T1ta3`Awt%P#7gwPj!)MGKnkz9UdPZ$4B*2vj}R0TUkq` z#8BwjQ*>q(_>)Jry44}hU$Vjb)$1;(6ob4_dNWBYepd~9r&+C-09*!2>xMZ1k3&Mf zIs@}QRyy^eo-6v3r-W$$&v+HuiF?T`2K<}qc4|d(1W^X3)?;4-$O>+Wl$+Y(Umbb@ zWhj!*32Qv#Ygy~o{bsn*a46l%g4opc9}SxLLjyfCdnN;qR*YOQT+aNOeWqvMCQIC; zRTGZ~C!)F9N<{ER3);j)A7KFtU4K`A{VwHv?E;g#pK^5D8$LM@yiy%a0<;MOOMR#2 zlR2$gf&Pu;B5dz3iWEpu_!D5()^aTtVTD7SGpPI}7DC9=BAjyTv8d#jE2()>K8{Bz zT8{;n4qBYwn{o4@n`wb z;Mc23Oj1`)YiYF^-HA&7?K4Nz!}Q6;;U{HIbKk^9TiE@+nt0>W&99E8U@HfN9ORxn z2S}0f#`#8=+R25B(~N}hvbD=coN>!_C(4Jg{$19p=QdJ$_?f=ZWiRFbTqETM@AjHQZz2W*1p6 zc0W4~hhI4yw;&OR&tQuA12X>Djwb^LV?W0_IwE+$FRxQalTG-s8?7bN#YD=#ltql01_7V))&HcaT8Vh zSa!uAI^C2o74cw?nY$0*27LArn<^NqvlE@8|ET_=Qt>F-1wS-TNJ0D+H@=-S;hY{slp?>JW$^F z2di34P2_ZpQ-0{J(kPkw2ZCC`u#}Z6xsaClYwy`&oQ;BELbxi>f#H ze=0x)r~nn90#twsP=T-&C@d&Q6y@b5iVF)9Mb+V@<^6wY+&EDnul%6`R3Kgo6bskI z`T14cH)?dYFkdSCUo6p8qLs7@4U%XN@O$#gA1Xiv;-P@Yy>MOBv?CpGX{* zsF3(V;t$eBj1d06k8we=s3=h=?FE0R02PR?0wuzIQaElUryz_EmDnkvQp8vC{vv4u zn#(JGKe{K*l2d^kR{;Dbh5trnWng)T!~uy^?CbBpByB(*t_2YMp#oGOS_&mGoSqnh5HW!u#fiPxI{OJ=$t%DN(FLUfjo@a(eD=&v=VmzD3M8DU(@e= zC7;ue{O7o#c>0l10IXI=sk|x@Ok{90#v|V1(J;#B~H)x$pSuM_W3%p zk2Zq*yIbWf85QspXfEudk4DJ7zJHb1IT9K5@v_Dz-S3fqdW;GW^3qZ@YSbvzrBf#*Yy48R0p#EDx@YlnTLEuPzx4ak24Fq^M2SDAV;_0o z{bzUItu}7lsCxG3p$hWy$bW9zh-V)`1+cCc{L6Rwz9n4WC2_*UKKlNfh7VVtfBw1J zxpSxLam5v?ko-rm_F3dyRsidJu&y^>zM~_|-z0(W@TBU;z4y`Y-!O2X+OlPfs;sP3 zJ9g|)SCao+wh_-gf(m%o`|^FB!ekQc_m;qS_l)dc*Q=M>uwjEbb?THldGaLrk6_)i z$hoZm*5V?Bcf=JE+fD4_`u>W`FIVrp^Nz>is)}`HLks znApdd|B{O?Qh!*!{51RIKZ2FdBImRMxW|umy}~=9wZyxbu-~zLd-d|mFMo}F@}JXI z;>kx$0We=wHP46n{&M;EYbN$F&)>RLD>ZN4Ja4QI=Jnw9f5c3{;^wRZ;2$CDd$F!} zaVG4yXx2>4nKMTnJ9g}A-1}_+`OjH9@x&vffH&7yHRty|;rwNx6nE#c;U(&IUJbagD^v{2;_U+r3mVNRc5fiYOIjMj@<}b_7KTEhC zEpf`kKJrK1e!I7(7wdcc>-Bo0^@cb>|9{XuY+SiPNejdT3z+5qyOo1WuYyQ4s{^!vEa zFJpj0;Xb2vy)BzJSI^11-gIndl1KjC@c~PcdkT1acvP|9NZ6k%VO-bStVt8~_>388 z*Y#$?K7NP%=bqR0OU;lswYeVMcY zmVY>;39`{K9=Ri8nIo_s>7?JhfoKg3#GVn?U}v;|(4)-U?t%Ki2gM z?}((tizfCl&yRJz&ph*tI)40ko!9oJ>htyY$$z|k$g{Jr0G`nS?!7hrg@p|z{a=}| z-?Y43Juz#Rw^ui~)Hw|+P5$lsgGY?F0{(aTU2A{eu!;Nu<5Uk8%IKW5=ok2M$0(3H!H>9GUTcy?(!4U$5jpzJBDH*;4>}c4A#`gOUv}U`AHPHX ziczq0balwV<8??cn3)!Tl(H8ETt@BMQ8 zcoY9w(a^OzWqATKhW%}KN&HsgYPpU)N7_(tOn~cp{P^tWwQY;{dr5%%wEe5(JL}7m z;$k&!+_#a?jP+3{2UU}seh35*O&2m{&md#zi*P?FFm(LRZ@w^-FnjT|;B-YTh zXaV-Ih6dx!$a)bR{rgXU>h0HyF?MZuwEep%ANCX~I;DQUY?*4^s+G6zV3hS|*(1fy9GMsR zF6R1VJ{Qlx`hAq^uTbXD2lnr;KKbO6$guCX4abfhQ?s9ZQZ;YZjBP-K`=5yM^S@Hw z;njQP+$!nHh?UMMZ^rYW8e}wF@T4n|-?*_?meOQW{jgg%HEZTfwR`vO$o2brdxACqdyl>T`s-@w zkRj?kS+{_;%|BOHv}ZZo;lNZE@QN}Npfdte{?WA(cBS~Y0kKyR)J^Ho_3 zDZ?<~2O$FX?!S zVjkKu|J-n0aPZ*4tZSa_I_L3g&)vHB-cwgyd8KJi>7`WsqwjCuwyj#XZk^{x_|k=l zBGYv#vkRKD3~qS8HuZo$Wa5Mg3UlMB7?<}~q{;*T9on^X;`{P-;fH^FB6edW)~UHp zF*e3Eq+uQ)<=h|nH)~8EQX^MAK>q1XSG~z{h3`%BAHK%)F*SD81LU9Hbk&&-SOdnHYS3N-f z=}lL?$#RA7P4XYU#`G~YcGUyqpWbxUn=DuO-X#CwYfK+gV^=*u{^?Cuy~%Qg?@jU_ zzQ*)1HFnhl>K>q1XSG~z{h3`%BAHK%)F*SD81LU9H zbk&&-S zOdnHYS3N-fbI+T#c|KHKe%Dpcvt0l1Jo*2J7P;VyBS(&?gYtY^t|VE z&w@7Ql@u4N|9s*J?>X=`&yUBdeP71@zxJ9%*=MNl-^`t>%1cWXp52`4x$Z&V$C#o| z@7`+1jveIR@xC8ECjYs`KgIz2_U=`~h7R?{|3Nvg>F>~#g2>1++6!9w+5W+1dZ+R{!t&x}u^&jTt>!73SyH z%QXP{{&sEJsMT-0!M@+YHs7!JGhfMnZt{Ll~@_0a}^|A`YODBkmrmY>Lf zwCd~3_PZ|s#u$CZ^yv!o^)+43XZjx6k8YPigjx@}JxMZ{NOM^}6=j zI?V-^CX;IRlTUKKKVs~Y|J-HYA2*==e}4Xa)wH}k&|Dzq{QC9nt9I?$MgGY@=X@jP z0bB#@-@jjt95JHSV*p(9pWC8^dU44T&iO^G-u3q1UlW?H)OXn71E)-+dYL5B58D z=%C(y>n+xMgzKI2f6?iu!yHq=2Ooa$f$G}1bH?`rA9&yab^Q2oj`_$x$Nv%bfZwiQ zE@0NonJQUSl=glg`u-l>yQ|HcH?!}Lc)hd#XaBGBkMHgLs864?bAkBo&~tOcKT0Ft0!P{`bmQu}}WVeogHA@1YHN`st^=IUej8h`j^xo}bUx==+ELfAs4xEDkXjxO?|* zb;qbtYTmqgWItwoKYT;}$-k-n*tc(=!hS)f-(!CCyhQ$^S7)|4!#Ke9F?l>Yz9j#4 zbf%xFvx^=e|MaDczGRu~_>%nF(V2dx&Mta@{L_~%`jTa`<4f{yM`!w(I=kor@=ss7 z=u4K#jxWi7Fr9Hv4BwN1eqnkQpO53@@;x|TgD`c*XM6Q2T8Dicmp-5T*Pef9wr$%s zwP?{Ib^oMEYV1AtgckVx#lQQV+Ahb#ab5CG*k!@_V%^c(Z@;acl;;bKyZ7GEbjD}n zndNWEeunsdW3Fh8^W^@&nY97vgW<;oFT9|7UU{V|lYMZoA4Y!T#-Rm1AN%cGbM@8U zadG@y7z@CcpMLtOx_9hY)#m*3z3(B0sWUzs-*d*X@La@An>OV{8$kXuV;_Cp(W6Jz z^v51kEoIMpaOr<9-o`7AhrRZnm^CZ*YjNSp4I4Ii`;cMJ5rhqmaSR*_`yBMS?mD$@ z-MZMf0pve3{-M+2#f#OsEn8;1KW=?{7QRFquwcQ0*!mJ<8+_kj#4Wd2Z)5fPLVG)S z(4g2q*NFU^`G+U*e5*daduP>?@F#rPyH~H+`Vw<+zm@MA;+n^1zpu0Jr}13W=bn2m zwrwrOwB+B+KRmf?*)r9vNt3L55=@{R%a$&Ug#cz#caHyt-W#ZU~fOf zxX!td|IGM*^wCGN&c4q8+5pT|qg@Jn4&LVHgE8D!xlEoVuJ1eOpSSH-l;N_jUA=uz z$bVstb1?8?-1q-Q8~Mxe_?`f3rrqr-G)*za5?R`XT>j&B?!6Bi&O$)8yZd&U*cjf3xP~->i}D zsi0}{Z%1dne#pOBbMkN2NcU9GH2JrqvtB>s->f-9ta&6<;c zvqrk7f~Lv89i8?1A^&F0$-h}6-BUr+0 z@^98i_f*g{`M0C9UO(jDtU38NYovQBXqx=n(OIt_@^99h{F^n>Jry)f{_W_j*AMwO zYfk>n8tI-2nkN5tbk^&K{F^l=|7MMJPX$ere>*zs^+W#6nv;LCM!Khhrpdn@o%Q-5 z|7Oj}zgZ*QQ$f?@-;U0D{g8jN=H%b3k?yIWY4UGJXT5&NzgctgZ`MfnRM0f}x1+OO zKjh!6Ir%qhqErs6*Nu$?dYu65BWE1PX5gr>7ELjCjWMH z*6WA-n>8o@W{q@D1x=HGJ38z2L;lU0lYg^Dx~GDs$-f<)_4*>Wco(h^K|8{iN>xcZCH7EaOjdV{1 zO_P5+I_vdA{>_?`f3rrqr-G)*za5?R`XT>j&B?!6Bi&O$)8yZd&U*cjf3xP~->i}D zsi0}{Z%1dne#pOBbMkN2NcU9GH2JrqvtB>s->f-9ta&6<;c zvqrk7f~Lv89i8?1A^&F0$-h}6-BUr+0 z@^98i_f*g{`M0C9UO(jDtU38NYovQBXqx=n(OIt_@^99h{F^n>Jry)f{_W_j*AMwO zYfk>n8tI-2nkN5tbk^&K{F^l=|7MMJPX$ere>*zs^+W#6nv;LCM!Khhrpdn@o%Q-5 z|7Oj}zgZ*QQ$f?@-;U0D{g8jN<{tm|-KX*!H&(@kg;|fn{Csu)q)7_p((5wnX`lc0 z?b@lLf`Y8;j52g=-(J1Hetqn;_Ps*>&HPtZR;ph={ICLduJAu&`t;a&5?ZCSr-W)rv$-kL zbm)*8Hgu>e$ji&R)+j^2>#tW|e)*+B+2Ww_ndiX%-`ozM?$IxGzx;Alm}OtWlRdh3 zSD$_MSu8yXKR)%;Qz}_hly&|w)|)wVW^DU@v<2kf%s+HO8}RJ2&#JPLlB`{u;(8Qg zkU3J0xb{alukG8ns~`30lhrW*+Sy*$T%)#Z*%Di=$$w_-`}`j`a6tWH+&EQITx|XN z6xX5{_fMQKK^;7JF!r8YwQ7~Rv};$d4X`~I2==>OdZ}8ua%HUhe)69=|9%^=Z{I%k z*kg~Wi!Qjp8wcQ?0Q?eG;Pc`C3p#XA(`C+Y|Ni~4W*<8HTEF$?n`+37H>#%P<(|G_ zYVChE+Smd8`l&T**5riF9RJt84LE-MxO(rs_Y~&fM~)bw1`isf1`Qlo?=kp>8`Q{K zZt;$@cI{e)J?f3eacxlFfpLCCMTO@@9Lqm;F1@0?^Y3#UK76>|HTLTa z_Z^@sj+JX|?6e0TG+ocGOm*KIW77_@USoZX*GQOngI~f5)3I|cpE?KYuTuGh{eS)H znM!BAPn})#0QsjcUGyc(WXG4}-;U1oGj(>+1LU8+bkUbAlO11@e>*zU&(zsP50HQQ z(nVjgOm=)p{_W^YKT~HHJwX2HOBa2~GTHGZ`M0Ap{Y;%*^Z@y%FJ1H{%Vfuwl(jURoadiUyO;{R3QyfWQ00M2XKQVkj~K;8V4pQxK}x`{yz=mFO~Agtn< z)~UGG-hW)uZ)*Sk#^2yMAb18Io}rQrsG=jPDu=qxLe{c5~km0J~g5T z;^Tq0iwX;iii?Vhg#AB~f3|=0K+bsJE2-;I5{c@#PF@d4uzjZn^g#SQ@Pb^|e^2`V zg!KQ3;*yfHg#9}uD%b|F4WtKR?SU1-d`pjepMQBRDJb}s)cXw*n^^y`u7CFL@$cVj zs$Ov1h#{+*8jz5sNRmS@b z(C-)J=O>cMq{qIhzn^2@PXAe}&rY8|HIY6)kv{+LiNut2AMjU0{+|u`$Lg5B)A09G z4f#@CY0uA)FY=`Qdyyw?zQ~g{U*t)fztfPv+K@lhkT2P3k8j9_Jn8-(@}$d$Jn8Zw zPrCe_hWyor{Hca~WT!d4clv4az0;4x_o}os(<6m zmnr^7pP5np?}n>WWK_TX?WyVW&q|-oTq-~Xr~nn90#twsPys4H1*iZOpaN8Y3Qz$m SKn17(6>wF7sp$)L*8c+|_-z6J From 43a880775b74d35a425f890ed76de5af6c8036e2 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 14 Sep 2023 03:06:12 +0900 Subject: [PATCH 141/355] =?UTF-8?q?[Refactor]=20Main=20Window:=20=5FprintT?= =?UTF-8?q?oTextbox.=20textbox=E3=81=B8=E3=81=AE=E5=87=BA=E5=8A=9B?= =?UTF-8?q?=E9=96=A2=E6=95=B0=E3=81=AE=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?= =?UTF-8?q?=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0=E3=80=82=20view.py?= =?UTF-8?q?=E3=81=A7=E3=81=AF=E3=82=BF=E3=83=BC=E3=82=B2=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=82=BF=E3=82=A4=E3=83=97=E3=81=A8=E3=83=A1=E3=83=83=E3=82=BB?= =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=82=92=E6=B8=A1=E3=81=99=E3=81=A0=E3=81=91?= =?UTF-8?q?=E3=81=AB=E3=81=97=E3=81=A6=E3=80=81vrct=5Fgui=E5=86=85?= =?UTF-8?q?=E3=81=A7widget=E3=81=AE=E5=8F=96=E5=BE=97=E3=81=A8=E3=80=81all?= =?UTF-8?q?=E3=82=BF=E3=83=96=E3=81=B8=E3=81=AE=E5=87=BA=E5=8A=9B=E8=87=AA?= =?UTF-8?q?=E5=8B=95=E8=A8=AD=E5=AE=9A=E3=82=92=E8=A1=8C=E3=81=86=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 21 +++++++++++++++------ vrct_gui/_printToTextbox.py | 2 +- vrct_gui/vrct_gui.py | 21 +++++++++++++++++++-- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/view.py b/view.py index 568a4bae..e7f72a0e 100644 --- a/view.py +++ b/view.py @@ -437,8 +437,11 @@ class View(): @staticmethod def _printToTextbox_Info(info_message): - vrct_gui.printToTextbox(vrct_gui.textbox_all, info_message, "", "INFO") - vrct_gui.printToTextbox(vrct_gui.textbox_system, info_message, "", "INFO") + vrct_gui.printToTextbox( + target_type="INFO", + original_message=info_message, + translated_message="", + ) @@ -447,8 +450,11 @@ class View(): @staticmethod def _printToTextbox_Sent(original_message, translated_message): - vrct_gui.printToTextbox(vrct_gui.textbox_all, original_message, translated_message, "SEND") - vrct_gui.printToTextbox(vrct_gui.textbox_sent, original_message, translated_message, "SEND") + vrct_gui.printToTextbox( + target_type="SEND", + original_message=original_message, + translated_message=translated_message, + ) def printToTextbox_ReceivedMessage(self, original_message, translated_message): @@ -456,8 +462,11 @@ class View(): @staticmethod def _printToTextbox_Received(original_message, translated_message): - vrct_gui.printToTextbox(vrct_gui.textbox_all, original_message, translated_message, "RECEIVE") - vrct_gui.printToTextbox(vrct_gui.textbox_received, original_message, translated_message, "RECEIVE") + vrct_gui.printToTextbox( + target_type="RECEIVE", + original_message=original_message, + translated_message=translated_message, + ) @staticmethod diff --git a/vrct_gui/_printToTextbox.py b/vrct_gui/_printToTextbox.py index 88bcc87b..9becc64d 100644 --- a/vrct_gui/_printToTextbox.py +++ b/vrct_gui/_printToTextbox.py @@ -1,7 +1,7 @@ from datetime import datetime from customtkinter import CTkFont -def _printToTextbox(settings, target_textbox, original_message, translated_message, tags=None): +def _printToTextbox(settings, target_textbox, original_message=None, translated_message=None, tags=None): now_raw_data = datetime.now() now = now_raw_data.strftime('%H:%M:%S') now_hm = now_raw_data.strftime('%H:%M') diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 36287308..f525ff4a 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -125,13 +125,30 @@ class VRCT_GUI(CTk): target_names=target_names, ) - def printToTextbox(self, target_textbox, original_message, translated_message, tags=None): + def printToTextbox(self, target_type, original_message=None, translated_message=None): + match (target_type): + case "INFO": + target_textbox = self.textbox_system + case "SEND": + target_textbox = self.textbox_sent + case "RECEIVE": + target_textbox = self.textbox_received + case (_): + raise ValueError(f"No matching case for target_type: {target_type}") _printToTextbox( settings=self.settings.main, target_textbox=target_textbox, original_message=original_message, translated_message=translated_message, - tags=tags, + tags=target_type, + ) + # To automatically print the same log to the textbox_all widget as well. + _printToTextbox( + settings=self.settings.main, + target_textbox=self.textbox_all, + original_message=original_message, + translated_message=translated_message, + tags=target_type, ) def setDefaultActiveLanguagePresetTab(self, tab_no:str): From 6af238b754f8e5df150cd97a32b342984e5d5303 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 14 Sep 2023 04:13:11 +0900 Subject: [PATCH 142/355] [Chore] Config Window: change the config window title to Settings for the production env. --- vrct_gui/config_window/ConfigWindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index cbfcea6e..8e0a3155 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -13,7 +13,7 @@ class ConfigWindow(CTkToplevel): # configure window self.after(200, lambda: self.iconbitmap(getImagePath("vrct_logo_mark_black.ico"))) - self.title("test config_window.py") + self.title("Settings") self.geometry(f"{1080}x{680}") From f27e4985cddcf435da9ee7c79be0b69621a5955b Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 14 Sep 2023 05:13:47 +0900 Subject: [PATCH 143/355] =?UTF-8?q?[Refactor]=20Main=20Window:=20=5FprintT?= =?UTF-8?q?oTextbox.=20textbox=E3=81=B8=E3=81=AE=E5=87=BA=E5=8A=9B?= =?UTF-8?q?=E9=96=A2=E6=95=B0=E3=81=AE=E3=83=AA=E3=83=95=E3=82=A1=E3=82=AF?= =?UTF-8?q?=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B02=20textbox=20widget?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=82=92=5FprintToTextbox=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E5=86=85=E3=81=A7=E8=A1=8C=E3=81=86=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_printToTextbox.py | 15 ++++++++++++++- vrct_gui/vrct_gui.py | 15 ++++----------- 2 files changed, 18 insertions(+), 12 deletions(-) diff --git a/vrct_gui/_printToTextbox.py b/vrct_gui/_printToTextbox.py index 9becc64d..41258793 100644 --- a/vrct_gui/_printToTextbox.py +++ b/vrct_gui/_printToTextbox.py @@ -1,7 +1,20 @@ from datetime import datetime from customtkinter import CTkFont -def _printToTextbox(settings, target_textbox, original_message=None, translated_message=None, tags=None): +def _printToTextbox(vrct_gui, settings, target_type, original_message=None, translated_message=None, tags=None): + match (target_type): + case "ALL": + target_textbox = vrct_gui.textbox_all + case "INFO": + target_textbox = vrct_gui.textbox_system + case "SEND": + target_textbox = vrct_gui.textbox_sent + case "RECEIVE": + target_textbox = vrct_gui.textbox_received + case (_): + raise ValueError(f"No matching case for target_type: {target_type}") + + now_raw_data = datetime.now() now = now_raw_data.strftime('%H:%M:%S') now_hm = now_raw_data.strftime('%H:%M') diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index f525ff4a..f181115f 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -126,26 +126,19 @@ class VRCT_GUI(CTk): ) def printToTextbox(self, target_type, original_message=None, translated_message=None): - match (target_type): - case "INFO": - target_textbox = self.textbox_system - case "SEND": - target_textbox = self.textbox_sent - case "RECEIVE": - target_textbox = self.textbox_received - case (_): - raise ValueError(f"No matching case for target_type: {target_type}") _printToTextbox( + vrct_gui=self, settings=self.settings.main, - target_textbox=target_textbox, + target_type=target_type, original_message=original_message, translated_message=translated_message, tags=target_type, ) # To automatically print the same log to the textbox_all widget as well. _printToTextbox( + vrct_gui=self, settings=self.settings.main, - target_textbox=self.textbox_all, + target_type="ALL", original_message=original_message, translated_message=translated_message, tags=target_type, From f4026816ee7d970b8532d86f71e0b72833bdfd73 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 14 Sep 2023 16:32:10 +0900 Subject: [PATCH 144/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20main=20Add=20cal?= =?UTF-8?q?lback=20ConfigWindow=20Open/Close?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 57 ++++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 17 deletions(-) diff --git a/main.py b/main.py index d02f00a3..9e9c9333 100644 --- a/main.py +++ b/main.py @@ -49,6 +49,18 @@ def stopTranscriptionSendMessage(): model.stopMicTranscript() view.setMainWindowAllWidgetsStatusToNormal() +def startThreadingTranscriptionSendMessage(): + view.printToTextbox_enableTranscriptionSend() + th_startTranscriptionSendMessage = Thread(target=startTranscriptionSendMessage) + th_startTranscriptionSendMessage.daemon = True + th_startTranscriptionSendMessage.start() + +def stopThreadingTranscriptionSendMessage(): + view.printToTextbox_disableTranscriptionSend() + th_stopTranscriptionSendMessage = Thread(target=stopTranscriptionSendMessage) + th_stopTranscriptionSendMessage.daemon = True + th_stopTranscriptionSendMessage.start() + # func transcription receive message def receiveSpeakerMessage(message): if len(message) > 0: @@ -83,6 +95,18 @@ def stopTranscriptionReceiveMessage(): model.stopSpeakerTranscript() view.setMainWindowAllWidgetsStatusToNormal() +def startThreadingTranscriptionReceiveMessage(): + view.printToTextbox_enableTranscriptionReceive() + th_startTranscriptionReceiveMessage = Thread(target=startTranscriptionReceiveMessage) + th_startTranscriptionReceiveMessage.daemon = True + th_startTranscriptionReceiveMessage.start() + +def stopThreadingTranscriptionReceiveMessage(): + view.printToTextbox_disableTranscriptionReceive() + th_stopTranscriptionReceiveMessage = Thread(target=stopTranscriptionReceiveMessage) + th_stopTranscriptionReceiveMessage.daemon = True + th_stopTranscriptionReceiveMessage.start() + # func message box def sendChatMessage(message): if len(message) > 0: @@ -184,29 +208,17 @@ def callbackToggleTranscriptionSend(is_turned_on): view.setMainWindowAllWidgetsStatusToDisabled() config.ENABLE_TRANSCRIPTION_SEND = is_turned_on if config.ENABLE_TRANSCRIPTION_SEND is True: - view.printToTextbox_enableTranscriptionSend() - th_startTranscriptionSendMessage = Thread(target=startTranscriptionSendMessage) - th_startTranscriptionSendMessage.daemon = True - th_startTranscriptionSendMessage.start() + startThreadingTranscriptionSendMessage() else: - view.printToTextbox_disableTranscriptionSend() - th_stopTranscriptionSendMessage = Thread(target=stopTranscriptionSendMessage) - th_stopTranscriptionSendMessage.daemon = True - th_stopTranscriptionSendMessage.start() + stopThreadingTranscriptionSendMessage() def callbackToggleTranscriptionReceive(is_turned_on): view.setMainWindowAllWidgetsStatusToDisabled() config.ENABLE_TRANSCRIPTION_RECEIVE = is_turned_on if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - view.printToTextbox_enableTranscriptionReceive() - th_startTranscriptionReceiveMessage = Thread(target=startTranscriptionReceiveMessage) - th_startTranscriptionReceiveMessage.daemon = True - th_startTranscriptionReceiveMessage.start() + startThreadingTranscriptionReceiveMessage() else: - view.printToTextbox_disableTranscriptionReceive() - th_stopTranscriptionReceiveMessage = Thread(target=stopTranscriptionReceiveMessage) - th_stopTranscriptionReceiveMessage.daemon = True - th_stopTranscriptionReceiveMessage.start() + stopThreadingTranscriptionReceiveMessage() def callbackToggleForeground(is_turned_on): config.ENABLE_FOREGROUND = is_turned_on @@ -217,8 +229,19 @@ def callbackToggleForeground(is_turned_on): view.printToTextbox_disableForeground() view.foregroundOff() - # Config Window +def callbackOpenConfigWindow(): + if config.ENABLE_TRANSCRIPTION_SEND is True: + stopThreadingTranscriptionSendMessage() + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + stopThreadingTranscriptionReceiveMessage() + +def callbackCloseConfigWindow(): + if config.ENABLE_TRANSCRIPTION_SEND is True: + startThreadingTranscriptionSendMessage() + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + startThreadingTranscriptionReceiveMessage() + # Compact Mode Switch def callbackEnableConfigWindowCompactMode(): config.IS_CONFIG_WINDOW_COMPACT_MODE = True From bf263c91d593cd0ba98e1dd680f33da7680ec903 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 14 Sep 2023 17:29:50 +0900 Subject: [PATCH 145/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20model=20?= =?UTF-8?q?=E5=89=8A=E9=99=A4=E3=81=99=E3=82=8B=E5=A4=89=E6=95=B0=E3=82=92?= =?UTF-8?q?=E9=96=93=E9=81=95=E3=81=88=E3=81=A6=E3=81=84=E3=81=9F=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit del self.translator -> del self.keyword_processor --- model.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/model.py b/model.py index 46ac13ea..ceef0686 100644 --- a/model.py +++ b/model.py @@ -67,7 +67,7 @@ class Model: self.translator = Translator() def resetKeywordProcessor(self): - del self.translator + del self.keyword_processor self.keyword_processor = KeywordProcessor() def authenticationTranslator(self, fnc, choice_translator=None, auth_key=None): From af1396876d130b09003daadb0b76d5e050951578 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 14 Sep 2023 17:32:18 +0900 Subject: [PATCH 146/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20config=20config.?= =?UTF-8?q?json=E3=81=8CUnicode=E3=82=A8=E3=82=B9=E3=82=B1=E3=83=BC?= =?UTF-8?q?=E3=83=97=E3=81=A7=E4=BF=9D=E5=AD=98=E3=81=95=E3=82=8C=E3=82=8B?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/config.py b/config.py index a47cd8b6..3c30c647 100644 --- a/config.py +++ b/config.py @@ -12,11 +12,11 @@ from models.transcription.transcription_languages import transcription_lang from models.transcription.transcription_utils import getInputDevices, getOutputDevices, getDefaultInputDevice, getDefaultOutputDevice def saveJson(path, key, value): - with open(path, "r") as fp: + with open(path, "r", encoding="utf-8") as fp: json_data = load(fp) json_data[key] = value - with open(path, "w") as fp: - dump(json_data, fp, indent=4) + with open(path, "w", encoding="utf-8") as fp: + json_dump(json_data, fp, indent=4, ensure_ascii=False) class Config: _instance = None @@ -543,13 +543,13 @@ class Config: def load_config(self): if os_path.isfile(self.PATH_CONFIG) is not False: - with open(self.PATH_CONFIG, 'r') as fp: + with open(self.PATH_CONFIG, 'r', encoding="utf-8") as fp: config = json_load(fp) for key in config.keys(): setattr(self, key, config[key]) - with open(self.PATH_CONFIG, 'w') as fp: + with open(self.PATH_CONFIG, 'w', encoding="utf-8") as fp: setter_methods = [ name for name, obj in vars(type(self)).items() if isinstance(obj, property) and obj.fset is not None @@ -557,6 +557,6 @@ class Config: config = {} for method in setter_methods: config[method] = getattr(self, method) - json_dump(config, fp, indent=4) + json_dump(config, fp, indent=4, ensure_ascii=False) config = Config() \ No newline at end of file From 9aeea214983818674d046b9bc5da1c2b6d4e0664 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 15 Sep 2023 06:43:08 +0900 Subject: [PATCH 147/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20main=20add=20sto?= =?UTF-8?q?p=20check=20Energy=20function=20for=20callbackCloseConfigWindow?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.py b/main.py index 9e9c9333..8ac04026 100644 --- a/main.py +++ b/main.py @@ -241,6 +241,8 @@ def callbackCloseConfigWindow(): startThreadingTranscriptionSendMessage() if config.ENABLE_TRANSCRIPTION_RECEIVE is True: startThreadingTranscriptionReceiveMessage() + model.stopCheckMicEnergy() + model.stopCheckSpeakerEnergy() # Compact Mode Switch def callbackEnableConfigWindowCompactMode(): From cbef891fe8e17c6fd788ca893e5059a4c8a08363 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 15 Sep 2023 09:51:41 +0900 Subject: [PATCH 148/355] =?UTF-8?q?[Update]=20Main=20Window:=20Textbox=20?= =?UTF-8?q?=E3=83=87=E3=82=B6=E3=82=A4=E3=83=B3=E5=A4=A7=E5=B9=85=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=80=82=E3=83=86=E3=82=B9=E3=83=88=E7=94=A8or?= =?UTF-8?q?=E4=BB=8A=E5=BE=8C=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E3=81=8C?= =?UTF-8?q?=E3=83=87=E3=82=B6=E3=82=A4=E3=83=B3=E3=82=AB=E3=82=B9=E3=82=BF?= =?UTF-8?q?=E3=83=A0=E8=A8=AD=E5=AE=9A=E3=81=99=E3=82=8B=E9=9A=9B=E3=81=AB?= =?UTF-8?q?=E4=BD=BF=E3=81=88=E3=82=8B=E3=82=B5=E3=83=B3=E3=83=97=E3=83=AB?= =?UTF-8?q?=E3=81=A8=E3=81=97=E3=81=A6=E4=BC=9A=E8=A9=B1=E3=82=92=E6=8C=BF?= =?UTF-8?q?=E5=85=A5=E3=81=99=E3=82=8B=E9=96=A2=E6=95=B0=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 134 +++++++++++++++++- vrct_gui/_printToTextbox.py | 130 +++++++++++++---- .../main_window/widgets/create_textbox.py | 2 +- vrct_gui/ui_managers/ColorThemeManager.py | 8 ++ vrct_gui/vrct_gui.py | 12 +- 5 files changed, 239 insertions(+), 47 deletions(-) diff --git a/view.py b/view.py index e7f72a0e..d1b8d688 100644 --- a/view.py +++ b/view.py @@ -350,6 +350,7 @@ class View(): self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window_registers.get("callback_set_osc_ip_address", None) self.view_variable.CALLBACK_SET_OSC_PORT = config_window_registers.get("callback_set_osc_port", None) + # self._insertSampleConversationToTextbox() @@ -438,22 +439,23 @@ class View(): @staticmethod def _printToTextbox_Info(info_message): vrct_gui.printToTextbox( - target_type="INFO", + target_type="SYSTEM", original_message=info_message, - translated_message="", + # translated_message="", ) - def printToTextbox_SentMessage(self, original_message, translated_message): - self._printToTextbox_Sent(original_message, translated_message) + def printToTextbox_SentMessage(self, original_message, translated_message, actual_sent_message=None): + self._printToTextbox_Sent(original_message, translated_message, actual_sent_message) @staticmethod - def _printToTextbox_Sent(original_message, translated_message): + def _printToTextbox_Sent(original_message, translated_message, actual_sent_message=None): vrct_gui.printToTextbox( - target_type="SEND", + target_type="SENT", original_message=original_message, translated_message=translated_message, + actual_sent_message=actual_sent_message, ) @@ -463,7 +465,7 @@ class View(): @staticmethod def _printToTextbox_Received(original_message, translated_message): vrct_gui.printToTextbox( - target_type="RECEIVE", + target_type="RECEIVED", original_message=original_message, translated_message=translated_message, ) @@ -626,4 +628,122 @@ class View(): + # These conversation is generated by ChatGPT + def _insertSampleConversationToTextbox(self): + + self.printToTextbox_enableTranscriptionSend() + self.printToTextbox_enableTranscriptionReceive() + + conversation_data_without_translation = [ + { + "me": "おはよう。", + }, + { + "me": "おはよう。", + "target": "やぁ。", + }, + { + "me": "今日の天気はどうかな?", + "target": "天気予報を見てないけど、晴れるといいね。", + }, + { + "me": "そうだね。昨日は雨だったから。", + "target": "それで、今日の予定は?", + }, + ] + + for data in conversation_data_without_translation: + if data.get("me", None) is not None: + self.printToTextbox_SentMessage(data.get("me", None), data.get("me_t", None)) + if data.get("target", None) is not None: + self.printToTextbox_ReceivedMessage(data.get("target", None), data.get("target_t", None)) + + self.printToTextbox_enableTranslation() + + conversation_data = [ + { + "me": "I have work in the morning, but I'm meeting friends for dinner in the evening.", + "me_t": "아침에 일이 있지만 저녁에 친구들과 만나 저녁 식사할 예정이에요.", + "target": "재미있어 보여요! 무엇을 먹을 예정이에요?", + "target_t": "Sounds fun! What are you planning to eat?" + }, + { + "me": "We're going to an Italian restaurant, and I'm going to have pizza.", + "me_t": "우리는 이탈리안 레스토랑에 가서 피자를 먹을 거에요.", + "target": "그걸 듣자마자 배가 고파져요. 언젠가 함께하고 싶어요.", + "target_t": "Just hearing that makes me hungry. I'd love to join you sometime." + }, + { + "me": "Let's plan it for next time!", + "me_t": "다음 번에 계획해 봐요!", + "target": "그래요!", + "target_t": "Sure!" + }, + { + "me": "When would be a good time for you?", + "me_t": "너에게 언제가 좋을까?", + "target": "나는 주말이 가장 좋을 것 같아요. 토요일은 어때요?", + "target_t": "I think the weekend works best for me. How about Saturday?" + }, + { + "me": "Saturday sounds perfect. What time would be convenient?", + "me_t": "토요일이 완벽해 보여. 편한 시간은 언제인가요?", + "target": "저는 저녁이 괜찮아요. 7시쯤 괜찮을까요?", + "target_t": "Evening works for me. Is around 7 PM okay?" + }, + { + "me": "7 PM works great. Do you have any preferences for food other than Italian?", + "me_t": "7시가 아주 적당해. 이탈리안 음식 이외에 어떤 음식을 좋아하세요?", + "target": "특별한 선호도는 없어요. 무엇이든 괜찮아요. 추천 디저트가 있다면 알려주세요.", + "target_t": "I don't have any particular preferences, so anything is fine. If there's a recommended dessert, let me know." + }, + + + { + "me": "朝は仕事があるけど、夜は友達と食事に行く予定だよ。", + "me_t": "I have work in the morning, but I'm meeting friends for dinner in the evening.", + "target": "Sounds fun! What are you planning to eat?", + "target_t": "楽しそう!何を食べる予定?", + }, + { + "me": "イタリアンレストランに行って、ピザを食べるつもりだよ。", + "me_t": "We're going to an Italian restaurant, and I'm going to have pizza.", + "target": "Just hearing that makes me hungry. I'd love to join you sometime.", + "target_t": "それ聞いただけでおなかすいたよ。私も一緒に行きたいな。", + }, + { + "me": "次回にぜひ一緒に行こう!", + "me_t": "Let's plan it for next time!", + "target": "Sure!", + "target_t": "そうだね!", + }, + { + "me": "次回はいつがいいかな?", + "me_t": "When would be a good time for you?", + "target": "I think the weekend works best for me. How about Saturday?", + "target_t": "私は週末が一番いいかな。土曜日はどう?" + }, + { + "me": "土曜日はちょうどいいね。何時ごろが良いかな?", + "me_t": "Saturday sounds perfect. What time would be convenient?", + "target": "Evening works for me. Is around 7 PM okay?", + "target_t": "夜がいいかな。7時くらいからがちょうど良いかな。" + }, + { + "me": "7時からはちょうどいいよ。イタリアン以外の食べ物について何か好みがある?", + "me_t": "7 PM works great. Do you have any preferences for food other than Italian?", + "target": "I don't have any particular preferences, so anything is fine. If there's a recommended dessert, let me know.", + "target_t": "特に好みはないから、何でも大丈夫。おすすめのデザートがあれば教えてね。" + }, + ] + for data in conversation_data: + if data.get("me", None) is not None: + # actual_sent_message = config.MESSAGE_FORMAT.replace("[message]", data.get("me", None)) + # actual_sent_message = actual_sent_message.replace("[translation]", data.get("me_t", None)) + self.printToTextbox_SentMessage(data.get("me", None), data.get("me_t", None)) + # self.printToTextbox_SentMessage(data.get("me", None), data.get("me_t", None), actual_sent_message) + if data.get("target", None) is not None: + self.printToTextbox_ReceivedMessage(data.get("target", None), data.get("target_t", None)) + + view = View() \ No newline at end of file diff --git a/vrct_gui/_printToTextbox.py b/vrct_gui/_printToTextbox.py index 41258793..8854071f 100644 --- a/vrct_gui/_printToTextbox.py +++ b/vrct_gui/_printToTextbox.py @@ -1,48 +1,120 @@ from datetime import datetime from customtkinter import CTkFont -def _printToTextbox(vrct_gui, settings, target_type, original_message=None, translated_message=None, tags=None): +def _printToTextbox(vrct_gui, settings, target_type, original_message=None, translated_message=None, actual_sent_message=None, tags=None, disable_print_to_textbox_all:bool=False): + now_raw_data = datetime.now() + # now = now_raw_data.strftime("%H:%M:%S") + now_hm = now_raw_data.strftime("%H:%M") + # set target textbox widget + + is_only_one_message = True if original_message is None or translated_message is None or translated_message == "" else False + match (target_type): - case "ALL": - target_textbox = vrct_gui.textbox_all - case "INFO": + case "SYSTEM": target_textbox = vrct_gui.textbox_system - case "SEND": + case "SENT": target_textbox = vrct_gui.textbox_sent - case "RECEIVE": + case "RECEIVED": target_textbox = vrct_gui.textbox_received case (_): raise ValueError(f"No matching case for target_type: {target_type}") - now_raw_data = datetime.now() - now = now_raw_data.strftime('%H:%M:%S') - now_hm = now_raw_data.strftime('%H:%M') + def printEachTextbox(target_textbox): + target_textbox.tag_config("JUSTIFY_CENTER", justify="center") + target_textbox.tag_config("JUSTIFY_RIGHT", justify="right") + target_textbox.tag_config("JUSTIFY_LEFT", justify="left") - target_textbox.tag_config("NORMAL_TEXT", foreground=settings.ctm.TEXTBOX_TEXT_COLOR) + # common tag settings + # target_textbox._textbox.tag_configure("START", spacing1=16) + target_textbox._textbox.tag_configure("LABEL", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) + target_textbox._textbox.tag_configure("TIMESTAMP", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal"), foreground=settings.ctm.TEXTBOX_TIMESTAMP_TEXT_COLOR) + target_textbox._textbox.tag_configure("SECONDARY_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) + target_textbox._textbox.tag_configure("MAIN_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=16, weight="normal")) - target_textbox.tag_config("ERROR", foreground="#FF0000") + # System Tag Settings + target_textbox.tag_config("SYSTEM_FOR_FIRST_INSERT", spacing1=16) + target_textbox.tag_config("SYSTEM_TAG", foreground=settings.ctm.TEXTBOX_SYSTEM_TAG_TEXT_COLOR) + target_textbox.tag_config("SYSTEM_TEXT", foreground=settings.ctm.TEXTBOX_TEXT_SUB_COLOR) + target_textbox._textbox.tag_configure("SYSTEM_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) - target_textbox.tag_config("INFO", justify="center") - target_textbox.tag_config("INFO_COLOR", foreground="#1BFF00") + # Sent Tag Settings + target_textbox.tag_config("SENT_FOR_FIRST_INSERT", spacing1=16) + target_textbox.tag_config("SENT_TAG", foreground=settings.ctm.TEXTBOX_SENT_TAG_TEXT_COLOR) + target_textbox.tag_config("SENT_TEXT", foreground=settings.ctm.TEXTBOX_TEXT_COLOR) + target_textbox.tag_config("SENT_SUB_TEXT", foreground=settings.ctm.TEXTBOX_TEXT_SUB_COLOR) + target_textbox._textbox.tag_configure("SENT_MAIN_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=16, weight="normal")) + target_textbox._textbox.tag_configure("SENT_SECONDARY_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) - target_textbox.tag_config("SEND", justify="left") - target_textbox.tag_config("SEND_COLOR", foreground="#0378e2") + # Received Tag Settings + target_textbox.tag_config("RECEIVED_FOR_FIRST_INSERT", spacing1=16) + target_textbox.tag_config("RECEIVED_TAG", foreground=settings.ctm.TEXTBOX_RECEIVED_TAG_TEXT_COLOR) + target_textbox.tag_config("RECEIVED_TEXT", foreground=settings.ctm.TEXTBOX_TEXT_COLOR) + target_textbox.tag_config("RECEIVED_SUB_TEXT", foreground=settings.ctm.TEXTBOX_TEXT_SUB_COLOR) + target_textbox._textbox.tag_configure("RECEIVED_MAIN_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=16, weight="normal")) + target_textbox._textbox.tag_configure("RECEIVED_SECONDARY_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) - target_textbox.tag_config("RECEIVE", justify="left") - target_textbox.tag_config("RECEIVE_COLOR", foreground="#ffa500") + FAKE_MARGIN = " " + # insert + target_textbox.configure(state="normal") + target_textbox.insert("end", "\n") + match (target_type): + case "SYSTEM": + target_textbox.insert("end", "System", ("SYSTEM_TAG", "SYSTEM_FOR_FIRST_INSERT", "JUSTIFY_CENTER")) + target_textbox.insert("end", FAKE_MARGIN+original_message+FAKE_MARGIN, ("SYSTEM_TEXT", "SYSTEM_TEXT_FONT", "JUSTIFY_CENTER")) + target_textbox.insert("end", now_hm, ("TIMESTAMP", "JUSTIFY_CENTER")) - target_textbox._textbox.tag_configure("START", spacing1=10) + case "SENT": + target_textbox.insert("end", now_hm, ("TIMESTAMP", "SENT_FOR_FIRST_INSERT", "JUSTIFY_RIGHT")) + target_textbox.insert("end", FAKE_MARGIN+"Sent", ("SENT_TAG")) + target_textbox.insert("end", "\n") + if is_only_one_message is False: + target_textbox.insert("end", original_message, ("SENT_SUB_TEXT", "SENT_SECONDARY_TEXT_FONT", "JUSTIFY_RIGHT")) + target_textbox.insert("end", "\n") + target_textbox.insert("end", translated_message, ("SENT_TEXT", "SENT_MAIN_TEXT_FONT", "JUSTIFY_RIGHT")) + # _actual_sent_message = "" if actual_sent_message is None else actual_sent_message + # target_textbox.insert("end", _actual_sent_message, ("SENT_TEXT", "SENT_MAIN_TEXT_FONT", "JUSTIFY_RIGHT")) + else: + target_textbox.insert("end", original_message, ("SENT_TEXT", "SENT_MAIN_TEXT_FONT", "JUSTIFY_RIGHT")) - target_textbox._textbox.tag_configure("LABEL", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) - target_textbox._textbox.tag_configure("TIMESTAMP", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) - target_textbox._textbox.tag_configure("ORIGINAL_MESSAGE", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) - target_textbox._textbox.tag_configure("TRANSLATED_MESSAGE", font=CTkFont(family=settings.FONT_FAMILY, size=16, weight="normal")) + case "RECEIVED": + target_textbox.insert("end", "Received", ("RECEIVED_TAG", "RECEIVED_FOR_FIRST_INSERT", "JUSTIFY_LEFT")) + target_textbox.insert("end", FAKE_MARGIN+now_hm, ("TIMESTAMP")) + if is_only_one_message is False: + target_textbox.insert("end", "\n") + target_textbox.insert("end", original_message, ("RECEIVED_SUB_TEXT", "RECEIVED_SECONDARY_TEXT_FONT")) + target_textbox.insert("end", "\n") + target_textbox.insert("end", translated_message, ("RECEIVED_TEXT", "RECEIVED_MAIN_TEXT_FONT", "JUSTIFY_LEFT")) + else: + target_textbox.insert("end", "\n") + target_textbox.insert("end", original_message, ("RECEIVED_TEXT", "RECEIVED_MAIN_TEXT_FONT", "JUSTIFY_LEFT")) - target_textbox.configure(state='normal') - target_textbox.insert("end", f"[{tags}] ", ("START", "LABEL", tags, f"{tags}_COLOR")) - target_textbox.insert("end", f"{now_hm} ", ("TIMESTAMP", tags)) - target_textbox.insert("end", f"{original_message}\n", ("ORIGINAL_MESSAGE", "NORMAL_TEXT", tags)) - target_textbox.insert("end", f"{translated_message}\n", ("TRANSLATED_MESSAGE", "NORMAL_TEXT", tags)) - target_textbox.configure(state='disabled') - target_textbox.see("end") \ No newline at end of file + target_textbox.configure(state="disabled") + target_textbox.see("end") + + printEachTextbox(target_textbox) + + # To automatically print the same log to the textbox_all widget as well. + if disable_print_to_textbox_all is not True: printEachTextbox(vrct_gui.textbox_all) + + + + # target_textbox.tag_config("ERROR", foreground="#FF0000") + + # target_textbox.tag_config("SYSTEM", justify="center") + # target_textbox.tag_config("SYSTEM_TAG", foreground="#1BFF00") + + # target_textbox.tag_config("SENT", justify="left") + # target_textbox.tag_config("SENT_COLOR", foreground="#0378e2") + + # target_textbox.tag_config("RECEIVED", justify="left") + # target_textbox.tag_config("RECEIVED_COLOR", foreground="#ffa500") + + + # target_textbox.configure(state="normal") + # target_textbox.insert("end", f"[{tags}] ", ("START", "LABEL", tags, f"{tags}_COLOR")) + # target_textbox.insert("end", f"{now_hm} ", ("TIMESTAMP", tags)) + # target_textbox.insert("end", f"{original_message}\n", ("SECONDARY_TEXT_FONT", "MAIN_TEXT_COLOR", tags)) + # target_textbox.insert("end", f"{translated_message}\n", ("MAIN_TEXT_FONT", "MAIN_TEXT_COLOR", tags)) + # target_textbox.configure(state="disabled") + # target_textbox.see("end") \ No newline at end of file diff --git a/vrct_gui/main_window/widgets/create_textbox.py b/vrct_gui/main_window/widgets/create_textbox.py index 14a5ba5f..b47d7ac0 100644 --- a/vrct_gui/main_window/widgets/create_textbox.py +++ b/vrct_gui/main_window/widgets/create_textbox.py @@ -145,12 +145,12 @@ def createTextbox(settings, main_window, view_variable): corner_radius=settings.uism.TEXTBOX_CORNER_RADIUS, fg_color=settings.ctm.TEXTBOX_BG_COLOR, text_color="lime", # Textbox's text_color is set when printing. so this is for prevent from non-setting text_color like the gloves used in food factories are blue. + wrap="word", )) textbox_widget = getattr(main_window, textbox_setting["textbox_attr_name"]) textbox_widget.grid(row=0, column=0, padx=settings.uism.TEXTBOX_PADX, pady=0, sticky="nsew") textbox_widget.grid_remove() textbox_widget.configure(state="disabled") - # print_textbox(main_window, textbox_widget, "send", textbox_setting["textbox_attr_name"], "SEND") column+=1 diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 2f980de4..f0e5c6d1 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -38,6 +38,7 @@ class ColorThemeManager(): self.DARK_200_COLOR = "#f1f2f6" self.DARK_300_COLOR = "#e9eaee" self.DARK_400_COLOR = "#c7c8cc" + self.DARK_450_COLOR = "#b8b9bd" self.DARK_500_COLOR = "#a9aaae" self.DARK_600_COLOR = "#7f8084" self.DARK_700_COLOR = "#6a6c6f" @@ -95,6 +96,13 @@ class ColorThemeManager(): self.main.TEXTBOX_BG_COLOR = self.DARK_900_COLOR self.main.TEXTBOX_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + self.main.TEXTBOX_TEXT_SUB_COLOR = self.DARK_450_COLOR + self.main.TEXTBOX_SYSTEM_TAG_TEXT_COLOR = self.PRIMARY_300_COLOR + self.main.TEXTBOX_SENT_TAG_TEXT_COLOR = "#6197b4" + self.main.TEXTBOX_RECEIVED_TAG_TEXT_COLOR = "#a861b4" + self.main.TEXTBOX_ERROR_TAG_TEXT_COLOR = "#c27583" + self.main.TEXTBOX_TIMESTAMP_TEXT_COLOR = self.DARK_600_COLOR + self.main.TEXTBOX_TAB_BG_PASSIVE_COLOR = self.DARK_850_COLOR self.main.TEXTBOX_TAB_BG_ACTIVE_COLOR = self.main.TEXTBOX_BG_COLOR self.main.TEXTBOX_TAB_BG_HOVERED_COLOR = self.DARK_800_COLOR diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index f181115f..ea23f65b 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -125,22 +125,14 @@ class VRCT_GUI(CTk): target_names=target_names, ) - def printToTextbox(self, target_type, original_message=None, translated_message=None): + def printToTextbox(self, target_type, original_message=None, translated_message=None, actual_sent_message=None): _printToTextbox( vrct_gui=self, settings=self.settings.main, target_type=target_type, original_message=original_message, translated_message=translated_message, - tags=target_type, - ) - # To automatically print the same log to the textbox_all widget as well. - _printToTextbox( - vrct_gui=self, - settings=self.settings.main, - target_type="ALL", - original_message=original_message, - translated_message=translated_message, + actual_sent_message=actual_sent_message, tags=target_type, ) From 4c0f356ee195955ff08df31ac34c33b8e81dccd3 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 15 Sep 2023 10:58:33 +0900 Subject: [PATCH 149/355] [Chore] remove the code that is no longer in use. and fixed some comments. --- view.py | 13 +++++-------- vrct_gui/_printToTextbox.py | 28 ++-------------------------- vrct_gui/vrct_gui.py | 5 ++--- 3 files changed, 9 insertions(+), 37 deletions(-) diff --git a/view.py b/view.py index d1b8d688..2015937c 100644 --- a/view.py +++ b/view.py @@ -350,6 +350,7 @@ class View(): self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window_registers.get("callback_set_osc_ip_address", None) self.view_variable.CALLBACK_SET_OSC_PORT = config_window_registers.get("callback_set_osc_port", None) + # Insert sample conversation for testing. # self._insertSampleConversationToTextbox() @@ -446,16 +447,15 @@ class View(): - def printToTextbox_SentMessage(self, original_message, translated_message, actual_sent_message=None): - self._printToTextbox_Sent(original_message, translated_message, actual_sent_message) + def printToTextbox_SentMessage(self, original_message, translated_message): + self._printToTextbox_Sent(original_message, translated_message) @staticmethod - def _printToTextbox_Sent(original_message, translated_message, actual_sent_message=None): + def _printToTextbox_Sent(original_message, translated_message): vrct_gui.printToTextbox( target_type="SENT", original_message=original_message, translated_message=translated_message, - actual_sent_message=actual_sent_message, ) @@ -628,7 +628,7 @@ class View(): - # These conversation is generated by ChatGPT + # These conversations are generated by ChatGPT def _insertSampleConversationToTextbox(self): self.printToTextbox_enableTranscriptionSend() @@ -738,10 +738,7 @@ class View(): ] for data in conversation_data: if data.get("me", None) is not None: - # actual_sent_message = config.MESSAGE_FORMAT.replace("[message]", data.get("me", None)) - # actual_sent_message = actual_sent_message.replace("[translation]", data.get("me_t", None)) self.printToTextbox_SentMessage(data.get("me", None), data.get("me_t", None)) - # self.printToTextbox_SentMessage(data.get("me", None), data.get("me_t", None), actual_sent_message) if data.get("target", None) is not None: self.printToTextbox_ReceivedMessage(data.get("target", None), data.get("target_t", None)) diff --git a/vrct_gui/_printToTextbox.py b/vrct_gui/_printToTextbox.py index 8854071f..cb732f81 100644 --- a/vrct_gui/_printToTextbox.py +++ b/vrct_gui/_printToTextbox.py @@ -1,7 +1,7 @@ from datetime import datetime from customtkinter import CTkFont -def _printToTextbox(vrct_gui, settings, target_type, original_message=None, translated_message=None, actual_sent_message=None, tags=None, disable_print_to_textbox_all:bool=False): +def _printToTextbox(vrct_gui, settings, target_type, original_message=None, translated_message=None, tags=None, disable_print_to_textbox_all:bool=False): now_raw_data = datetime.now() # now = now_raw_data.strftime("%H:%M:%S") now_hm = now_raw_data.strftime("%H:%M") @@ -72,8 +72,6 @@ def _printToTextbox(vrct_gui, settings, target_type, original_message=None, tran target_textbox.insert("end", original_message, ("SENT_SUB_TEXT", "SENT_SECONDARY_TEXT_FONT", "JUSTIFY_RIGHT")) target_textbox.insert("end", "\n") target_textbox.insert("end", translated_message, ("SENT_TEXT", "SENT_MAIN_TEXT_FONT", "JUSTIFY_RIGHT")) - # _actual_sent_message = "" if actual_sent_message is None else actual_sent_message - # target_textbox.insert("end", _actual_sent_message, ("SENT_TEXT", "SENT_MAIN_TEXT_FONT", "JUSTIFY_RIGHT")) else: target_textbox.insert("end", original_message, ("SENT_TEXT", "SENT_MAIN_TEXT_FONT", "JUSTIFY_RIGHT")) @@ -95,26 +93,4 @@ def _printToTextbox(vrct_gui, settings, target_type, original_message=None, tran printEachTextbox(target_textbox) # To automatically print the same log to the textbox_all widget as well. - if disable_print_to_textbox_all is not True: printEachTextbox(vrct_gui.textbox_all) - - - - # target_textbox.tag_config("ERROR", foreground="#FF0000") - - # target_textbox.tag_config("SYSTEM", justify="center") - # target_textbox.tag_config("SYSTEM_TAG", foreground="#1BFF00") - - # target_textbox.tag_config("SENT", justify="left") - # target_textbox.tag_config("SENT_COLOR", foreground="#0378e2") - - # target_textbox.tag_config("RECEIVED", justify="left") - # target_textbox.tag_config("RECEIVED_COLOR", foreground="#ffa500") - - - # target_textbox.configure(state="normal") - # target_textbox.insert("end", f"[{tags}] ", ("START", "LABEL", tags, f"{tags}_COLOR")) - # target_textbox.insert("end", f"{now_hm} ", ("TIMESTAMP", tags)) - # target_textbox.insert("end", f"{original_message}\n", ("SECONDARY_TEXT_FONT", "MAIN_TEXT_COLOR", tags)) - # target_textbox.insert("end", f"{translated_message}\n", ("MAIN_TEXT_FONT", "MAIN_TEXT_COLOR", tags)) - # target_textbox.configure(state="disabled") - # target_textbox.see("end") \ No newline at end of file + if disable_print_to_textbox_all is not True: printEachTextbox(vrct_gui.textbox_all) \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index ea23f65b..25c67b57 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -14,7 +14,7 @@ from .main_window import createMainWindowWidgets from .config_window import ConfigWindow from .ui_utils import _setDefaultActiveTab -from .main_window.widgets import createSidebar, createMinimizeSidebarButton +from .main_window.widgets import createMinimizeSidebarButton class VRCT_GUI(CTk): @@ -125,14 +125,13 @@ class VRCT_GUI(CTk): target_names=target_names, ) - def printToTextbox(self, target_type, original_message=None, translated_message=None, actual_sent_message=None): + def printToTextbox(self, target_type, original_message=None, translated_message=None): _printToTextbox( vrct_gui=self, settings=self.settings.main, target_type=target_type, original_message=original_message, translated_message=translated_message, - actual_sent_message=actual_sent_message, tags=target_type, ) From 34cd261128329b82ae8e400a858cc0f9062d82d0 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 15 Sep 2023 11:40:36 +0900 Subject: [PATCH 150/355] =?UTF-8?q?[Add]=20Config=20Window=E9=96=8B?= =?UTF-8?q?=E9=96=89=E6=99=82=E3=81=AECALLBACK=E9=96=A2=E6=95=B0=E3=81=AE?= =?UTF-8?q?=E5=8F=97=E3=81=91=E5=8F=96=E3=82=8A=E3=80=81=E5=AE=9F=E8=A1=8C?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 5 +++++ view.py | 14 ++++++++++++++ vrct_gui/vrct_gui.py | 5 +++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 8ac04026..81071fc4 100644 --- a/main.py +++ b/main.py @@ -540,6 +540,11 @@ if config.ENABLE_LOGGER is True: # set UI and callback view.register( + window_action_registers={ + "callback_open_config_window": callbackOpenConfigWindow, + "callback_close_config_window": callbackCloseConfigWindow, + }, + sidebar_features_registers={ "callback_toggle_translation": callbackToggleTranslation, "callback_toggle_transcription_send": callbackToggleTranscriptionSend, diff --git a/view.py b/view.py index 2015937c..ea7d1e71 100644 --- a/view.py +++ b/view.py @@ -43,6 +43,11 @@ class View(): ) self.view_variable = SimpleNamespace( + # Open Config Window + CALLBACK_OPEN_CONFIG_WINDOW=None, + CALLBACK_CLOSE_CONFIG_WINDOW=None, + + # Main Window # Sidebar # Sidebar Compact Mode @@ -255,12 +260,21 @@ class View(): def register( self, + window_action_registers=None, sidebar_features_registers=None, language_presets_registers=None, entry_message_box_registers=None, config_window_registers=None ): + + # Open Config Window + if window_action_registers is not None: + self.view_variable.CALLBACK_OPEN_CONFIG_WINDOW=window_action_registers.get("callback_open_config_window", None) + self.view_variable.CALLBACK_CLOSE_CONFIG_WINDOW=window_action_registers.get("callback_close_config_window", None) + + + self.view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = self._toggleMainWindowSidebarCompactMode if sidebar_features_registers is not None: diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 25c67b57..8ac42492 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -1,5 +1,3 @@ -from types import SimpleNamespace - from customtkinter import CTk, CTkImage # from window_help_and_info import ToplevelWindowInformation @@ -16,6 +14,7 @@ from .ui_utils import _setDefaultActiveTab from .main_window.widgets import createMinimizeSidebarButton +from utils import callFunctionIfCallable class VRCT_GUI(CTk): def __init__(self): @@ -54,12 +53,14 @@ class VRCT_GUI(CTk): def openConfigWindow(self, e): + callFunctionIfCallable(self._view_variable.CALLBACK_OPEN_CONFIG_WINDOW) self.config_window.deiconify() self.config_window.focus_set() self.config_window.focus() self.config_window.grab_set() def closeConfigWindow(self): + callFunctionIfCallable(self._view_variable.CALLBACK_CLOSE_CONFIG_WINDOW) self.config_window.withdraw() self.config_window.grab_release() From 41d8e9ca87ec8a8ee7eb7390cb1b2fc4b991cf14 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 15 Sep 2023 13:33:44 +0900 Subject: [PATCH 151/355] =?UTF-8?q?[Refactor]=20view.py=E7=B3=BB=E3=81=AE?= =?UTF-8?q?=E9=96=A2=E6=95=B0=E5=90=8D=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 46 +++++++++++++++++++--------------------------- view.py | 16 ++++++++-------- 2 files changed, 27 insertions(+), 35 deletions(-) diff --git a/main.py b/main.py index 81071fc4..a4f60f3f 100644 --- a/main.py +++ b/main.py @@ -248,18 +248,18 @@ def callbackCloseConfigWindow(): def callbackEnableConfigWindowCompactMode(): config.IS_CONFIG_WINDOW_COMPACT_MODE = True model.stopCheckMicEnergy() - view.replaceConfigWindowMicThresholdCheckButtonToPassive() + view.replaceMicThresholdCheckButton_Passive() model.stopCheckSpeakerEnergy() - view.replaceConfigWindowSpeakerThresholdCheckButtonToPassive() + view.replaceSpeakerThresholdCheckButton_Passive() view.reloadConfigWindowSettingBoxContainer() def callbackDisableConfigWindowCompactMode(): config.IS_CONFIG_WINDOW_COMPACT_MODE = False model.stopCheckMicEnergy() - view.replaceConfigWindowMicThresholdCheckButtonToPassive() + view.replaceMicThresholdCheckButton_Passive() model.stopCheckSpeakerEnergy() - view.replaceConfigWindowSpeakerThresholdCheckButtonToPassive() + view.replaceSpeakerThresholdCheckButton_Passive() view.reloadConfigWindowSettingBoxContainer() @@ -308,14 +308,14 @@ def callbackSetMicHost(value): view.updateList_MicDevice(model.getListInputDevice()) model.stopCheckMicEnergy() - view.replaceConfigWindowMicThresholdCheckButtonToPassive() + view.replaceMicThresholdCheckButton_Passive() def callbackSetMicDevice(value): print("callbackSetMicDevice", value) config.CHOICE_MIC_DEVICE = value model.stopCheckMicEnergy() - view.replaceConfigWindowMicThresholdCheckButtonToPassive() + view.replaceMicThresholdCheckButton_Passive() def callbackSetMicEnergyThreshold(value): print("callbackSetMicEnergyThreshold", value) @@ -344,19 +344,15 @@ def setProgressBarMicEnergy(energy): def callbackCheckMicThreshold(is_turned_on): print("callbackCheckMicThreshold", is_turned_on) if is_turned_on is True: - # view.setConfigWindowCompactModeSwitchStatusToDisabled() - - view.setConfigWindowThresholdCheckWidgetsStatusToDisabled() + view.setWidgetsStatus_ThresholdCheckButton_Disabled() model.startCheckMicEnergy(setProgressBarMicEnergy) - view.replaceConfigWindowMicThresholdCheckButtonToActive() - view.setConfigWindowThresholdCheckWidgetsStatusToNormal() + view.replaceMicThresholdCheckButton_Active() + view.setWidgetsStatus_ThresholdCheckButton_Normal() else: - view.setConfigWindowThresholdCheckWidgetsStatusToDisabled() + view.setWidgetsStatus_ThresholdCheckButton_Disabled() model.stopCheckMicEnergy() - view.replaceConfigWindowMicThresholdCheckButtonToPassive() - view.setConfigWindowThresholdCheckWidgetsStatusToNormal() - - # view.setConfigWindowCompactModeSwitchStatusToNormal() + view.replaceMicThresholdCheckButton_Passive() + view.setWidgetsStatus_ThresholdCheckButton_Normal() def callbackSetMicRecordTimeout(value): print("callbackSetMicRecordTimeout", value) @@ -407,7 +403,7 @@ def callbackSetSpeakerDevice(value): config.CHOICE_SPEAKER_DEVICE = value model.stopCheckSpeakerEnergy() - view.replaceConfigWindowSpeakerThresholdCheckButtonToPassive() + view.replaceSpeakerThresholdCheckButton_Passive() def callbackSetSpeakerEnergyThreshold(value): print("callbackSetSpeakerEnergyThreshold", value) @@ -436,20 +432,16 @@ def setProgressBarSpeakerEnergy(energy): def callbackCheckSpeakerThreshold(is_turned_on): print("callbackCheckSpeakerThreshold", is_turned_on) if is_turned_on is True: - # view.setConfigWindowCompactModeSwitchStatusToDisabled() - - view.setConfigWindowThresholdCheckWidgetsStatusToDisabled() + view.setWidgetsStatus_ThresholdCheckButton_Disabled() model.startCheckSpeakerEnergy(setProgressBarSpeakerEnergy) - view.replaceConfigWindowSpeakerThresholdCheckButtonToActive() - view.setConfigWindowThresholdCheckWidgetsStatusToNormal() + view.replaceSpeakerThresholdCheckButton_Active() + view.setWidgetsStatus_ThresholdCheckButton_Normal() else: - view.setConfigWindowThresholdCheckWidgetsStatusToDisabled() + view.setWidgetsStatus_ThresholdCheckButton_Disabled() model.stopCheckSpeakerEnergy() - view.replaceConfigWindowSpeakerThresholdCheckButtonToPassive() - view.setConfigWindowThresholdCheckWidgetsStatusToNormal() - - # view.setConfigWindowCompactModeSwitchStatusToNormal() + view.replaceSpeakerThresholdCheckButton_Passive() + view.setWidgetsStatus_ThresholdCheckButton_Normal() def callbackSetSpeakerRecordTimeout(value): print("callbackSetSpeakerRecordTimeout", value) diff --git a/view.py b/view.py index ea7d1e71..1bd1d44a 100644 --- a/view.py +++ b/view.py @@ -509,15 +509,15 @@ class View(): # Config Window @staticmethod - def setConfigWindowCompactModeSwitchStatusToDisabled(): + def setWidgetsStatus_ConfigWindowCompactModeSwitch_Disabled(): vrct_gui.config_window.setting_box_compact_mode_switch_box.configure(state="disabled") @staticmethod - def setConfigWindowCompactModeSwitchStatusToNormal(): + def setWidgetsStatus_ConfigWindowCompactModeSwitch_Normal(): vrct_gui.config_window.setting_box_compact_mode_switch_box.configure(state="normal") @staticmethod - def setConfigWindowThresholdCheckWidgetsStatusToDisabled(): + def setWidgetsStatus_ThresholdCheckButton_Disabled(): vrct_gui.changeConfigWindowWidgetsStatus( status="disabled", target_names=[ @@ -527,7 +527,7 @@ class View(): ) @staticmethod - def setConfigWindowThresholdCheckWidgetsStatusToNormal(): + def setWidgetsStatus_ThresholdCheckButton_Normal(): vrct_gui.changeConfigWindowWidgetsStatus( status="normal", target_names=[ @@ -537,24 +537,24 @@ class View(): ) @staticmethod - def replaceConfigWindowMicThresholdCheckButtonToActive(): + def replaceMicThresholdCheckButton_Active(): vrct_gui.config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.grid_remove() vrct_gui.config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold.grid() @staticmethod - def replaceConfigWindowMicThresholdCheckButtonToPassive(): + def replaceMicThresholdCheckButton_Passive(): vrct_gui.config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold.grid_remove() vrct_gui.config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.grid() @staticmethod - def replaceConfigWindowSpeakerThresholdCheckButtonToActive(): + def replaceSpeakerThresholdCheckButton_Active(): vrct_gui.config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.grid_remove() vrct_gui.config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold.grid() @staticmethod - def replaceConfigWindowSpeakerThresholdCheckButtonToPassive(): + def replaceSpeakerThresholdCheckButton_Passive(): vrct_gui.config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold.grid_remove() vrct_gui.config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.grid() From 23b15a8a67dd0202bf1ccb006c3c053435f985f1 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 15 Sep 2023 13:52:54 +0900 Subject: [PATCH 152/355] [bugfix] Config Window: Add functions that replaces the threshold check button's passive/active state within the callbackCloseConfigWindow function(main.py). --- main.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/main.py b/main.py index a4f60f3f..1979f3ee 100644 --- a/main.py +++ b/main.py @@ -242,7 +242,9 @@ def callbackCloseConfigWindow(): if config.ENABLE_TRANSCRIPTION_RECEIVE is True: startThreadingTranscriptionReceiveMessage() model.stopCheckMicEnergy() + view.replaceMicThresholdCheckButton_Passive() model.stopCheckSpeakerEnergy() + view.replaceSpeakerThresholdCheckButton_Passive() # Compact Mode Switch def callbackEnableConfigWindowCompactMode(): From 0ea26a53879b1d7d3c1a5b51a531c5211ec8c247 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 15 Sep 2023 14:42:14 +0900 Subject: [PATCH 153/355] =?UTF-8?q?[bugfix]=20=E8=A8=AD=E5=AE=9A=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E9=96=89=E3=81=98=E3=81=9F=E6=99=82=E3=81=AB=E3=80=81?= =?UTF-8?q?=E3=83=9E=E3=82=A4=E3=82=AF/=E3=82=B9=E3=83=94=E3=83=BC?= =?UTF-8?q?=E3=82=AB=E3=83=BC=20=E3=82=B9=E3=83=AC=E3=83=83=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=AB=E3=83=89=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=8C=E3=81=9D=E3=81=AE=E3=81=BE?= =?UTF-8?q?=E3=81=BE=E3=81=AA=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=20Progre?= =?UTF-8?q?ssBar=E3=81=AB0=E3=82=92=E3=82=BB=E3=83=83=E3=83=88=E3=81=99?= =?UTF-8?q?=E3=82=8B=E9=96=A2=E6=95=B0=E3=82=92view.py=E3=81=AB=E3=81=A8?= =?UTF-8?q?=E3=82=8A=E3=81=82=E3=81=88=E3=81=9A=E8=BF=BD=E5=8A=A0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 3 +++ view.py | 16 ++++++++++++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index 1979f3ee..c09a0755 100644 --- a/main.py +++ b/main.py @@ -243,8 +243,11 @@ def callbackCloseConfigWindow(): startThreadingTranscriptionReceiveMessage() model.stopCheckMicEnergy() view.replaceMicThresholdCheckButton_Passive() + # view.initProgressBar_MicEnergy() # ProgressBarに0をセットしたい + model.stopCheckSpeakerEnergy() view.replaceSpeakerThresholdCheckButton_Passive() + # view.initProgressBar_SpeakerEnergy() # ProgressBarに0をセットしたい # Compact Mode Switch def callbackEnableConfigWindowCompactMode(): diff --git a/view.py b/view.py index 1bd1d44a..00c1bf96 100644 --- a/view.py +++ b/view.py @@ -580,16 +580,28 @@ class View(): def updateSelected_MicDevice(self, default_selected_mic_device_name:str): self.view_variable.VAR_MIC_DEVICE.set(default_selected_mic_device_name) - def updateSetProgressBar_MicEnergy(self, new_mic_energy): + + @staticmethod + def updateSetProgressBar_MicEnergy(new_mic_energy): vrct_gui.config_window.sb__progressbar_x_slider__progressbar_mic_energy_threshold.set(new_mic_energy/config.MAX_MIC_ENERGY_THRESHOLD) + @staticmethod + def initProgressBar_MicEnergy(): + vrct_gui.config_window.sb__progressbar_x_slider__progressbar_mic_energy_threshold.set(0) + + def updateList_SpeakerDevice(self, new_speaker_device_list): self.view_variable.LIST_SPEAKER_DEVICE = new_speaker_device_list vrct_gui.config_window.sb__optionmenu_speaker_device.configure(values=new_speaker_device_list) - def updateSetProgressBar_SpeakerEnergy(self, new_speaker_energy): + @staticmethod + def updateSetProgressBar_SpeakerEnergy(new_speaker_energy): vrct_gui.config_window.sb__progressbar_x_slider__progressbar_speaker_energy_threshold.set(new_speaker_energy/config.MAX_SPEAKER_ENERGY_THRESHOLD) + @staticmethod + def initProgressBar_SpeakerEnergy(): + vrct_gui.config_window.sb__progressbar_x_slider__progressbar_speaker_energy_threshold.set(0) + def setGuiVariable_MicEnergyThreshold(self, slider_value:int, entry_value:Union[None, str]=None): From cdc8495f9947a05fe4c6825afa1f430ccfac34c4 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 15 Sep 2023 15:03:50 +0900 Subject: [PATCH 154/355] =?UTF-8?q?[Update]=20Main=20Window:=20Sidebar=20L?= =?UTF-8?q?anguage=20Settings=E3=81=AEDropdownMenu=E3=83=9C=E3=82=BF?= =?UTF-8?q?=E3=83=B3=E3=81=AE=E3=83=9B=E3=83=90=E3=83=BC=E6=99=82=E3=81=AB?= =?UTF-8?q?=E3=80=81=E3=82=AB=E3=83=BC=E3=82=BD=E3=83=AB=E5=A4=89=E6=9B=B4?= =?UTF-8?q?=E3=82=92=E8=BF=BD=E5=8A=A0=E3=80=82=20[bugfix]=20=E7=9F=A2?= =?UTF-8?q?=E5=8D=B0img=E9=83=A8=E5=88=86=E3=83=9B=E3=83=90=E3=83=BC?= =?UTF-8?q?=E6=99=82=E3=81=AE=E8=89=B2=E5=A4=89=E6=9B=B4=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=81=97=E5=BF=98=E3=82=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_create_sidebar/createSidebarLanguagesSettings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 08dd9c70..5ed03a2c 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -74,7 +74,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - sls__selected_language_box = CTkFrame(sls__box_wrapper, corner_radius=0, fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR) + sls__selected_language_box = CTkFrame(sls__box_wrapper, corner_radius=0, fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, cursor="hand2") sls__selected_language_box.grid(row=1, column=0) @@ -111,8 +111,8 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - bindEnterAndLeaveColor([sls__selected_language_label_frame, sls__selected_language_box, sls__selected_language_label], settings.ctm.SLS__DROPDOWN_MENU_HOVERED_BG_COLOR, settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR) - bindButtonPressColor([sls__selected_language_label_frame, sls__selected_language_box, sls__selected_language_label], settings.ctm.SLS__DROPDOWN_MENU_CLICKED_BG_COLOR, settings.ctm.SLS__DROPDOWN_MENU_HOVERED_BG_COLOR) + bindEnterAndLeaveColor([sls__selected_language_label_frame, sls__selected_language_box, sls__selected_language_label, sls__selected_language_arrow_img], settings.ctm.SLS__DROPDOWN_MENU_HOVERED_BG_COLOR, settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR) + bindButtonPressColor([sls__selected_language_label_frame, sls__selected_language_box, sls__selected_language_label, sls__selected_language_arrow_img], settings.ctm.SLS__DROPDOWN_MENU_CLICKED_BG_COLOR, settings.ctm.SLS__DROPDOWN_MENU_HOVERED_BG_COLOR) From 5d7c34a6bfe53cee44763ea7d9bc9d34f91dccf2 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 15 Sep 2023 17:50:14 +0900 Subject: [PATCH 155/355] =?UTF-8?q?[bugfix](Not=20enough)=20Sidebar=20Lang?= =?UTF-8?q?uage=20Settings=20DropdownMenu=E3=83=9C=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E8=87=AA=E4=BD=93=E3=82=92=E6=8A=BC=E3=81=97=E3=81=9F=E6=99=82?= =?UTF-8?q?=E3=81=AE=E9=96=8B=E9=96=89=E3=81=8C=E6=A9=9F=E8=83=BD=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=81=AA=E3=81=8B=E3=81=A3=E3=81=9F=E3=81=AE?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82=E3=81=9F=E3=81=A0=E7=84=A1?= =?UTF-8?q?=E7=90=86=E3=82=84=E3=82=8A=E3=81=AA=E3=81=86=E3=81=88=E3=81=AB?= =?UTF-8?q?=E6=A0=B9=E6=9C=AC=E7=9A=84=E3=81=AA=E8=A7=A3=E6=B1=BA=E3=81=A7?= =?UTF-8?q?=E3=81=AF=E3=81=AA=E3=81=84=E3=81=AE=E3=81=A7=E3=80=81=E9=95=B7?= =?UTF-8?q?=E6=8A=BC=E3=81=97=E3=81=A0=E3=81=A3=E3=81=9F=E3=82=8A=E3=80=81?= =?UTF-8?q?=E6=97=A9=E3=81=84=E6=93=8D=E4=BD=9C=E3=81=A0=E3=81=A3=E3=81=9F?= =?UTF-8?q?=E3=82=8A=E3=81=99=E3=82=8B=E3=81=A8=E7=8F=BE=E7=8A=B6=E4=BA=88?= =?UTF-8?q?=E6=9C=9F=E3=81=9B=E3=81=AC=E5=8B=95=E4=BD=9C=E3=81=AF=E8=B5=B7?= =?UTF-8?q?=E3=81=93=E3=82=8B=E3=80=82(=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=81=8C=E5=87=BA=E3=82=8B=E3=82=8F=E3=81=91=E3=81=A7=E3=81=AF?= =?UTF-8?q?=E3=81=AA=E3=81=84=E8=A6=8B=E3=81=9F=E7=9B=AE=E3=81=AE=E5=95=8F?= =?UTF-8?q?=E9=A1=8C=E3=80=82=E9=96=8B=E9=96=89=E3=81=8C=E6=AD=A3=E3=81=97?= =?UTF-8?q?=E3=81=8F=E8=A1=8C=E3=82=8F=E3=82=8C=E3=81=AA=E3=81=84=E3=81=AA?= =?UTF-8?q?=E3=81=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/vrct_gui.py | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 8ac42492..383bf4cd 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -68,6 +68,9 @@ class VRCT_GUI(CTk): def openSelectableLanguagesWindow(self, selectable_language_window_type): + # print("___________________________________open____________________________________________________") + # print("your", self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW) + # print("target", self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW) if selectable_language_window_type == "your_language": if self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW is False: self.sls__arrow_img_your_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT),size=(20,20))) @@ -97,8 +100,16 @@ class VRCT_GUI(CTk): self.sls__arrow_img_your_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT).rotate(180),size=(20,20))) self.sls__arrow_img_target_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT).rotate(180),size=(20,20))) self.selectable_languages_window.withdraw() - self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW = False - self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW = False + + + # print("______________________________________close_________________________________________________") + # print("your", self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW) + # print("target", self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW) + if self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW is not False or self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW is not False: + def callback(): + self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW = False + self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW = False + self.after(500,callback) From 4b0b573645d5614c5dfb58d8663168eda6ae0af9 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 16 Sep 2023 03:03:52 +0900 Subject: [PATCH 156/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20=E7=BF=BB?= =?UTF-8?q?=E8=A8=B3=E3=82=A8=E3=83=B3=E3=82=B8=E3=83=B3=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=97=E3=81=9F=E6=99=82=E3=81=AB=E5=B8=B8=E3=81=AB?= =?UTF-8?q?model.authenticationTranslator(...)=E3=82=92=E8=A1=8C=E3=81=86?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit エンジン変更時にmodel.translator.translator_statusのステータスを変更する必要があった --- main.py | 11 ++++++++ models/translation/translation_translator.py | 28 ++++++++++---------- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/main.py b/main.py index c09a0755..d4ec38ff 100644 --- a/main.py +++ b/main.py @@ -162,6 +162,7 @@ def setYourLanguageAndCountry(select): config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) def setTargetLanguageAndCountry(select): languages = config.SELECTED_TAB_TARGET_LANGUAGES @@ -171,6 +172,7 @@ def setTargetLanguageAndCountry(select): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) def callbackSelectedLanguagePresetTab(selected_tab_no): config.SELECTED_TAB_NO = selected_tab_no @@ -186,6 +188,7 @@ def callbackSelectedLanguagePresetTab(selected_tab_no): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) def callbackSetAuthKeys(keys): config.AUTH_KEYS = keys @@ -299,7 +302,13 @@ def callbackSetDeeplAuthkey(value): print("callbackSetDeeplAuthkey", str(value)) if len(value) > 0 and model.authenticationTranslator(callbackSetAuthKeys, choice_translator="DeepL(auth)", auth_key=value) is True: config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) view.printToTextbox_AuthenticationSuccess() + elif len(value) == 0: + auth_keys = config.AUTH_KEYS + auth_keys["DeepL(auth)"] = None + config.AUTH_KEYS = auth_keys + model.authenticationTranslator(callbackSetAuthKeys) else: view.printToTextbox_AuthenticationError() @@ -521,6 +530,8 @@ view.createGUI() if model.authenticationTranslator(callbackSetAuthKeys) is False: # error update Auth key view.printToTextbox_AuthenticationError() + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) # set word filter model.addKeywords() diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index b386835e..da4f911f 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -13,23 +13,23 @@ class Translator(): def authentication(self, translator_name, authkey=None): result = False - try: - if translator_name == "DeepL(web)": - self.translator_status["DeepL(web)"] = True - result = True - elif translator_name == "DeepL(auth)": + if translator_name == "DeepL(web)": + self.translator_status[translator_name] = True + result = True + elif translator_name == "DeepL(auth)": + try: self.deepl_client = deepl_Translator(authkey) self.deepl_client.translate_text(" ", target_lang="EN-US") - self.translator_status["DeepL(auth)"] = True + self.translator_status[translator_name] = True result = True - elif translator_name == "Google(web)": - self.translator_status["Google(web)"] = True - result = True - elif translator_name == "Bing(web)": - self.translator_status["Bing(web)"] = True - result = True - except: - pass + except: + self.translator_status[translator_name] = False + elif translator_name == "Google(web)": + self.translator_status[translator_name] = True + result = True + elif translator_name == "Bing(web)": + self.translator_status[translator_name] = True + result = True return result def translate(self, translator_name, source_language, target_language, message): From 5a95fccd3754db8bb8532fbcc4cecf3f79c1124e Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 16 Sep 2023 17:37:26 +0900 Subject: [PATCH 157/355] =?UTF-8?q?[Update]=20Config=20Window:=20=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E7=94=BB=E9=9D=A2=E3=81=AE=E9=81=B8=E6=8A=9E=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=9F=E3=82=BF=E3=83=96=E6=83=85=E5=A0=B1=E3=82=92?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E3=80=82=E8=A8=AD=E5=AE=9A=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E9=96=8B=E9=96=89=E3=80=81=E3=82=B3=E3=83=B3=E3=83=91=E3=82=AF?= =?UTF-8?q?=E3=83=88=E3=83=A2=E3=83=BC=E3=83=89=E5=88=87=E3=82=8A=E6=9B=BF?= =?UTF-8?q?=E3=81=88=E6=99=82=E3=81=A7=E3=82=82=E5=89=8D=E5=9B=9E=E9=96=8B?= =?UTF-8?q?=E3=81=84=E3=81=A6=E3=81=84=E3=81=9F=E3=82=BF=E3=83=96=E3=81=8C?= =?UTF-8?q?=E9=81=B8=E6=8A=9E=E3=81=95=E3=82=8C=E3=81=9F=E7=8A=B6=E6=85=8B?= =?UTF-8?q?=E3=81=A7=E8=A1=A8=E7=A4=BA=E3=81=95=E3=82=8C=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB(VRCT=E5=86=8D=E8=B5=B7=E5=8B=95=E3=81=A7?= =?UTF-8?q?=E5=88=9D=E6=9C=9F=E8=A1=A8=E7=A4=BA=E3=82=BF=E3=83=96=E3=81=B8?= =?UTF-8?q?=E3=81=A8=E3=83=AA=E3=82=BB=E3=83=83=E3=83=88)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 7 +++++++ .../_addConfigSideMenuItem.py | 6 ++++-- .../createSideMenuAndSettingsBoxContainers.py | 4 ++-- 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/view.py b/view.py index 00c1bf96..a5553f61 100644 --- a/view.py +++ b/view.py @@ -100,6 +100,8 @@ class View(): # Config Window + ACTIVE_SETTING_BOX_TAB_ATTR_NAME="side_menu_tab_appearance", + CALLBACK_SELECTED_SETTING_BOX_TAB=None, # Appearance Tab VAR_LABEL_TRANSPARENCY=StringVar(value="Transparency"), VAR_DESC_TRANSPARENCY=StringVar(value="Change the window's transparency. 50% to 100%. (Default: 100%)"), @@ -309,6 +311,8 @@ class View(): # Config Window + self.view_variable.CALLBACK_SELECTED_SETTING_BOX_TAB=self._updateActiveSettingBoxTabNo + # Compact Mode Switch if config_window_registers is not None: @@ -508,6 +512,9 @@ class View(): # Config Window + def _updateActiveSettingBoxTabNo(self, active_setting_box_tab_attr_name:str): + self.view_variable.ACTIVE_SETTING_BOX_TAB_ATTR_NAME = active_setting_box_tab_attr_name + @staticmethod def setWidgetsStatus_ConfigWindowCompactModeSwitch_Disabled(): vrct_gui.config_window.setting_box_compact_mode_switch_box.configure(state="disabled") diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py index cb2f79b2..22e9d372 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py @@ -2,9 +2,10 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor +from utils import callFunctionIfCallable -def _addConfigSideMenuItem(config_window, settings, side_menu_settings, side_menu_row, all_side_menu_tab_attr_name): +def _addConfigSideMenuItem(config_window, settings, view_variable, side_menu_settings, side_menu_row, all_side_menu_tab_attr_name): def switchActiveAndPassiveSettingBoxContainerTabsColor(target_active_widget): @@ -51,11 +52,12 @@ def _addConfigSideMenuItem(config_window, settings, side_menu_settings, side_men def switchToTargetSettingBoxContainer(e, text, target_active_tab_widget_attr_name, target_setting_box_container_attr_name): - print("switchToTargetSettingBoxContainer", target_setting_box_container_attr_name) config_window.main_current_active_config_title.configure(text=text) target_active_tab_widget = getattr(config_window, target_active_tab_widget_attr_name) switchSettingBoxContainerTabFunction(target_active_tab_widget) switchSettingBoxContainer(target_setting_box_container_attr_name) + callFunctionIfCallable(view_variable.CALLBACK_SELECTED_SETTING_BOX_TAB, target_active_tab_widget_attr_name) + diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index 32c1156e..390220d4 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -59,7 +59,6 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl { "section_title": None, "setting_box": createSettingBox_Appearance }, ] }, - "activate_by_default": True, }, { "side_menu_tab_attr_name": "side_menu_tab_translation", @@ -119,6 +118,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl _addConfigSideMenuItem( config_window=config_window, settings=settings, + view_variable=view_variable, # view_variable=view_variable, side_menu_settings=sm_and_sbc_setting, side_menu_row=side_menu_row, @@ -136,7 +136,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl ) - if sm_and_sbc_setting.get("activate_by_default", None) is not None: + if sm_and_sbc_setting["side_menu_tab_attr_name"] == view_variable.ACTIVE_SETTING_BOX_TAB_ATTR_NAME: # Set default active side menu tab config_window.main_current_active_config_title.configure(text=sm_and_sbc_setting["text"]) config_window.current_active_side_menu_tab = getattr(config_window, sm_and_sbc_setting["side_menu_tab_attr_name"]) From 5f0cca062d7906a038f839af383a25051f0c16c7 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 16 Sep 2023 20:34:25 +0900 Subject: [PATCH 158/355] [Update] Config Window: Add chackboxes ENABLE_OSC and ENABLE_OSC_ERROR_LOG to setting box. --- view.py | 13 +++++++++ .../createSettingBox_Others.py | 29 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/view.py b/view.py index a5553f61..5fa0b13f 100644 --- a/view.py +++ b/view.py @@ -246,6 +246,19 @@ class View(): VAR_MESSAGE_FORMAT=StringVar(value=config.MESSAGE_FORMAT), + VAR_LABEL_ENABLE_OSC=StringVar(value="Send Message To VRChat"), + VAR_DESC_ENABLE_OSC=StringVar(value="There is a way to use it without sending messages to VRChat."), + CALLBACK_SET_ENABLE_OSC=None, + VAR_ENABLE_OSC=BooleanVar(value=config.ENABLE_OSC), + + VAR_LABEL_ENABLE_OSC_ERROR_LOG=StringVar(value="Ignore The OSC Error Message"), + VAR_DESC_ENABLE_OSC_ERROR_LOG=StringVar(value="Remember to turn on OSC yourself when you want to use it and send messages to VRChat."), + CALLBACK_SET_ENABLE_OSC_ERROR_LOG=None, + VAR_ENABLE_OSC_ERROR_LOG=BooleanVar(value=config.ENABLE_OSC_ERROR_LOG), + + + + # Advanced Settings Tab VAR_LABEL_OSC_IP_ADDRESS=StringVar(value="OSC IP Address"), VAR_DESC_OSC_IP_ADDRESS=StringVar(value="(Default: 127.0.0.1)"), diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index 311fbbbd..ad39496b 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -18,6 +18,12 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v def checkbox_auto_export_message_logs_callback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, checkbox_box_widget.get()) + def checkbox_enable_osc_callback(checkbox_box_widget): + callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_OSC, checkbox_box_widget.get()) + + def checkbox_enable_osc_error_log_callback(checkbox_box_widget): + callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_OSC_ERROR_LOG, checkbox_box_widget.get()) + def entry_message_format_callback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, value) @@ -69,4 +75,27 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v entry_textvariable=view_variable.VAR_MESSAGE_FORMAT, ) config_window.sb__message_format.grid(row=row) + row+=1 + + + config_window.sb__enable_osc = createSettingBoxCheckbox( + parent_widget=setting_box_wrapper, + for_var_label_text=view_variable.VAR_LABEL_ENABLE_OSC, + for_var_desc_text=view_variable.VAR_DESC_ENABLE_OSC, + checkbox_attr_name="sb__checkbox_enable_osc", + command=lambda: checkbox_enable_osc_callback(config_window.sb__checkbox_enable_osc), + variable=view_variable.VAR_ENABLE_OSC, + ) + config_window.sb__enable_osc.grid(row=row) + row+=1 + + config_window.sb__enable_osc_error_log = createSettingBoxCheckbox( + parent_widget=setting_box_wrapper, + for_var_label_text=view_variable.VAR_LABEL_ENABLE_OSC_ERROR_LOG, + for_var_desc_text=view_variable.VAR_DESC_ENABLE_OSC_ERROR_LOG, + checkbox_attr_name="sb__checkbox_enable_osc_error_log", + command=lambda: checkbox_enable_osc_error_log_callback(config_window.sb__checkbox_enable_osc_error_log), + variable=view_variable.VAR_ENABLE_OSC_ERROR_LOG, + ) + config_window.sb__enable_osc_error_log.grid(row=row) row+=1 \ No newline at end of file From 7ebc06d049926afe3fcb2ce02cd2a3fd8a15f707 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 17 Sep 2023 03:58:41 +0900 Subject: [PATCH 159/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20main=20configWin?= =?UTF-8?q?dow=20open/close=E6=99=82=E3=81=AB=E6=9C=80=E5=89=8D=E9=9D=A2?= =?UTF-8?q?=E8=A1=A8=E7=A4=BAON=E3=81=AE=E5=A0=B4=E5=90=88=E3=81=AE?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/main.py b/main.py index d4ec38ff..1a5eca28 100644 --- a/main.py +++ b/main.py @@ -238,6 +238,8 @@ def callbackOpenConfigWindow(): stopThreadingTranscriptionSendMessage() if config.ENABLE_TRANSCRIPTION_RECEIVE is True: stopThreadingTranscriptionReceiveMessage() + if config.ENABLE_FOREGROUND is True: + view.foregroundOff() def callbackCloseConfigWindow(): if config.ENABLE_TRANSCRIPTION_SEND is True: @@ -252,6 +254,9 @@ def callbackCloseConfigWindow(): view.replaceSpeakerThresholdCheckButton_Passive() # view.initProgressBar_SpeakerEnergy() # ProgressBarに0をセットしたい + if config.ENABLE_FOREGROUND is True: + view.foregroundOn() + # Compact Mode Switch def callbackEnableConfigWindowCompactMode(): config.IS_CONFIG_WINDOW_COMPACT_MODE = True From b31cfc0487cf5c06e645926d1845867ecb7ea196 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 17 Sep 2023 07:38:51 +0900 Subject: [PATCH 160/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20model=20stopChec?= =?UTF-8?q?kMicEnergy/stopCheckSpeakerEnergy=E3=81=AE=E7=B5=82=E4=BA=86?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92stopMicTranscript/stopSpeakerTranscr?= =?UTF-8?q?ipt=E3=81=AE=E5=87=A6=E7=90=86=E3=81=A8=E5=90=8C=E3=81=98?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/model.py b/model.py index ceef0686..b6964143 100644 --- a/model.py +++ b/model.py @@ -281,10 +281,11 @@ class Model: self.mic_energy_plot_progressbar.start() def stopCheckMicEnergy(self): + if isinstance(self.mic_energy_plot_progressbar, threadFnc): + self.mic_energy_plot_progressbar.stop() if self.mic_energy_recorder != None: self.mic_energy_recorder.stop() - if self.mic_energy_plot_progressbar != None: - self.mic_energy_plot_progressbar.stop() + self.mic_energy_recorder.stop = None def startSpeakerTranscript(self, fnc): spk_audio_queue = Queue() @@ -346,10 +347,11 @@ class Model: self.speaker_energy_plot_progressbar.start() def stopCheckSpeakerEnergy(self): + if isinstance(self.speaker_energy_plot_progressbar, threadFnc): + self.speaker_energy_plot_progressbar.stop() if self.speaker_energy_recorder != None: self.speaker_energy_recorder.stop() - if self.speaker_energy_plot_progressbar != None: - self.speaker_energy_plot_progressbar.stop() + self.speaker_energy_recorder.stop != None def notificationXSOverlay(self, message): xsoverlayForVRCT(content=f"{message}") From e84764dcd904530772acc161191fd3e6fcb78946 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 17 Sep 2023 07:40:07 +0900 Subject: [PATCH 161/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20main=20config.EN?= =?UTF-8?q?ABLE=5FTRANSCRIPTION=5FSEND=20is=20True=20=E3=81=8B=E3=81=A4=20?= =?UTF-8?q?config.ENABLE=5FTRANSCRIPTION=5FRECEIVE=20is=20True=E3=81=AE?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=AB=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9?= =?UTF-8?q?=E3=83=89=E3=83=A9=E3=82=A4=E3=83=90=E3=81=AE=E3=82=A2=E3=82=AF?= =?UTF-8?q?=E3=82=BB=E3=82=B9=E5=B9=B2=E6=B8=89=E3=81=8C=E8=B5=B7=E3=81=8D?= =?UTF-8?q?=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=ABNOP=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/main.py b/main.py index 1a5eca28..696b8c64 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +from time import sleep from threading import Thread from config import config from model import model @@ -242,18 +243,19 @@ def callbackOpenConfigWindow(): view.foregroundOff() def callbackCloseConfigWindow(): - if config.ENABLE_TRANSCRIPTION_SEND is True: - startThreadingTranscriptionSendMessage() - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - startThreadingTranscriptionReceiveMessage() model.stopCheckMicEnergy() + model.stopCheckSpeakerEnergy() view.replaceMicThresholdCheckButton_Passive() # view.initProgressBar_MicEnergy() # ProgressBarに0をセットしたい - - model.stopCheckSpeakerEnergy() view.replaceSpeakerThresholdCheckButton_Passive() # view.initProgressBar_SpeakerEnergy() # ProgressBarに0をセットしたい + if config.ENABLE_TRANSCRIPTION_SEND is True: + startThreadingTranscriptionSendMessage() + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + sleep(2) + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + startThreadingTranscriptionReceiveMessage() if config.ENABLE_FOREGROUND is True: view.foregroundOn() From 7a8aa701d2355dc5835e53a410f0fd549f6d567a Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 17 Sep 2023 08:34:19 +0900 Subject: [PATCH 162/355] =?UTF-8?q?[Update]=20=E5=A4=89=E6=95=B0=E5=90=8D?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E3=81=A8=E3=81=9D=E3=82=8C=E3=81=AB=E5=90=88?= =?UTF-8?q?=E3=82=8F=E3=81=9B=E3=81=A6UI=E3=81=AE=E6=96=87=E8=A8=80?= =?UTF-8?q?=E8=AA=BF=E6=95=B4=20ENABLE=5FOSC=5FERROR=5FLOG=20->=20STARTUP?= =?UTF-8?q?=5FOSC=5FENABLED=5FCHECK=20VRCT=E8=B5=B7=E5=8B=95=E6=99=82?= =?UTF-8?q?=E3=81=ABOSC=E3=81=8C=E6=9C=89=E5=8A=B9=E3=81=8B=E3=81=A9?= =?UTF-8?q?=E3=81=86=E3=81=8B=E3=81=AE=E3=83=81=E3=82=A7=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=A2=E3=82=AF=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=92=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=81=8B=E3=81=A9=E3=81=86=E3=81=8B=E3=81=A8=E3=81=84?= =?UTF-8?q?=E3=81=86=E6=84=8F=E5=91=B3=E3=81=AB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 13 ++++++------ main.py | 9 ++++++-- view.py | 12 ++++++----- .../createSettingBox_Others.py | 21 ++++++++++--------- 4 files changed, 32 insertions(+), 23 deletions(-) diff --git a/config.py b/config.py index 3c30c647..8055a675 100644 --- a/config.py +++ b/config.py @@ -389,13 +389,14 @@ class Config: self._ENABLE_OSC = value @property - def ENABLE_OSC_ERROR_LOG(self): - return self._ENABLE_OSC_ERROR_LOG + def STARTUP_OSC_ENABLED_CHECK(self): + return self._STARTUP_OSC_ENABLED_CHECK - @ENABLE_OSC_ERROR_LOG.setter - def ENABLE_OSC_ERROR_LOG(self, value): + @STARTUP_OSC_ENABLED_CHECK.setter + def STARTUP_OSC_ENABLED_CHECK(self, value): if type(value) is bool: - self._ENABLE_OSC_ERROR_LOG = value + self._STARTUP_OSC_ENABLED_CHECK = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property def UPDATE_FLAG(self): @@ -516,7 +517,7 @@ class Config: self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = False self._ENABLE_NOTICE_XSOVERLAY = False self._ENABLE_OSC = False - self._ENABLE_OSC_ERROR_LOG = True + self._STARTUP_OSC_ENABLED_CHECK = True self._UPDATE_FLAG = False self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" # self._BREAK_KEYSYM_LIST = [ diff --git a/main.py b/main.py index 1a5eca28..ddcf1468 100644 --- a/main.py +++ b/main.py @@ -32,7 +32,7 @@ def sendMicMessage(message): osc_message = message model.oscSendMessage(osc_message) else: - if config.ENABLE_OSC_ERROR_LOG is True: + if config.STARTUP_OSC_ENABLED_CHECK is True: view.printToTextbox_OSCError() view.printToTextbox_SentMessage(message, translation) @@ -131,7 +131,7 @@ def sendChatMessage(message): osc_message = message model.oscSendMessage(osc_message) else: - if config.ENABLE_OSC_ERROR_LOG is True: + if config.STARTUP_OSC_ENABLED_CHECK is True: view.printToTextbox_OSCError() # update textbox message log @@ -516,6 +516,9 @@ def callbackSetMessageFormat(value): if len(value) > 0: config.MESSAGE_FORMAT = value +def callbackSetStartupOscEnabledCheck(value): + print("callbackSetStartupOscEnabledCheck", value) + config.STARTUP_OSC_ENABLED_CHECK = value # Advanced Settings Tab def callbackSetOscIpAddress(value): @@ -622,6 +625,8 @@ view.register( "callback_set_enable_auto_export_message_logs": callbackSetEnableAutoExportMessageLogs, "callback_set_message_format": callbackSetMessageFormat, + "callback_set_startup_osc_enabled_check": callbackSetStartupOscEnabledCheck, + # Advanced Settings Tab "callback_set_osc_ip_address": callbackSetOscIpAddress, "callback_set_osc_port": callbackSetOscPort, diff --git a/view.py b/view.py index 5fa0b13f..9c69c50c 100644 --- a/view.py +++ b/view.py @@ -251,10 +251,10 @@ class View(): CALLBACK_SET_ENABLE_OSC=None, VAR_ENABLE_OSC=BooleanVar(value=config.ENABLE_OSC), - VAR_LABEL_ENABLE_OSC_ERROR_LOG=StringVar(value="Ignore The OSC Error Message"), - VAR_DESC_ENABLE_OSC_ERROR_LOG=StringVar(value="Remember to turn on OSC yourself when you want to use it and send messages to VRChat."), - CALLBACK_SET_ENABLE_OSC_ERROR_LOG=None, - VAR_ENABLE_OSC_ERROR_LOG=BooleanVar(value=config.ENABLE_OSC_ERROR_LOG), + VAR_LABEL_STARTUP_OSC_ENABLED_CHECK=StringVar(value="Check If OSC Is Enabled At Startup"), + VAR_DESC_STARTUP_OSC_ENABLED_CHECK=StringVar(value="Every time VRCT is started up, your character in VRChat moves forward ever so slightly. If your character is seated, they might even stand up. Unfortunately, this is the only method we've found to check if OSC is enabled at startup... Sorry about that. (Remember to turn on OSC yourself when you want to send messages to VRChat.)"), + CALLBACK_SET_STARTUP_OSC_ENABLED_CHECK=None, + VAR_STARTUP_OSC_ENABLED_CHECK=BooleanVar(value=config.STARTUP_OSC_ENABLED_CHECK), @@ -377,6 +377,8 @@ class View(): self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS = config_window_registers.get("callback_set_enable_auto_export_message_logs", None) self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window_registers.get("callback_set_message_format", None) + self.view_variable.CALLBACK_SET_STARTUP_OSC_ENABLED_CHECK = config_window_registers.get("callback_set_startup_osc_enabled_check", None) + # Advanced Settings Tab self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window_registers.get("callback_set_osc_ip_address", None) self.view_variable.CALLBACK_SET_OSC_PORT = config_window_registers.get("callback_set_osc_port", None) @@ -462,7 +464,7 @@ class View(): self._printToTextbox_Info("Auth Key is incorrect or Usage limit reached") def printToTextbox_OSCError(self): - self._printToTextbox_Info("OSC is not enabled, please enable OSC and rejoin") + self._printToTextbox_Info("OSC is not enabled, please enable OSC and rejoin. or turn off the \"Send Message To VRChat\" setting") def printToTextbox_DetectedByWordFilter(self, detected_message): self._printToTextbox_Info(f"Detect WordFilter :{detected_message}") diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index ad39496b..2c257f64 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -21,8 +21,8 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v def checkbox_enable_osc_callback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_OSC, checkbox_box_widget.get()) - def checkbox_enable_osc_error_log_callback(checkbox_box_widget): - callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_OSC_ERROR_LOG, checkbox_box_widget.get()) + def checkbox_startup_osc_enabled_check_callback(checkbox_box_widget): + callFunctionIfCallable(view_variable.CALLBACK_SET_STARTUP_OSC_ENABLED_CHECK, checkbox_box_widget.get()) def entry_message_format_callback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, value) @@ -89,13 +89,14 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v config_window.sb__enable_osc.grid(row=row) row+=1 - config_window.sb__enable_osc_error_log = createSettingBoxCheckbox( + config_window.sb__startup_osc_enabled_check = createSettingBoxCheckbox( parent_widget=setting_box_wrapper, - for_var_label_text=view_variable.VAR_LABEL_ENABLE_OSC_ERROR_LOG, - for_var_desc_text=view_variable.VAR_DESC_ENABLE_OSC_ERROR_LOG, - checkbox_attr_name="sb__checkbox_enable_osc_error_log", - command=lambda: checkbox_enable_osc_error_log_callback(config_window.sb__checkbox_enable_osc_error_log), - variable=view_variable.VAR_ENABLE_OSC_ERROR_LOG, + for_var_label_text=view_variable.VAR_LABEL_STARTUP_OSC_ENABLED_CHECK, + for_var_desc_text=view_variable.VAR_DESC_STARTUP_OSC_ENABLED_CHECK, + checkbox_attr_name="sb__checkbox_startup_osc_enabled_check", + command=lambda: checkbox_startup_osc_enabled_check_callback(config_window.sb__checkbox_startup_osc_enabled_check), + variable=view_variable.VAR_STARTUP_OSC_ENABLED_CHECK, ) - config_window.sb__enable_osc_error_log.grid(row=row) - row+=1 \ No newline at end of file + config_window.sb__startup_osc_enabled_check.grid(row=row) + row+=1 + From 5e7174501debe7ad20832dbe6b38b454f434b3d1 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 17 Sep 2023 09:24:27 +0900 Subject: [PATCH 163/355] =?UTF-8?q?[refactor]=20Main=20Window:=20Sidebar?= =?UTF-8?q?=E9=96=8B=E9=96=89=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=92=E9=96=8B?= =?UTF-8?q?=E9=96=89=E6=99=82=E3=81=AB=E6=AF=8E=E5=9B=9Edestroy=E3=81=97?= =?UTF-8?q?=E3=81=A6widget=E5=86=8D=E7=94=9F=E6=88=90=E3=81=97=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=81=9F=E3=81=AE=E3=82=92=E3=80=81grid=5Fremove?= =?UTF-8?q?=E3=81=A8grid=E3=81=AB=E7=BD=AE=E3=81=8D=E6=8F=9B=E3=81=88?= =?UTF-8?q?=E3=81=A6=E3=80=81=E5=86=8D=E7=94=9F=E6=88=90=E3=81=97=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_changeMainWindowWidgetsStatus.py | 22 +++--- .../widgets/create_minimize_sidebar_button.py | 67 +++++++++++++------ vrct_gui/vrct_gui.py | 7 +- 3 files changed, 62 insertions(+), 34 deletions(-) diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index c2350182..eeea8cee 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -115,24 +115,26 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta case "minimize_sidebar_button": - LOGO_SIZE = vrct_gui.minimize_sidebar_button.cget("image").cget("size") + MINIMIZE_SIDEBAR_IMAGE_SIZE = vrct_gui.minimize_sidebar_button__for_opening.cget("image").cget("size") if status == "disabled": - vrct_gui.minimize_sidebar_button_container.configure(cursor="") - + vrct_gui.minimize_sidebar_button_container__for_opening.configure(cursor="") + vrct_gui.minimize_sidebar_button_container__for_closing.configure(cursor="") if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - image_file = CTkImage((settings.image_file.ARROW_LEFT_DISABLED).rotate(180), size=LOGO_SIZE) + image_file = CTkImage((settings.image_file.ARROW_LEFT_DISABLED).rotate(180), size=MINIMIZE_SIDEBAR_IMAGE_SIZE) else: - image_file = CTkImage((settings.image_file.ARROW_LEFT_DISABLED), size=LOGO_SIZE) - vrct_gui.minimize_sidebar_button.configure(image=image_file) + image_file = CTkImage((settings.image_file.ARROW_LEFT_DISABLED), size=MINIMIZE_SIDEBAR_IMAGE_SIZE) elif status == "normal": - vrct_gui.minimize_sidebar_button_container.configure(cursor="hand2") + vrct_gui.minimize_sidebar_button_container__for_opening.configure(cursor="hand2") + vrct_gui.minimize_sidebar_button_container__for_closing.configure(cursor="hand2") + if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - image_file = CTkImage((settings.image_file.ARROW_LEFT).rotate(180), size=LOGO_SIZE) + image_file = CTkImage((settings.image_file.ARROW_LEFT).rotate(180), size=MINIMIZE_SIDEBAR_IMAGE_SIZE) else: - image_file = CTkImage((settings.image_file.ARROW_LEFT), size=LOGO_SIZE) - vrct_gui.minimize_sidebar_button.configure(image=image_file) + image_file = CTkImage((settings.image_file.ARROW_LEFT), size=MINIMIZE_SIDEBAR_IMAGE_SIZE) + vrct_gui.minimize_sidebar_button__for_opening.configure(image=image_file) + vrct_gui.minimize_sidebar_button__for_closing.configure(image=image_file) case "entry_message_box": diff --git a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py index 250e2ac4..8a08c1f0 100644 --- a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py +++ b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py @@ -15,36 +15,61 @@ def createMinimizeSidebarButton(settings, main_window, view_variable): - main_window.minimize_sidebar_button_container = CTkFrame(main_window.main_topbar_container, corner_radius=0, fg_color=settings.ctm.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR, cursor="hand2", width=0, height=0) + main_window.minimize_sidebar_button_container__for_closing = CTkFrame(main_window.main_topbar_container, corner_radius=0, fg_color=settings.ctm.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR, cursor="hand2", width=0, height=0) + main_window.minimize_sidebar_button_container__for_opening = CTkFrame(main_window.main_topbar_container, corner_radius=0, fg_color=settings.ctm.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR, cursor="hand2", width=0, height=0) - main_window.minimize_sidebar_button = CTkLabel( - main_window.minimize_sidebar_button_container, + + # For Closing [<] + main_window.minimize_sidebar_button__for_closing = CTkLabel( + main_window.minimize_sidebar_button_container__for_closing, text=None, corner_radius=0, height=0, image=CTkImage((settings.image_file.ARROW_LEFT),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) ) + main_window.minimize_sidebar_button_container__for_closing.grid_rowconfigure((0,2), weight=1) + main_window.minimize_sidebar_button__for_closing.grid(row=1, column=0, padx=0, pady=0) + + bindEnterAndLeaveColor([main_window.minimize_sidebar_button__for_closing, main_window.minimize_sidebar_button_container__for_closing], settings.ctm.MINIMIZE_SIDEBAR_BUTTON_HOVERED_BG_COLOR, settings.ctm.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR) + bindButtonPressColor([main_window.minimize_sidebar_button__for_closing, main_window.minimize_sidebar_button_container__for_closing], settings.ctm.MINIMIZE_SIDEBAR_BUTTON_CLICKED_BG_COLOR, settings.ctm.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR) + bindButtonReleaseFunction([main_window.minimize_sidebar_button_container__for_closing, main_window.minimize_sidebar_button__for_closing], enableCompactMode) + + + + +# For Opening [>] + main_window.minimize_sidebar_button__for_opening = CTkLabel( + main_window.minimize_sidebar_button_container__for_opening, + text=None, + corner_radius=0, + height=0, + image=CTkImage((settings.image_file.ARROW_LEFT).rotate(180),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) + ) + + + + + main_window.minimize_sidebar_button_container__for_opening.grid_rowconfigure((0,2), weight=1) + main_window.minimize_sidebar_button__for_opening.grid(row=1, column=0, padx=0, pady=0) + + + bindEnterAndLeaveColor([main_window.minimize_sidebar_button__for_opening, main_window.minimize_sidebar_button_container__for_opening], settings.ctm.MINIMIZE_SIDEBAR_BUTTON_HOVERED_BG_COLOR, settings.ctm.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR) + bindButtonPressColor([main_window.minimize_sidebar_button__for_opening, main_window.minimize_sidebar_button_container__for_opening], settings.ctm.MINIMIZE_SIDEBAR_BUTTON_CLICKED_BG_COLOR, settings.ctm.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR) + bindButtonReleaseFunction([main_window.minimize_sidebar_button_container__for_opening, main_window.minimize_sidebar_button__for_opening], disableCompactMode) + + + + + + main_window.minimize_sidebar_button_container__for_opening.grid(row=0, column=0, sticky="nsw") + main_window.minimize_sidebar_button_container__for_closing.grid(row=0, column=0, sticky="nsw") + main_window.minimize_sidebar_button_container__for_opening.grid_remove() + main_window.minimize_sidebar_button_container__for_closing.grid_remove() if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - image_file = CTkImage((settings.image_file.ARROW_LEFT).rotate(180),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) - bindButtonReleaseFunction([main_window.minimize_sidebar_button_container, main_window.minimize_sidebar_button], disableCompactMode) - + main_window.minimize_sidebar_button_container__for_opening.grid() else: - image_file = CTkImage((settings.image_file.ARROW_LEFT),size=(settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X,settings.uism.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y)) - bindButtonReleaseFunction([main_window.minimize_sidebar_button_container, main_window.minimize_sidebar_button], enableCompactMode) - - main_window.minimize_sidebar_button_container.grid_rowconfigure((0,2), weight=1) - main_window.minimize_sidebar_button.configure(image=image_file) - main_window.minimize_sidebar_button_container.grid(row=0, column=0, sticky="nsw") - main_window.minimize_sidebar_button.grid(row=1, column=0, padx=0, pady=0) - - - bindEnterAndLeaveColor([main_window.minimize_sidebar_button, main_window.minimize_sidebar_button_container], settings.ctm.MINIMIZE_SIDEBAR_BUTTON_HOVERED_BG_COLOR, settings.ctm.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR) - bindButtonPressColor([main_window.minimize_sidebar_button, main_window.minimize_sidebar_button_container], settings.ctm.MINIMIZE_SIDEBAR_BUTTON_CLICKED_BG_COLOR, settings.ctm.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR) - - - - + main_window.minimize_sidebar_button_container__for_closing.grid() \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 383bf4cd..ae343c2c 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -156,15 +156,16 @@ class VRCT_GUI(CTk): ) def recreateMainWindowSidebar(self): - self.minimize_sidebar_button_container.destroy() - createMinimizeSidebarButton(self.settings.main, self, view_variable=self._view_variable) - if self._view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE: self.sidebar_bg_container.grid_remove() self.sidebar_compact_mode_bg_container.grid() + self.minimize_sidebar_button_container__for_closing.grid_remove() + self.minimize_sidebar_button_container__for_opening.grid() else: self.sidebar_compact_mode_bg_container.grid_remove() self.sidebar_bg_container.grid() + self.minimize_sidebar_button_container__for_opening.grid_remove() + self.minimize_sidebar_button_container__for_closing.grid() vrct_gui = VRCT_GUI() \ No newline at end of file From 43133ac7ad2d568df9234e3a601b2feba49197d0 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 17 Sep 2023 10:23:24 +0900 Subject: [PATCH 164/355] =?UTF-8?q?[Chore]=20Auto=20Clear=20The=20Message?= =?UTF-8?q?=20Box=E3=81=AF=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB=E3=83=88?= =?UTF-8?q?=E3=81=A7ON=E3=81=AB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 8055a675..41e1a154 100644 --- a/config.py +++ b/config.py @@ -514,7 +514,7 @@ class Config: "Google(web)": None, } self._MESSAGE_FORMAT = "[message]([translation])" - self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = False + self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = True self._ENABLE_NOTICE_XSOVERLAY = False self._ENABLE_OSC = False self._STARTUP_OSC_ENABLED_CHECK = True From 6f5b7983a936c168d7482b9966b88259cae750c8 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 17 Sep 2023 12:01:23 +0900 Subject: [PATCH 165/355] [Update] add config variable and callback, ENABLE_SEND_MESSAGE_TO_VRC and callbackSetEnableSendMessageToVrc. add Config Window Setting Item(Send Message To VRChat) --- config.py | 11 +++++++++++ main.py | 6 +++++- view.py | 9 +++++---- .../createSettingBox_Others.py | 18 +++++++++--------- 4 files changed, 30 insertions(+), 14 deletions(-) diff --git a/config.py b/config.py index 41e1a154..45a2bf54 100644 --- a/config.py +++ b/config.py @@ -388,6 +388,16 @@ class Config: if type(value) is bool: self._ENABLE_OSC = value + @property + def ENABLE_SEND_MESSAGE_TO_VRC(self): + return self._ENABLE_SEND_MESSAGE_TO_VRC + + @ENABLE_SEND_MESSAGE_TO_VRC.setter + def ENABLE_SEND_MESSAGE_TO_VRC(self, value): + if type(value) is bool: + self._ENABLE_SEND_MESSAGE_TO_VRC = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property def STARTUP_OSC_ENABLED_CHECK(self): return self._STARTUP_OSC_ENABLED_CHECK @@ -517,6 +527,7 @@ class Config: self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = True self._ENABLE_NOTICE_XSOVERLAY = False self._ENABLE_OSC = False + self._ENABLE_SEND_MESSAGE_TO_VRC = True self._STARTUP_OSC_ENABLED_CHECK = True self._UPDATE_FLAG = False self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" diff --git a/main.py b/main.py index 8175af67..ad8c1374 100644 --- a/main.py +++ b/main.py @@ -518,6 +518,10 @@ def callbackSetMessageFormat(value): if len(value) > 0: config.MESSAGE_FORMAT = value +def callbackSetEnableSendMessageToVrc(value): + print("callbackSetEnableSendMessageToVrc", value) + config.ENABLE_SEND_MESSAGE_TO_VRC = value + def callbackSetStartupOscEnabledCheck(value): print("callbackSetStartupOscEnabledCheck", value) config.STARTUP_OSC_ENABLED_CHECK = value @@ -626,7 +630,7 @@ view.register( "callback_set_enable_notice_xsoverlay": callbackSetEnableNoticeXsoverlay, "callback_set_enable_auto_export_message_logs": callbackSetEnableAutoExportMessageLogs, "callback_set_message_format": callbackSetMessageFormat, - + "callback_set_enable_send_message_to_vrc": callbackSetEnableSendMessageToVrc, "callback_set_startup_osc_enabled_check": callbackSetStartupOscEnabledCheck, # Advanced Settings Tab diff --git a/view.py b/view.py index 9c69c50c..135bb6c7 100644 --- a/view.py +++ b/view.py @@ -246,10 +246,10 @@ class View(): VAR_MESSAGE_FORMAT=StringVar(value=config.MESSAGE_FORMAT), - VAR_LABEL_ENABLE_OSC=StringVar(value="Send Message To VRChat"), - VAR_DESC_ENABLE_OSC=StringVar(value="There is a way to use it without sending messages to VRChat."), - CALLBACK_SET_ENABLE_OSC=None, - VAR_ENABLE_OSC=BooleanVar(value=config.ENABLE_OSC), + VAR_LABEL_ENABLE_SEND_MESSAGE_TO_VRC=StringVar(value="Send Message To VRChat"), + VAR_DESC_ENABLE_SEND_MESSAGE_TO_VRC=StringVar(value="There is a way to use it without sending messages to VRChat.\nThat is not covered by support, though."), + CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC=None, + VAR_ENABLE_SEND_MESSAGE_TO_VRC=BooleanVar(value=config.ENABLE_SEND_MESSAGE_TO_VRC), VAR_LABEL_STARTUP_OSC_ENABLED_CHECK=StringVar(value="Check If OSC Is Enabled At Startup"), VAR_DESC_STARTUP_OSC_ENABLED_CHECK=StringVar(value="Every time VRCT is started up, your character in VRChat moves forward ever so slightly. If your character is seated, they might even stand up. Unfortunately, this is the only method we've found to check if OSC is enabled at startup... Sorry about that. (Remember to turn on OSC yourself when you want to send messages to VRChat.)"), @@ -377,6 +377,7 @@ class View(): self.view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS = config_window_registers.get("callback_set_enable_auto_export_message_logs", None) self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window_registers.get("callback_set_message_format", None) + self.view_variable.CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC = config_window_registers.get("callback_set_enable_send_message_to_vrc", None) self.view_variable.CALLBACK_SET_STARTUP_OSC_ENABLED_CHECK = config_window_registers.get("callback_set_startup_osc_enabled_check", None) # Advanced Settings Tab diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index 2c257f64..b125aa75 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -18,8 +18,8 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v def checkbox_auto_export_message_logs_callback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, checkbox_box_widget.get()) - def checkbox_enable_osc_callback(checkbox_box_widget): - callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_OSC, checkbox_box_widget.get()) + def checkbox_enable_send_message_to_vrc_callback(checkbox_box_widget): + callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC, checkbox_box_widget.get()) def checkbox_startup_osc_enabled_check_callback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_STARTUP_OSC_ENABLED_CHECK, checkbox_box_widget.get()) @@ -78,15 +78,15 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v row+=1 - config_window.sb__enable_osc = createSettingBoxCheckbox( + config_window.sb__enable_send_message_to_vrc = createSettingBoxCheckbox( parent_widget=setting_box_wrapper, - for_var_label_text=view_variable.VAR_LABEL_ENABLE_OSC, - for_var_desc_text=view_variable.VAR_DESC_ENABLE_OSC, - checkbox_attr_name="sb__checkbox_enable_osc", - command=lambda: checkbox_enable_osc_callback(config_window.sb__checkbox_enable_osc), - variable=view_variable.VAR_ENABLE_OSC, + for_var_label_text=view_variable.VAR_LABEL_ENABLE_SEND_MESSAGE_TO_VRC, + for_var_desc_text=view_variable.VAR_DESC_ENABLE_SEND_MESSAGE_TO_VRC, + checkbox_attr_name="sb__checkbox_enable_send_message_to_vrc", + command=lambda: checkbox_enable_send_message_to_vrc_callback(config_window.sb__checkbox_enable_send_message_to_vrc), + variable=view_variable.VAR_ENABLE_SEND_MESSAGE_TO_VRC, ) - config_window.sb__enable_osc.grid(row=row) + config_window.sb__enable_send_message_to_vrc.grid(row=row) row+=1 config_window.sb__startup_osc_enabled_check = createSettingBoxCheckbox( From 6e0efd66077320c138cb07763df7ce5013eed5d1 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 17 Sep 2023 12:24:08 +0900 Subject: [PATCH 166/355] =?UTF-8?q?[Update]=20Logger=E3=81=A7=E5=87=BA?= =?UTF-8?q?=E5=8A=9B=E3=81=99=E3=82=8B=E9=9A=9B=E3=81=AE=E3=82=BF=E3=82=B0?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=20[SEND]=20[RECEIVE]=20->=20[SENT]=20[RECEIV?= =?UTF-8?q?ED]?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index ad8c1374..cf787652 100644 --- a/main.py +++ b/main.py @@ -40,7 +40,7 @@ def sendMicMessage(message): if config.ENABLE_LOGGER is True: if len(translation) > 0: translation = f" ({translation})" - model.logger.info(f"[SEND] {message}{translation}") + model.logger.info(f"[SENT] {message}{translation}") def startTranscriptionSendMessage(): model.startMicTranscript(sendMicMessage) @@ -86,7 +86,7 @@ def receiveSpeakerMessage(message): if config.ENABLE_LOGGER is True: if len(translation) > 0: translation = f" ({translation})" - model.logger.info(f"[RECEIVE] {message}{translation}") + model.logger.info(f"[RECEIVED] {message}{translation}") def startTranscriptionReceiveMessage(): model.startSpeakerTranscript(receiveSpeakerMessage) @@ -140,7 +140,7 @@ def sendChatMessage(message): if config.ENABLE_LOGGER is True: if len(translation) > 0: translation = f" ({translation})" - model.logger.info(f"[SEND] {message}{translation}") + model.logger.info(f"[SENT] {message}{translation}") # delete message in entry message box if config.ENABLE_AUTO_CLEAR_MESSAGE_BOX is True: From bc6547cf5cf4ded34734adf8c8aec975019aa887 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 17 Sep 2023 12:45:05 +0900 Subject: [PATCH 167/355] [Chore] remove the print code that is under vrct_gui directory. --- .../_createSettingBoxCompactModeButton.py | 1 - .../widgets/_create_sidebar/createSidebarLanguagesSettings.py | 3 --- vrct_gui/main_window/widgets/create_textbox.py | 4 ---- vrct_gui/ui_managers/ColorThemeManager.py | 1 - vrct_gui/ui_managers/UiScalingManager.py | 1 - 5 files changed, 10 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py index 7ffc533f..bc61811b 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py @@ -3,7 +3,6 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkSwitch def _createSettingBoxCompactModeButton(parent_widget, config_window, settings, view_variable): def switchConfigWindowCompactMode(): - print(config_window.setting_box_compact_mode_switch_box.get()) if config_window.setting_box_compact_mode_switch_box.get() is True: if callable(view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE) is True: view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE() diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 5ed03a2c..24dcd842 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -32,19 +32,16 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): def switchToPreset1(e): - print("1") callFunctionIfCallable(view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "1") target_active_widget = getattr(main_window, "sls__presets_button_1") switchPresetTabFunction(target_active_widget) def switchToPreset2(e): - print("2") callFunctionIfCallable(view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "2") target_active_widget = getattr(main_window, "sls__presets_button_2") switchPresetTabFunction(target_active_widget) def switchToPreset3(e): - print("3") callFunctionIfCallable(view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB, "3") target_active_widget = getattr(main_window, "sls__presets_button_3") switchPresetTabFunction(target_active_widget) diff --git a/vrct_gui/main_window/widgets/create_textbox.py b/vrct_gui/main_window/widgets/create_textbox.py index b47d7ac0..404220a4 100644 --- a/vrct_gui/main_window/widgets/create_textbox.py +++ b/vrct_gui/main_window/widgets/create_textbox.py @@ -11,25 +11,21 @@ def createTextbox(settings, main_window, view_variable): main_window.current_active_textbox.grid() def switchToTextboxAll(e): - print("switchToTextboxAll") target_active_widget = getattr(main_window, "textbox_tab_all") switchTextboxTabFunction(target_active_widget) switchTextbox("textbox_all") def switchToTextboxSent(e): - print("switchToTextboxSent") target_active_widget = getattr(main_window, "textbox_tab_sent") switchTextboxTabFunction(target_active_widget) switchTextbox("textbox_sent") def switchToTextboxReceived(e): - print("switchToTextboxReceived") target_active_widget = getattr(main_window, "textbox_tab_received") switchTextboxTabFunction(target_active_widget) switchTextbox("textbox_received") def switchToTextboxSystem(e): - print("switchToTextboxSystem") target_active_widget = getattr(main_window, "textbox_tab_system") switchTextboxTabFunction(target_active_widget) switchTextbox("textbox_system") diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index f0e5c6d1..9c288b55 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -2,7 +2,6 @@ from types import SimpleNamespace class ColorThemeManager(): def __init__(self, theme): - print(theme) self.main = SimpleNamespace() self.config_window = SimpleNamespace() self.selectable_language_window = SimpleNamespace() diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 06f5284f..a5769c12 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -3,7 +3,6 @@ from types import SimpleNamespace class UiScalingManager(): def __init__(self, scaling_percentage): scaling_float = int(scaling_percentage.replace("%", "")) / 100 - print(scaling_float) self.SCALING_FLOAT = max(scaling_float, 0.4) self.main = SimpleNamespace() self.config_window = SimpleNamespace() From f0abb8bcfade720115cfc609dc971392a25e7f26 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 17 Sep 2023 14:57:16 +0900 Subject: [PATCH 168/355] [Update] add function to view.py and print system message when switched language preset and selected language. --- main.py | 3 +++ view.py | 20 ++++++++++++++++++++ vrct_gui/_CreateSelectableLanguagesWindow.py | 2 +- 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index cf787652..efba02c8 100644 --- a/main.py +++ b/main.py @@ -164,6 +164,7 @@ def setYourLanguageAndCountry(select): config.SOURCE_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) model.authenticationTranslator(callbackSetAuthKeys) + view.printToTextbox_selectedYourLanguages(select) def setTargetLanguageAndCountry(select): languages = config.SELECTED_TAB_TARGET_LANGUAGES @@ -174,6 +175,7 @@ def setTargetLanguageAndCountry(select): config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) model.authenticationTranslator(callbackSetAuthKeys) + view.printToTextbox_selectedTargetLanguages(select) def callbackSelectedLanguagePresetTab(selected_tab_no): config.SELECTED_TAB_NO = selected_tab_no @@ -190,6 +192,7 @@ def callbackSelectedLanguagePresetTab(selected_tab_no): config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) model.authenticationTranslator(callbackSetAuthKeys) + view.printToTextbox_changedLanguagePresetTab(config.SELECTED_TAB_NO) def callbackSetAuthKeys(keys): config.AUTH_KEYS = keys diff --git a/view.py b/view.py index 135bb6c7..13de0d26 100644 --- a/view.py +++ b/view.py @@ -471,6 +471,26 @@ class View(): self._printToTextbox_Info(f"Detect WordFilter :{detected_message}") + + def printToTextbox_selectedYourLanguages(self, selected_your_language): + your_language = selected_your_language.replace("\n", " ") + self._printToTextbox_Info(f"Your Language has changed : {your_language}") + + def printToTextbox_selectedTargetLanguages(self, selected_target_language): + target_language = selected_target_language.replace("\n", " ") + self._printToTextbox_Info(f"Target Language has changed : {target_language}") + + def printToTextbox_latestSelectedLanguages(self): + your_language = self.view_variable.VAR_YOUR_LANGUAGE.get().replace("\n", " ") + target_language = self.view_variable.VAR_TARGET_LANGUAGE.get().replace("\n", " ") + self._printToTextbox_Info(f"Your Language : {your_language} -- Target Language : {target_language}") + + def printToTextbox_changedLanguagePresetTab(self, tab_no:str): + your_language = config.SELECTED_TAB_YOUR_LANGUAGES[tab_no].replace("\n", " ") + target_language = config.SELECTED_TAB_TARGET_LANGUAGES[tab_no].replace("\n", " ") + self._printToTextbox_Info(f"Switched Language Preset. No.{tab_no}\nYour Language : {your_language} -- Target Language : {target_language}") + + @staticmethod def _printToTextbox_Info(info_message): vrct_gui.printToTextbox( diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index e0c8d6ce..5892f7d1 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -62,8 +62,8 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): callback = self._view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE target_variable = self._view_variable.VAR_TARGET_LANGUAGE - callFunctionIfCallable(callback, value) target_variable.set(value) + callFunctionIfCallable(callback, value) self.vrct_gui.closeSelectableLanguagesWindow() From e29d5ae9d4527fa0174c153dcbcea65f65e3ae9d Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 17 Sep 2023 18:13:32 +0900 Subject: [PATCH 169/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20OSC=E3=81=AE?= =?UTF-8?q?=E6=9C=89=E5=8A=B9=E6=80=A7=E3=81=AE=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=83=AD=E3=82=B0=E3=81=AE=E8=A1=A8=E7=A4=BA=E3=82=92=E8=B5=B7?= =?UTF-8?q?=E5=8B=95=E6=99=82=E3=81=AE=E3=81=BF=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 20 ++++++++++---------- main.py | 20 +++++++++----------- model.py | 2 +- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/config.py b/config.py index 45a2bf54..e2326b98 100644 --- a/config.py +++ b/config.py @@ -379,15 +379,6 @@ class Config: self._ENABLE_NOTICE_XSOVERLAY = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property - def ENABLE_OSC(self): - return self._ENABLE_OSC - - @ENABLE_OSC.setter - def ENABLE_OSC(self, value): - if type(value) is bool: - self._ENABLE_OSC = value - @property def ENABLE_SEND_MESSAGE_TO_VRC(self): return self._ENABLE_SEND_MESSAGE_TO_VRC @@ -408,6 +399,15 @@ class Config: self._STARTUP_OSC_ENABLED_CHECK = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property + def IS_VALID_OSC(self): + return self._IS_VALID_OSC + + @IS_VALID_OSC.setter + def IS_VALID_OSC(self, value): + if type(value) is bool: + self._IS_VALID_OSC = value + @property def UPDATE_FLAG(self): return self._UPDATE_FLAG @@ -526,9 +526,9 @@ class Config: self._MESSAGE_FORMAT = "[message]([translation])" self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = True self._ENABLE_NOTICE_XSOVERLAY = False - self._ENABLE_OSC = False self._ENABLE_SEND_MESSAGE_TO_VRC = True self._STARTUP_OSC_ENABLED_CHECK = True + self._IS_VALID_OSC = False self._UPDATE_FLAG = False self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" # self._BREAK_KEYSYM_LIST = [ diff --git a/main.py b/main.py index efba02c8..3ebab849 100644 --- a/main.py +++ b/main.py @@ -25,16 +25,13 @@ def sendMicMessage(message): translation = "" if config.ENABLE_TRANSCRIPTION_SEND is True: - if config.ENABLE_OSC is True: + if config.ENABLE_SEND_MESSAGE_TO_VRC is True: if len(translation) > 0: osc_message = config.MESSAGE_FORMAT.replace("[message]", message) osc_message = osc_message.replace("[translation]", translation) else: osc_message = message model.oscSendMessage(osc_message) - else: - if config.STARTUP_OSC_ENABLED_CHECK is True: - view.printToTextbox_OSCError() view.printToTextbox_SentMessage(message, translation) if config.ENABLE_LOGGER is True: @@ -124,16 +121,13 @@ def sendChatMessage(message): translation = "" # send OSC message - if config.ENABLE_OSC is True: + if config.ENABLE_SEND_MESSAGE_TO_VRC is True: if len(translation) > 0: osc_message = config.MESSAGE_FORMAT.replace("[message]", message) osc_message = osc_message.replace("[translation]", translation) else: osc_message = message model.oscSendMessage(osc_message) - else: - if config.STARTUP_OSC_ENABLED_CHECK is True: - view.printToTextbox_OSCError() # update textbox message log view.printToTextbox_SentMessage(message, translation) @@ -197,8 +191,8 @@ def callbackSelectedLanguagePresetTab(selected_tab_no): def callbackSetAuthKeys(keys): config.AUTH_KEYS = keys -def callbackChangeStatusOSC(value): - config.ENABLE_OSC = value +def callbackChangeStatusIsValidOSC(value): + config.IS_VALID_OSC = value def callbackChangeStatusSoftwareUpdated(value): config.UPDATE_FLAG = value @@ -554,7 +548,11 @@ if model.authenticationTranslator(callbackSetAuthKeys) is False: model.addKeywords() # check OSC started -model.checkOSCStarted(callbackChangeStatusOSC) +if config.STARTUP_OSC_ENABLED_CHECK is True: + model.checkOSCStarted(callbackChangeStatusIsValidOSC) + sleep(2) + if config.IS_VALID_OSC is False: + view.printToTextbox_OSCError() # check Software Updated model.checkSoftwareUpdated(callbackChangeStatusSoftwareUpdated) diff --git a/model.py b/model.py index b6964143..7a1756e0 100644 --- a/model.py +++ b/model.py @@ -180,7 +180,7 @@ class Model: @staticmethod def checkOSCStarted(fnc): def checkOscReceive(address, osc_arguments): - if config.ENABLE_OSC is False: + if config.IS_VALID_OSC is False: fnc(True) # start receive osc From cd7f4aa61416b9924b7ef03a4622b2f3e944192f Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 17 Sep 2023 18:47:33 +0900 Subject: [PATCH 170/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20OSC=E3=81=AEchec?= =?UTF-8?q?k=E3=81=8C=E5=AE=8C=E4=BA=86=E3=81=99=E3=82=8B=E3=81=BE?= =?UTF-8?q?=E3=81=A7OSC=E3=81=AE=E3=83=86=E3=82=B9=E3=83=88=E3=82=92?= =?UTF-8?q?=E7=B6=9A=E3=81=91=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 29 ++++++++++++++++++++++------- 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/model.py b/model.py index 7a1756e0..cac44d69 100644 --- a/model.py +++ b/model.py @@ -80,7 +80,10 @@ class Model: if result: auth_keys = config.AUTH_KEYS auth_keys[choice_translator] = auth_key - fnc(auth_keys) + try: + fnc(auth_keys) + except: + pass return result def startLogger(self): @@ -180,8 +183,15 @@ class Model: @staticmethod def checkOSCStarted(fnc): def checkOscReceive(address, osc_arguments): - if config.IS_VALID_OSC is False: - fnc(True) + if config.IS_VALID_OSC is False and config.STARTUP_OSC_ENABLED_CHECK is True: + try: + fnc(True) + except: + pass + + def sendTestActionLoop(): + while config.IS_VALID_OSC is False and config.STARTUP_OSC_ENABLED_CHECK is True: + sendTestAction() # start receive osc th_receive_osc_parameters = Thread(target=receiveOscParameters, args=(checkOscReceive,)) @@ -189,7 +199,9 @@ class Model: th_receive_osc_parameters.start() # check osc started - sendTestAction() + th_send_osc_test_action = Thread(target=sendTestActionLoop) + th_send_osc_test_action.daemon = True + th_send_osc_test_action.start() @staticmethod def checkSoftwareUpdated(fnc): @@ -197,7 +209,10 @@ class Model: response = requests_get(config.GITHUB_URL) tag_name = response.json()["tag_name"] if tag_name != config.VERSION: - fnc(True) + try: + fnc(True) + except: + pass @staticmethod def getListInputHost(): @@ -270,7 +285,7 @@ class Model: fnc(energy) except: pass - sleep(0.01) + # sleep(0.01) mic_energy_queue = Queue() mic_device = [device for device in getInputDevices()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] @@ -336,7 +351,7 @@ class Model: fnc(energy) except: pass - sleep(0.01) + # sleep(0.01) speaker_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] speaker_energy_queue = Queue() From acd9373938277a2c589f3af5f63f8c781c6879be Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 18 Sep 2023 03:26:53 +0900 Subject: [PATCH 171/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20model=20change?= =?UTF-8?q?=20method=20check=20osc?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 10 ---------- main.py | 10 ++-------- model.py | 29 ++++++++++++++++++++--------- models/osc/osc_tools.py | 2 +- 4 files changed, 23 insertions(+), 28 deletions(-) diff --git a/config.py b/config.py index e2326b98..42c1c404 100644 --- a/config.py +++ b/config.py @@ -399,15 +399,6 @@ class Config: self._STARTUP_OSC_ENABLED_CHECK = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property - def IS_VALID_OSC(self): - return self._IS_VALID_OSC - - @IS_VALID_OSC.setter - def IS_VALID_OSC(self, value): - if type(value) is bool: - self._IS_VALID_OSC = value - @property def UPDATE_FLAG(self): return self._UPDATE_FLAG @@ -528,7 +519,6 @@ class Config: self._ENABLE_NOTICE_XSOVERLAY = False self._ENABLE_SEND_MESSAGE_TO_VRC = True self._STARTUP_OSC_ENABLED_CHECK = True - self._IS_VALID_OSC = False self._UPDATE_FLAG = False self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" # self._BREAK_KEYSYM_LIST = [ diff --git a/main.py b/main.py index 3ebab849..b28e10aa 100644 --- a/main.py +++ b/main.py @@ -191,9 +191,6 @@ def callbackSelectedLanguagePresetTab(selected_tab_no): def callbackSetAuthKeys(keys): config.AUTH_KEYS = keys -def callbackChangeStatusIsValidOSC(value): - config.IS_VALID_OSC = value - def callbackChangeStatusSoftwareUpdated(value): config.UPDATE_FLAG = value @@ -548,11 +545,8 @@ if model.authenticationTranslator(callbackSetAuthKeys) is False: model.addKeywords() # check OSC started -if config.STARTUP_OSC_ENABLED_CHECK is True: - model.checkOSCStarted(callbackChangeStatusIsValidOSC) - sleep(2) - if config.IS_VALID_OSC is False: - view.printToTextbox_OSCError() +if config.STARTUP_OSC_ENABLED_CHECK is True and config.ENABLE_SEND_MESSAGE_TO_VRC is True: + model.checkOSCStarted(view.printToTextbox_OSCError) # check Software Updated model.checkSoftwareUpdated(callbackChangeStatusSoftwareUpdated) diff --git a/model.py b/model.py index cac44d69..19dc293d 100644 --- a/model.py +++ b/model.py @@ -180,21 +180,26 @@ class Model: def oscSendMessage(message): sendMessage(message, config.OSC_IP_ADDRESS, config.OSC_PORT) - @staticmethod - def checkOSCStarted(fnc): + def checkOSCStarted(self, fnc): + self.is_valid_osc = False def checkOscReceive(address, osc_arguments): - if config.IS_VALID_OSC is False and config.STARTUP_OSC_ENABLED_CHECK is True: - try: - fnc(True) - except: - pass + if self.is_valid_osc is False: + self.is_valid_osc = True + + self.listening_server = receiveOscParameters(checkOscReceive) + def oscListener(): + self.listening_server.serve_forever() def sendTestActionLoop(): - while config.IS_VALID_OSC is False and config.STARTUP_OSC_ENABLED_CHECK is True: + for _ in range(10): sendTestAction() + if self.is_valid_osc is True: + break + sleep(0.1) + self.listening_server.shutdown() # start receive osc - th_receive_osc_parameters = Thread(target=receiveOscParameters, args=(checkOscReceive,)) + th_receive_osc_parameters = Thread(target=oscListener) th_receive_osc_parameters.daemon = True th_receive_osc_parameters.start() @@ -203,6 +208,12 @@ class Model: th_send_osc_test_action.daemon = True th_send_osc_test_action.start() + th_receive_osc_parameters.join() + th_send_osc_test_action.join() + + if self.is_valid_osc is False: + fnc() + @staticmethod def checkSoftwareUpdated(fnc): # check update diff --git a/models/osc/osc_tools.py b/models/osc/osc_tools.py index c2f765ec..7cb926c2 100644 --- a/models/osc/osc_tools.py +++ b/models/osc/osc_tools.py @@ -50,7 +50,7 @@ def receiveOscParameters(target, filter="/*", ip_address="127.0.0.1", port=9001) _dispatcher = dispatcher.Dispatcher() _dispatcher.map(filter, target) server = osc_server.ThreadingOSCUDPServer((ip_address, port), _dispatcher) - server.serve_forever() + return server if __name__ == "__main__": sendChangeVoice() From 4cd3bf741e4372c4aa3e974e51ce0ced8291e7ea Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 18 Sep 2023 12:42:01 +0900 Subject: [PATCH 172/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20main=20ConfigWin?= =?UTF-8?q?dow=20Open/Close=E6=99=82=E3=81=AETranscription=E3=81=AE?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=82=92=E5=8D=98=E7=8B=AC=E3=81=AE=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 42 ++++++++++++++++++++++++++++++++++++++---- 1 file changed, 38 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index b28e10aa..5a35e54c 100644 --- a/main.py +++ b/main.py @@ -59,6 +59,22 @@ def stopThreadingTranscriptionSendMessage(): th_stopTranscriptionSendMessage.daemon = True th_stopTranscriptionSendMessage.start() +def startTranscriptionSendMessageOnCloseConfigWindow(): + model.startMicTranscript(sendMicMessage) + +def stopTranscriptionSendMessageOnOpenConfigWindow(): + model.stopMicTranscript() + +def startThreadingTranscriptionSendMessageOnCloseConfigWindow(): + th_startTranscriptionSendMessage = Thread(target=startTranscriptionSendMessageOnCloseConfigWindow) + th_startTranscriptionSendMessage.daemon = True + th_startTranscriptionSendMessage.start() + +def stopThreadingTranscriptionSendMessageOnOpenConfigWindow(): + th_stopTranscriptionSendMessage = Thread(target=stopTranscriptionSendMessageOnOpenConfigWindow) + th_stopTranscriptionSendMessage.daemon = True + th_stopTranscriptionSendMessage.start() + # func transcription receive message def receiveSpeakerMessage(message): if len(message) > 0: @@ -105,6 +121,22 @@ def stopThreadingTranscriptionReceiveMessage(): th_stopTranscriptionReceiveMessage.daemon = True th_stopTranscriptionReceiveMessage.start() +def startTranscriptionReceiveMessageOnCloseConfigWindow(): + model.startSpeakerTranscript(receiveSpeakerMessage) + +def stopTranscriptionReceiveMessageOnOpenConfigWindow(): + model.stopSpeakerTranscript() + +def startThreadingTranscriptionReceiveMessageOnCloseConfigWindow(): + th_startTranscriptionReceiveMessage = Thread(target=startTranscriptionReceiveMessageOnCloseConfigWindow) + th_startTranscriptionReceiveMessage.daemon = True + th_startTranscriptionReceiveMessage.start() + +def stopThreadingTranscriptionReceiveMessageOnOpenConfigWindow(): + th_stopTranscriptionReceiveMessage = Thread(target=stopTranscriptionReceiveMessageOnOpenConfigWindow) + th_stopTranscriptionReceiveMessage.daemon = True + th_stopTranscriptionReceiveMessage.start() + # func message box def sendChatMessage(message): if len(message) > 0: @@ -229,10 +261,11 @@ def callbackToggleForeground(is_turned_on): # Config Window def callbackOpenConfigWindow(): + view.setMainWindowAllWidgetsStatusToDisabled() if config.ENABLE_TRANSCRIPTION_SEND is True: - stopThreadingTranscriptionSendMessage() + stopThreadingTranscriptionSendMessageOnOpenConfigWindow() if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - stopThreadingTranscriptionReceiveMessage() + stopThreadingTranscriptionReceiveMessageOnOpenConfigWindow() if config.ENABLE_FOREGROUND is True: view.foregroundOff() @@ -245,13 +278,14 @@ def callbackCloseConfigWindow(): # view.initProgressBar_SpeakerEnergy() # ProgressBarに0をセットしたい if config.ENABLE_TRANSCRIPTION_SEND is True: - startThreadingTranscriptionSendMessage() + startThreadingTranscriptionSendMessageOnCloseConfigWindow() if config.ENABLE_TRANSCRIPTION_RECEIVE is True: sleep(2) if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - startThreadingTranscriptionReceiveMessage() + startThreadingTranscriptionReceiveMessageOnCloseConfigWindow() if config.ENABLE_FOREGROUND is True: view.foregroundOn() + view.setMainWindowAllWidgetsStatusToNormal() # Compact Mode Switch def callbackEnableConfigWindowCompactMode(): From c3b7c735dd473342f366bdf9c2a47ac488e955f4 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:39:18 +0900 Subject: [PATCH 173/355] =?UTF-8?q?[Refactor]=20Config=20Window:=20Setting?= =?UTF-8?q?=20Box=E5=9B=9E=E3=82=8A=E3=80=82=E7=89=B9=E3=81=AB=E6=84=8F?= =?UTF-8?q?=E5=91=B3=E3=81=AE=E3=81=AA=E3=81=84=E9=96=A2=E6=95=B0=E3=81=AE?= =?UTF-8?q?=E5=88=87=E3=82=8A=E5=87=BA=E3=81=97=E3=82=92=E3=81=AA=E3=81=8F?= =?UTF-8?q?=E3=81=97=E3=81=9F=E3=82=8A=E3=80=82=20=E2=80=BB=E3=83=86?= =?UTF-8?q?=E3=82=B9=E3=83=88=E7=94=A8=E3=81=AE=E3=82=B3=E3=83=BC=E3=83=89?= =?UTF-8?q?=E3=81=AF=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=A2=E3=82=A6?= =?UTF-8?q?=E3=83=88=E3=81=97=E3=81=A6=E3=81=9D=E3=81=AE=E3=81=BE=E3=81=BE?= =?UTF-8?q?=E3=81=A7=E3=81=99=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_createSettingBoxContainer.py | 1 + .../_SettingBoxGenerator.py | 28 +++++++++++-------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py index 62e8088d..0a007e41 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py @@ -49,6 +49,7 @@ def _createSettingBoxContainer(config_window, settings, view_variable, setting_b # if the last one of setting boxes, remove bottom pady if i+1 == setting_boxes_length: SB__BOTTOM_PADY = 0 + # setting_box_wrapper = CTkFrame(setting_box_and_section_title_wrapper, fg_color="red", corner_radius=0, width=0, height=0) setting_box_wrapper = CTkFrame(setting_box_and_section_title_wrapper, fg_color=settings.ctm.SB__WRAPPER_BG_COLOR, corner_radius=0, width=0, height=0) setting_box_wrapper.grid(row=1, column=0) setting_box_row+=1 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 89f947e1..c84012bd 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -14,25 +14,32 @@ class _SettingBoxGenerator(): self.config_window = config_window + def _createSettingBoxFrame(self, parent_widget, for_var_label_text, for_var_desc_text): + setting_box_frame = CTkFrame(parent_widget, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=0, height=0) + # setting_box_frame = CTkFrame(parent_widget, corner_radius=0, fg_color="gray", width=0, height=0) - def _createSettingBoxFrameWrapper(self, setting_box_frame): + # "pady=(0,1)" is for bottom padding. It can be removed(override) when you do like "self.attr_name.grid(row=row, pady=0)" + # setting_box_frame.grid(column=0, padx=0, pady=0, sticky="ew") + setting_box_frame.grid(column=0, padx=0, pady=(0,1), sticky="ew") + + + # setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color="gray", width=0, height=0) setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=self.uism.SB__MAIN_WIDTH, height=0) setting_box_frame_wrapper.grid(row=0, column=0, padx=self.uism.SB__IPADX, pady=self.uism.SB__IPADY, sticky="ew") setting_box_frame_wrapper.grid_columnconfigure((0,1), weight=1, minsize=int(self.uism.SB__MAIN_WIDTH / 2)) - return setting_box_frame_wrapper - def _createSettingBoxFrame(self, parent_widget, for_var_label_text, for_var_desc_text): - setting_box_frame = CTkFrame(parent_widget, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=0, height=0) - setting_box_frame_wrapper = self._createSettingBoxFrameWrapper(setting_box_frame) + + # setting_box_frame_wrapper.grid(column=0, padx=0, pady=0) + setting_box_frame_wrapper.grid(row=0, column=0, padx=self.uism.SB__IPADX, pady=self.uism.SB__IPADY, sticky="ew") + self._setSettingBoxLabels(setting_box_frame_wrapper, for_var_label_text, for_var_desc_text) - # "pady=(0,1)" is for bottom padding. It can be removed(override) when you do like "self.attr_name.grid(row=row, pady=0)" - setting_box_frame.grid(column=0, padx=0, pady=(0,1), sticky="ew") return (setting_box_frame, setting_box_frame_wrapper) - def _setSettingBoxLabels(self, setting_box_frame, for_var_label_text, for_var_desc_text=None): + def _setSettingBoxLabels(self, setting_box_frame_wrapper, for_var_label_text, for_var_desc_text=None): - setting_box_labels_frame = CTkFrame(setting_box_frame, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=0, height=0) + setting_box_labels_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=0, height=0) + setting_box_labels_frame.grid(row=0, column=0, padx=0, pady=0, sticky="w") setting_box_label = CTkLabel( setting_box_labels_frame, @@ -59,9 +66,6 @@ class _SettingBoxGenerator(): ) self.setting_box_desc.grid(row=1, column=0, padx=0, pady=(self.uism.SB__DESC_TOP_PADY,0), sticky="ew") - setting_box_labels_frame.grid(row=0, column=0, padx=0, pady=0, sticky="w") - - def createSettingBoxDropdownMenu(self, parent_widget, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, variable=None, dropdown_menu_attr_name=None, dropdown_menu_values=None): (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, for_var_label_text, for_var_desc_text) From cfca2be71e216c1185c90058ff67f35bdab05242 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 20 Sep 2023 20:02:07 +0900 Subject: [PATCH 174/355] =?UTF-8?q?[Refactor]=20Config=20Window:=20Setting?= =?UTF-8?q?=20Box=E5=9B=9E=E3=82=8A=E3=80=82=E5=85=B1=E9=80=9A=E3=81=A7?= =?UTF-8?q?=E4=BD=BF=E3=81=86=E5=A4=89=E6=95=B0=E3=82=92=E3=82=AF=E3=83=A9?= =?UTF-8?q?=E3=82=B9=E5=A4=89=E6=95=B0=E3=81=AB=E3=81=97=E3=81=9F=E3=82=8A?= =?UTF-8?q?=E3=80=81ctm(=E8=89=B2)=E3=82=84uism(=E3=82=B5=E3=82=A4?= =?UTF-8?q?=E3=82=BA)=E3=82=82settings=E3=81=AB=E3=81=BE=E3=81=A8=E3=82=81?= =?UTF-8?q?=E3=81=9F=E3=82=8A=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_SettingBoxGenerator.py | 199 +++++++++--------- .../createSettingBox_AdvancedSettings.py | 4 +- .../createSettingBox_Appearance.py | 7 +- .../createSettingBox_Others.py | 8 +- .../createSettingBox_Mic.py | 10 +- .../createSettingBox_Speaker.py | 8 +- .../createSettingBox_Translation.py | 3 +- 7 files changed, 105 insertions(+), 134 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index c84012bd..70afcedd 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -5,17 +5,16 @@ from vrct_gui.ui_utils import createButtonWithImage from typing import Union class _SettingBoxGenerator(): - def __init__(self, config_window, settings): + def __init__(self, parent_widget, config_window, settings): self.IS_CONFIG_WINDOW_COMPACT_MODE = settings.IS_CONFIG_WINDOW_COMPACT_MODE - self.ctm = settings.ctm - self.uism = settings.uism - self.FONT_FAMILY = settings.FONT_FAMILY self.config_window = config_window + self.parent_widget = parent_widget + self.settings = settings - def _createSettingBoxFrame(self, parent_widget, for_var_label_text, for_var_desc_text): - setting_box_frame = CTkFrame(parent_widget, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=0, height=0) + def _createSettingBoxFrame(self, for_var_label_text, for_var_desc_text): + setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) # setting_box_frame = CTkFrame(parent_widget, corner_radius=0, fg_color="gray", width=0, height=0) # "pady=(0,1)" is for bottom padding. It can be removed(override) when you do like "self.attr_name.grid(row=row, pady=0)" @@ -24,13 +23,13 @@ class _SettingBoxGenerator(): # setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color="gray", width=0, height=0) - setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=self.uism.SB__MAIN_WIDTH, height=0) - setting_box_frame_wrapper.grid(row=0, column=0, padx=self.uism.SB__IPADX, pady=self.uism.SB__IPADY, sticky="ew") - setting_box_frame_wrapper.grid_columnconfigure((0,1), weight=1, minsize=int(self.uism.SB__MAIN_WIDTH / 2)) + setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=self.settings.uism.SB__MAIN_WIDTH, height=0) + setting_box_frame_wrapper.grid(row=0, column=0, padx=self.settings.uism.SB__IPADX, pady=self.settings.uism.SB__IPADY, sticky="ew") + setting_box_frame_wrapper.grid_columnconfigure((0,1), weight=1, minsize=int(self.settings.uism.SB__MAIN_WIDTH / 2)) # setting_box_frame_wrapper.grid(column=0, padx=0, pady=0) - setting_box_frame_wrapper.grid(row=0, column=0, padx=self.uism.SB__IPADX, pady=self.uism.SB__IPADY, sticky="ew") + setting_box_frame_wrapper.grid(row=0, column=0, padx=self.settings.uism.SB__IPADX, pady=self.settings.uism.SB__IPADY, sticky="ew") self._setSettingBoxLabels(setting_box_frame_wrapper, for_var_label_text, for_var_desc_text) @@ -38,7 +37,7 @@ class _SettingBoxGenerator(): def _setSettingBoxLabels(self, setting_box_frame_wrapper, for_var_label_text, for_var_desc_text=None): - setting_box_labels_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=0, height=0) + setting_box_labels_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) setting_box_labels_frame.grid(row=0, column=0, padx=0, pady=0, sticky="w") setting_box_label = CTkLabel( @@ -46,8 +45,8 @@ class _SettingBoxGenerator(): textvariable=for_var_label_text, anchor="w", # height=0, - font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__LABEL_FONT_SIZE, weight="normal"), - text_color=self.ctm.LABELS_TEXT_COLOR + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__LABEL_FONT_SIZE, weight="normal"), + text_color=self.settings.ctm.LABELS_TEXT_COLOR ) setting_box_label.grid(row=0, column=0, padx=0, pady=0, sticky="ew") @@ -60,17 +59,17 @@ class _SettingBoxGenerator(): anchor="w", justify="left", # height=0, - wraplength=int(self.uism.SB__MAIN_WIDTH / 2), - font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__DESC_FONT_SIZE, weight="normal"), - text_color=self.ctm.LABELS_DESC_TEXT_COLOR + wraplength=int(self.settings.uism.SB__MAIN_WIDTH / 2), + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__DESC_FONT_SIZE, weight="normal"), + text_color=self.settings.ctm.LABELS_DESC_TEXT_COLOR ) - self.setting_box_desc.grid(row=1, column=0, padx=0, pady=(self.uism.SB__DESC_TOP_PADY,0), sticky="ew") + self.setting_box_desc.grid(row=1, column=0, padx=0, pady=(self.settings.uism.SB__DESC_TOP_PADY,0), sticky="ew") - def createSettingBoxDropdownMenu(self, parent_widget, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, variable=None, dropdown_menu_attr_name=None, dropdown_menu_values=None): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, for_var_label_text, for_var_desc_text) + def createSettingBoxDropdownMenu(self, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, variable=None, dropdown_menu_attr_name=None, dropdown_menu_values=None): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) - setting_box_dropdown_menu_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) + setting_box_dropdown_menu_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) setting_box_dropdown_menu_frame.grid(row=0, column=1, padx=0, sticky="e") self.createOption_DropdownMenu( @@ -86,10 +85,10 @@ class _SettingBoxGenerator(): - def createSettingBoxSwitch(self, parent_widget, for_var_label_text, for_var_desc_text, switch_attr_name, is_checked, command): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, for_var_label_text, for_var_desc_text) + def createSettingBoxSwitch(self, for_var_label_text, for_var_desc_text, switch_attr_name, is_checked, command): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) - setting_box_switch_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) + setting_box_switch_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) setting_box_switch_frame.grid(row=0, column=1, padx=0, sticky="e") switch_widget = CTkSwitch( @@ -97,16 +96,16 @@ class _SettingBoxGenerator(): text=None, height=0, width=0, - corner_radius=int(self.uism.SB__SWITCH_BOX_HEIGHT/2), + corner_radius=int(self.settings.uism.SB__SWITCH_BOX_HEIGHT/2), border_width=0, - switch_height=self.uism.SB__SWITCH_BOX_HEIGHT, - switch_width=self.uism.SB__SWITCH_BOX_WIDTH, + switch_height=self.settings.uism.SB__SWITCH_BOX_HEIGHT, + switch_width=self.settings.uism.SB__SWITCH_BOX_WIDTH, onvalue=True, offvalue=False, command=command, - fg_color=self.ctm.SB__SWITCH_BOX_BG_COLOR, + fg_color=self.settings.ctm.SB__SWITCH_BOX_BG_COLOR, # bg_color="red", - progress_color=self.ctm.SB__SWITCH_BOX_ACTIVE_BG_COLOR, + progress_color=self.settings.ctm.SB__SWITCH_BOX_ACTIVE_BG_COLOR, ) setattr(self.config_window, switch_attr_name, switch_widget) @@ -118,31 +117,31 @@ class _SettingBoxGenerator(): - def createSettingBoxCheckbox(self, parent_widget, for_var_label_text, for_var_desc_text, checkbox_attr_name, variable, command): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, for_var_label_text, for_var_desc_text) + def createSettingBoxCheckbox(self, for_var_label_text, for_var_desc_text, checkbox_attr_name, variable, command): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) - setting_box_checkbox_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) + setting_box_checkbox_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) setting_box_checkbox_frame.grid(row=0, column=1, padx=0, sticky="e") checkbox_widget = CTkCheckBox( setting_box_checkbox_frame, text=None, width=0, - checkbox_width=self.uism.SB__CHECKBOX_SIZE, - checkbox_height=self.uism.SB__CHECKBOX_SIZE, + checkbox_width=self.settings.uism.SB__CHECKBOX_SIZE, + checkbox_height=self.settings.uism.SB__CHECKBOX_SIZE, onvalue=True, offvalue=False, variable=variable, command=command, - corner_radius=self.uism.SB__CHECKBOX_CORNER_RADIUS, - border_width=self.uism.SB__CHECKBOX_BORDER_WIDTH, - border_color=self.ctm.SB__CHECKBOX_BORDER_COLOR, - hover_color=self.ctm.SB__CHECKBOX_HOVER_COLOR, - checkmark_color=self.ctm.SB__CHECKBOX_CHECKMARK_COLOR, - fg_color=self.ctm.SB__CHECKBOX_CHECKED_COLOR, - # fg_color=self.ctm.SB__SWITCH_BOX_BG_COLOR, + corner_radius=self.settings.uism.SB__CHECKBOX_CORNER_RADIUS, + border_width=self.settings.uism.SB__CHECKBOX_BORDER_WIDTH, + border_color=self.settings.ctm.SB__CHECKBOX_BORDER_COLOR, + hover_color=self.settings.ctm.SB__CHECKBOX_HOVER_COLOR, + checkmark_color=self.settings.ctm.SB__CHECKBOX_CHECKMARK_COLOR, + fg_color=self.settings.ctm.SB__CHECKBOX_CHECKED_COLOR, + # fg_color=self.settings.ctm.SB__SWITCH_BOX_BG_COLOR, # bg_color="red", - # progress_color=self.ctm.SB__SWITCH_BOX_ACTIVE_BG_COLOR, + # progress_color=self.settings.ctm.SB__SWITCH_BOX_ACTIVE_BG_COLOR, ) setattr(self.config_window, checkbox_attr_name, checkbox_widget) @@ -157,10 +156,10 @@ class _SettingBoxGenerator(): - def createSettingBoxSlider(self, parent_widget, for_var_label_text, for_var_desc_text, slider_attr_name, slider_range, command, variable, slider_number_of_steps: Union[int, None] = None): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, for_var_label_text, for_var_desc_text) + def createSettingBoxSlider(self, for_var_label_text, for_var_desc_text, slider_attr_name, slider_range, command, variable, slider_number_of_steps: Union[int, None] = None): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) - setting_box_slider_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) + setting_box_slider_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) setting_box_slider_frame.grid(row=0, column=1, padx=0, sticky="e") slider_widget = CTkSlider( @@ -168,8 +167,8 @@ class _SettingBoxGenerator(): from_=slider_range[0], to=slider_range[1], number_of_steps=slider_number_of_steps, - button_color=self.ctm.SB__SLIDER_BUTTON_COLOR, - button_hover_color=self.ctm.SB__SLIDER_BUTTON_HOVERED_COLOR, + button_color=self.settings.ctm.SB__SLIDER_BUTTON_COLOR, + button_hover_color=self.settings.ctm.SB__SLIDER_BUTTON_HOVERED_COLOR, command=command, variable=variable, ) @@ -183,7 +182,7 @@ class _SettingBoxGenerator(): def createSettingBoxProgressbarXSlider(self, - parent_widget, for_var_label_text, for_var_desc_text, command, + for_var_label_text, for_var_desc_text, command, entry_attr_name, slider_attr_name, slider_range, progressbar_attr_name, @@ -197,17 +196,17 @@ class _SettingBoxGenerator(): ): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) - setting_box_progressbar_x_slider_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) + setting_box_progressbar_x_slider_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) setting_box_progressbar_x_slider_frame.grid(row=0, column=1, padx=0, sticky="e") - ENTRY_WIDTH = self.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH - BAR_WIDTH = self.uism.SB__PROGRESSBAR_X_SLIDER__BAR_WIDTH + ENTRY_WIDTH = self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH + BAR_WIDTH = self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BAR_WIDTH - BAR_PADDING = int(ENTRY_WIDTH + self.uism.SB__PROGRESSBAR_X_SLIDER__BAR_RIGHT_PADX) - BUTTON_PADDING = int(BAR_WIDTH + BAR_PADDING + self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_RIGHT_PADX) + BAR_PADDING = int(ENTRY_WIDTH + self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BAR_RIGHT_PADX) + BUTTON_PADDING = int(BAR_WIDTH + BAR_PADDING + self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_RIGHT_PADX) def adjusted_command__for_entry_bind__Any_KeyRelease(e): command(e.widget.get()) @@ -217,9 +216,9 @@ class _SettingBoxGenerator(): entry_widget = CTkEntry( setting_box_progressbar_x_slider_frame, width=ENTRY_WIDTH, - height=self.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT, + height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT, textvariable=entry_variable, - font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__ENTRY_FONT_SIZE, weight="normal"), + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__ENTRY_FONT_SIZE, weight="normal"), ) entry_widget.bind("", adjusted_command__for_entry_bind__Any_KeyRelease) @@ -228,7 +227,7 @@ class _SettingBoxGenerator(): # at least 2px is needed otherwise the slider button is gonna broken. - SLIDER_BORDER_WIDTH = max(2,self.uism.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_LENGTH) + SLIDER_BORDER_WIDTH = max(2,self.settings.uism.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_LENGTH) SLIDER_BUTTON_LENGTH = int(SLIDER_BORDER_WIDTH/2) slider_widget = CTkSlider( setting_box_progressbar_x_slider_frame, @@ -237,17 +236,17 @@ class _SettingBoxGenerator(): number_of_steps=slider_number_of_steps, command=adjusted_command__for_slider, variable=slider_variable, - height=self.uism.SB__PROGRESSBAR_X_SLIDER__SLIDER_HEIGHT, + height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__SLIDER_HEIGHT, width=BAR_WIDTH, border_width=0, button_length=SLIDER_BORDER_WIDTH, button_corner_radius=SLIDER_BUTTON_LENGTH, corner_radius=0, - button_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_COLOR, - button_hover_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_HOVERED_COLOR, - fg_color=self.ctm.SB__BG_COLOR, - progress_color=self.ctm.SB__BG_COLOR, - border_color=self.ctm.SB__BG_COLOR, + button_color=self.settings.ctm.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_COLOR, + button_hover_color=self.settings.ctm.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_HOVERED_COLOR, + fg_color=self.settings.ctm.SB__BG_COLOR, + progress_color=self.settings.ctm.SB__BG_COLOR, + border_color=self.settings.ctm.SB__BG_COLOR, ) slider_widget.grid(row=0, column=0, padx=(0, BAR_PADDING), sticky="e") setattr(self.config_window, slider_attr_name, slider_widget) @@ -258,7 +257,7 @@ class _SettingBoxGenerator(): progressbar_widget = CTkProgressBar( setting_box_progressbar_x_slider_frame, width=BAR_WIDTH, - height=self.uism.SB__PROGRESSBAR_X_SLIDER__PROGRESSBAR_HEIGHT, + height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__PROGRESSBAR_HEIGHT, corner_radius=0, ) setattr(self.config_window, progressbar_attr_name, progressbar_widget) @@ -280,10 +279,10 @@ class _SettingBoxGenerator(): - def createSettingBoxEntry(self, parent_widget, for_var_label_text, for_var_desc_text, entry_attr_name, entry_width, entry_bind__Any_KeyRelease, entry_textvariable): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(parent_widget, for_var_label_text, for_var_desc_text) + def createSettingBoxEntry(self, for_var_label_text, for_var_desc_text, entry_attr_name, entry_width, entry_bind__Any_KeyRelease, entry_textvariable): + (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) - setting_box_entry_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.ctm.SB__BG_COLOR) + setting_box_entry_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) setting_box_entry_frame.grid(row=0, column=1, padx=0, sticky="e") def adjusted_command__for_entry_bind__Any_KeyRelease(e): @@ -292,9 +291,9 @@ class _SettingBoxGenerator(): entry_widget = CTkEntry( setting_box_entry_frame, width=entry_width, - height=self.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT, + height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT, textvariable=entry_textvariable, - font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__ENTRY_FONT_SIZE, weight="normal"), + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__ENTRY_FONT_SIZE, weight="normal"), ) entry_widget.bind("", adjusted_command__for_entry_bind__Any_KeyRelease) setattr(self.config_window, entry_attr_name, entry_widget) @@ -307,8 +306,8 @@ class _SettingBoxGenerator(): # if setting_box_type == "dropdown_menu_x_dropdown_menu": - # self.setting_box_dropdown_menu_x_dropdown_menu = CTkFrame(self.setting_box, corner_radius=0, fg_color=self.ctm.SB__BG_COLOR, width=0, height=0) - # self.setting_box_dropdown_menu_x_dropdown_menu.grid(row=0, column=1, padx=(0, self.uism.SB__RIGHT_PADX), rowspan=2, sticky="e") + # self.setting_box_dropdown_menu_x_dropdown_menu = CTkFrame(self.setting_box, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) + # self.setting_box_dropdown_menu_x_dropdown_menu.grid(row=0, column=1, padx=(0, self.settings.uism.SB__RIGHT_PADX), rowspan=2, sticky="e") @@ -316,7 +315,7 @@ class _SettingBoxGenerator(): # self.optionmenu_label_left = CTkLabel( # self.setting_box_dropdown_menu_x_dropdown_menu, # text=kwargs["left_dropdown_menu_label"], - # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), + # font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), # ) # self.optionmenu_label_left.grid(row=0, column=0) @@ -330,7 +329,7 @@ class _SettingBoxGenerator(): # self.optionmenu_label_right = CTkLabel( # self.setting_box_dropdown_menu_x_dropdown_menu, # text=kwargs["right_dropdown_menu_label"], - # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), + # font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), # ) # self.optionmenu_label_right.grid(row=0, column=2) @@ -355,10 +354,10 @@ class _SettingBoxGenerator(): # self.setting_box_dropdown_menu_x_dropdown_menu, # text="-->", # # anchor="w", - # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), - # text_color=self.ctm.LABELS_TEXT_COLOR + # font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), + # text_color=self.settings.ctm.LABELS_TEXT_COLOR # ) - # self.the_label_between_optionmenu.grid(row=1, column=1, padx=self.uism.SB__RIGHT_PADX/2) + # self.the_label_between_optionmenu.grid(row=1, column=1, padx=self.settings.uism.SB__RIGHT_PADX/2) # self.createOption_DropdownMenu( @@ -378,27 +377,27 @@ class _SettingBoxGenerator(): # if setting_box_type == "radio_buttons": # self.setting_box_radio_buttons_frame = CTkFrame(self.setting_box, corner_radius=0, width=0, height=0) - # self.setting_box_radio_buttons_frame.grid(row=0, column=1, padx=(0, self.uism.SB__RIGHT_PADX), rowspan=2, sticky="e") + # self.setting_box_radio_buttons_frame.grid(row=0, column=1, padx=(0, self.settings.uism.SB__RIGHT_PADX), rowspan=2, sticky="e") # RADIO_BUTTON_RIGHT_PAD = 14 # self.setting_box_radio_button_1 = CTkRadioButton( # self.setting_box_radio_buttons_frame, # text="lorem ipsum", - # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__RADIO_BUTTON_FONT_SIZE, weight="normal") + # font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__RADIO_BUTTON_FONT_SIZE, weight="normal") # ) # self.setting_box_radio_button_1.grid(row=0, column=0, padx=(0,RADIO_BUTTON_RIGHT_PAD), sticky="e") # self.setting_box_radio_button_2 = CTkRadioButton( # self.setting_box_radio_buttons_frame, # text="lorem ipsum", - # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__RADIO_BUTTON_FONT_SIZE, weight="normal") + # font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__RADIO_BUTTON_FONT_SIZE, weight="normal") # ) # self.setting_box_radio_button_2.grid(row=0, column=1, padx=(0,RADIO_BUTTON_RIGHT_PAD), sticky="e") # self.setting_box_radio_button_3 = CTkRadioButton( # self.setting_box_radio_buttons_frame, # text="lorem ipsum", - # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__RADIO_BUTTON_FONT_SIZE, weight="normal") + # font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__RADIO_BUTTON_FONT_SIZE, weight="normal") # ) # self.setting_box_radio_button_3.grid(row=0, column=2, padx=(0,RADIO_BUTTON_RIGHT_PAD), sticky="e") @@ -415,13 +414,13 @@ class _SettingBoxGenerator(): option_menu_widget = CTkOptionMenu( setting_box_dropdown_menu_frame, - height=self.uism.SB__OPTIONMENU_HEIGHT, - width=self.uism.SB__OPTIONMENU_WIDTH, + height=self.settings.uism.SB__OPTIONMENU_HEIGHT, + width=self.settings.uism.SB__OPTIONMENU_WIDTH, values=dropdown_menu_values, - button_color=self.ctm.SB__OPTIONMENU_BG_COLOR, - button_hover_color=self.ctm.SB__OPTIONMENU_HOVERED_BG_COLOR, - fg_color=self.ctm.SB__OPTIONMENU_BG_COLOR, - font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), + button_color=self.settings.ctm.SB__OPTIONMENU_BG_COLOR, + button_hover_color=self.settings.ctm.SB__OPTIONMENU_HOVERED_BG_COLOR, + fg_color=self.settings.ctm.SB__OPTIONMENU_BG_COLOR, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), variable=variable, command=command, anchor="w", @@ -434,13 +433,13 @@ class _SettingBoxGenerator(): # dropdown_menu_widget = CTkScrollableDropdown( # option_menu_widget, # justify="left", - # width=self.uism.SB__DROPDOWN_MENU_WIDTH, + # width=self.settings.uism.SB__DROPDOWN_MENU_WIDTH, # min_show_button_num=6, # button_pady=0, - # frame_corner_radius=self.uism.SB__DROPDOWN_MENU_FRAME_CORNER_RADIUS, - # max_button_height=self.uism.SB__DROPDOWN_MENU_MAX_BUTTON_HEIGHT, - # max_height=self.uism.SB__DROPDOWN_MENU_FRAME_MAX_HEIGHT, - # font=CTkFont(family=self.FONT_FAMILY, size=self.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), + # frame_corner_radius=self.settings.uism.SB__DROPDOWN_MENU_FRAME_CORNER_RADIUS, + # max_button_height=self.settings.uism.SB__DROPDOWN_MENU_MAX_BUTTON_HEIGHT, + # max_height=self.settings.uism.SB__DROPDOWN_MENU_FRAME_MAX_HEIGHT, + # font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), # command=adjustedCommand, # ) @@ -462,12 +461,12 @@ class _SettingBoxGenerator(): def _createPassiveButtonForProgressbarXSlider(self, setting_box_progressbar_x_slider_frame, BUTTON_PADDING, button_command, button_image_file): button_wrapper = createButtonWithImage( parent_widget=setting_box_progressbar_x_slider_frame, - button_fg_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR, - button_enter_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_HOVERED_COLOR, - button_clicked_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_CLICKED_COLOR, + button_fg_color=self.settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR, + button_enter_color=self.settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_HOVERED_COLOR, + button_clicked_color=self.settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_CLICKED_COLOR, button_image_file=button_image_file, - button_image_size=self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE, - button_ipadxy=self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY, + button_image_size=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE, + button_ipadxy=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY, button_command=button_command, shape="circle", ) @@ -480,12 +479,12 @@ class _SettingBoxGenerator(): def _createActiveButtonForProgressbarXSlider(self, setting_box_progressbar_x_slider_frame, BUTTON_PADDING, button_command, button_image_file): button_wrapper = createButtonWithImage( parent_widget=setting_box_progressbar_x_slider_frame, - button_fg_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_COLOR, - button_enter_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_HOVERED_COLOR, - button_clicked_color=self.ctm.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_CLICKED_COLOR, + button_fg_color=self.settings.ctm.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_COLOR, + button_enter_color=self.settings.ctm.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_HOVERED_COLOR, + button_clicked_color=self.settings.ctm.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_CLICKED_COLOR, button_image_file=button_image_file, - button_image_size=self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE, - button_ipadxy=self.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY, + button_image_size=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE, + button_ipadxy=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY, button_command=button_command, shape="circle", ) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py index c66d1e86..28895d3c 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py @@ -3,7 +3,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settings, view_variable): - sbg = _SettingBoxGenerator(config_window, settings) + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings) createSettingBoxEntry = sbg.createSettingBoxEntry @@ -15,7 +15,6 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin row=0 config_window.sb__ip_address = createSettingBoxEntry( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_OSC_IP_ADDRESS, for_var_desc_text=view_variable.VAR_DESC_OSC_IP_ADDRESS, entry_attr_name="sb__entry_ip_address", @@ -28,7 +27,6 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin config_window.sb__port = createSettingBoxEntry( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_OSC_PORT, for_var_desc_text=view_variable.VAR_DESC_OSC_PORT, entry_attr_name="sb__entry_port", diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index 80ace904..ab596c34 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -3,7 +3,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, view_variable): - sbg = _SettingBoxGenerator(config_window, settings) + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxSlider = sbg.createSettingBoxSlider @@ -27,7 +27,6 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi row=0 config_window.sb__transparency = createSettingBoxSlider( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_TRANSPARENCY, for_var_desc_text=view_variable.VAR_DESC_TRANSPARENCY, slider_attr_name="sb__transparency_slider", @@ -40,7 +39,6 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi config_window.sb__appearance_theme = createSettingBoxDropdownMenu( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_APPEARANCE_THEME, for_var_desc_text=view_variable.VAR_DESC_APPEARANCE_THEME, optionmenu_attr_name="sb__optionmenu_appearance_theme", @@ -55,7 +53,6 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi config_window.sb__ui_scaling = createSettingBoxDropdownMenu( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_UI_SCALING, for_var_desc_text=view_variable.VAR_DESC_UI_SCALING, optionmenu_attr_name="sb__optionmenu_ui_scaling", @@ -69,7 +66,6 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi config_window.sb__font_family = createSettingBoxDropdownMenu( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_FONT_FAMILY, for_var_desc_text=view_variable.VAR_DESC_FONT_FAMILY, optionmenu_attr_name="sb__optionmenu_font_family", @@ -82,7 +78,6 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi config_window.sb__ui_language = createSettingBoxDropdownMenu( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_UI_LANGUAGE, for_var_desc_text=view_variable.VAR_DESC_UI_LANGUAGE, optionmenu_attr_name="sb__optionmenu_ui_language", diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index b125aa75..afa79980 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -3,7 +3,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_variable): - sbg = _SettingBoxGenerator(config_window, settings) + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings) createSettingBoxCheckbox = sbg.createSettingBoxCheckbox createSettingBoxEntry = sbg.createSettingBoxEntry @@ -30,7 +30,6 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v row=0 config_window.sb__auto_clear_message_box = createSettingBoxCheckbox( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_ENABLE_AUTO_CLEAR_MESSAGE_BOX, for_var_desc_text=view_variable.VAR_DESC_ENABLE_AUTO_CLEAR_MESSAGE_BOX, checkbox_attr_name="sb__checkbox_auto_clear_message_box", @@ -42,7 +41,6 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v config_window.sb__notice_xsoverlay = createSettingBoxCheckbox( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_ENABLE_NOTICE_XSOVERLAY, for_var_desc_text=view_variable.VAR_DESC_ENABLE_NOTICE_XSOVERLAY, checkbox_attr_name="sb__checkbox_notice_xsoverlay", @@ -54,7 +52,6 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v config_window.sb__auto_export_message_logs = createSettingBoxCheckbox( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, for_var_desc_text=view_variable.VAR_DESC_ENABLE_AUTO_EXPORT_MESSAGE_LOGS, checkbox_attr_name="sb__checkbox_auto_export_message_logs", @@ -66,7 +63,6 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v config_window.sb__message_format = createSettingBoxEntry( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_MESSAGE_FORMAT, for_var_desc_text=view_variable.VAR_DESC_MESSAGE_FORMAT, entry_attr_name="sb__entry_message_format", @@ -79,7 +75,6 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v config_window.sb__enable_send_message_to_vrc = createSettingBoxCheckbox( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_ENABLE_SEND_MESSAGE_TO_VRC, for_var_desc_text=view_variable.VAR_DESC_ENABLE_SEND_MESSAGE_TO_VRC, checkbox_attr_name="sb__checkbox_enable_send_message_to_vrc", @@ -90,7 +85,6 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v row+=1 config_window.sb__startup_osc_enabled_check = createSettingBoxCheckbox( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_STARTUP_OSC_ENABLED_CHECK, for_var_desc_text=view_variable.VAR_DESC_STARTUP_OSC_ENABLED_CHECK, checkbox_attr_name="sb__checkbox_startup_osc_enabled_check", diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 82731054..852f92e7 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -3,7 +3,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_variable): - sbg = _SettingBoxGenerator(config_window, settings) + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxCheckbox = sbg.createSettingBoxCheckbox createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider @@ -43,7 +43,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari row=0 # Mic Host と Mic Device は一つの項目として引っ付ける予定 config_window.sb__mic_host = createSettingBoxDropdownMenu( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_MIC_HOST, for_var_desc_text=view_variable.VAR_DESC_MIC_HOST, optionmenu_attr_name="sb__optionmenu_mic_host", @@ -56,7 +55,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari row+=1 config_window.sb__mic_device = createSettingBoxDropdownMenu( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_MIC_DEVICE, for_var_desc_text=view_variable.VAR_DESC_MIC_DEVICE, optionmenu_attr_name="sb__optionmenu_mic_device", @@ -70,7 +68,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari config_window.sb__mic_energy_threshold = createSettingBoxProgressbarXSlider( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_MIC_ENERGY_THRESHOLD, for_var_desc_text=view_variable.VAR_DESC_MIC_ENERGY_THRESHOLD, command=slider_input_mic_energy_threshold_callback, @@ -96,7 +93,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari # Mic Dynamic Energy Thresholdも上に引っ付ける予定 config_window.sb__mic_dynamic_energy_threshold = createSettingBoxCheckbox( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD, for_var_desc_text=view_variable.VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD, checkbox_attr_name="sb__checkbox_mic_dynamic_energy_threshold", @@ -109,7 +105,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari # 以下3つも一つの項目にまとめるかもしれない config_window.sb__mic_record_timeout = createSettingBoxEntry( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_MIC_RECORD_TIMEOUT, for_var_desc_text=view_variable.VAR_DESC_MIC_RECORD_TIMEOUT, entry_attr_name="sb__entry_mic_record_timeout", @@ -121,7 +116,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari row+=1 config_window.sb__mic_phrase_timeout = createSettingBoxEntry( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_MIC_PHRASE_TIMEOUT, for_var_desc_text=view_variable.VAR_DESC_MIC_PHRASE_TIMEOUT, entry_attr_name="sb__entry_mic_phrase_timeout", @@ -133,7 +127,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari row+=1 config_window.sb__mic_max_phrases = createSettingBoxEntry( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_MIC_MAX_PHRASES, for_var_desc_text=view_variable.VAR_DESC_MIC_MAX_PHRASES, entry_attr_name="sb__entry_mic_max_phrases", @@ -147,7 +140,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari config_window.sb__mic_word_filter = createSettingBoxEntry( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_MIC_WORD_FILTER, for_var_desc_text=view_variable.VAR_DESC_MIC_WORD_FILTER, entry_attr_name="sb__entry_mic_word_filter", diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 60d6d168..064535a7 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -3,7 +3,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_variable): - sbg = _SettingBoxGenerator(config_window, settings) + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxCheckbox = sbg.createSettingBoxCheckbox createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider @@ -37,7 +37,6 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ row=0 config_window.sb__speaker_device = createSettingBoxDropdownMenu( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_SPEAKER_DEVICE, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_DEVICE, optionmenu_attr_name="sb__optionmenu_speaker_device", @@ -51,7 +50,6 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ config_window.sb__speaker_energy_threshold = createSettingBoxProgressbarXSlider( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_SPEAKER_ENERGY_THRESHOLD, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_ENERGY_THRESHOLD, command=slider_input_speaker_energy_threshold_callback, @@ -77,7 +75,6 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ # Speaker Dynamic Energy Thresholdも上に引っ付ける予定 config_window.sb__speaker_dynamic_energy_threshold = createSettingBoxCheckbox( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, checkbox_attr_name="sb__checkbox_speaker_dynamic_energy_threshold", @@ -90,7 +87,6 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ # 以下3つも一つの項目にまとめるかもしれない config_window.sb__speaker_record_timeout = createSettingBoxEntry( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_SPEAKER_RECORD_TIMEOUT, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_RECORD_TIMEOUT, entry_attr_name="sb__entry_speaker_record_timeout", @@ -102,7 +98,6 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ row+=1 config_window.sb__speaker_phrase_timeout = createSettingBoxEntry( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_SPEAKER_PHRASE_TIMEOUT, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_PHRASE_TIMEOUT, entry_attr_name="sb__entry_speaker_phrase_timeout", @@ -114,7 +109,6 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ row+=1 config_window.sb__speaker_max_phrases = createSettingBoxEntry( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_SPEAKER_MAX_PHRASES, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_MAX_PHRASES, entry_attr_name="sb__entry_speaker_max_phrases", diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index 7096bbc2..48b01e95 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -3,7 +3,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Translation(setting_box_wrapper, config_window, settings, view_variable): - sbg = _SettingBoxGenerator(config_window, settings) + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings) createSettingBoxEntry = sbg.createSettingBoxEntry @@ -13,7 +13,6 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings, v row=0 config_window.sb__deepl_authkey = createSettingBoxEntry( - parent_widget=setting_box_wrapper, for_var_label_text=view_variable.VAR_LABEL_DEEPL_AUTH_KEY, for_var_desc_text=view_variable.VAR_DESC_DEEPL_AUTH_KEY, entry_attr_name="sb__deepl_authkey", From 4176ec4cb0e9d6f29648e3d813d6a76bac507762 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 20 Sep 2023 21:01:58 +0900 Subject: [PATCH 175/355] =?UTF-8?q?[Refactor]=20IS=5FCONFIG=5FWINDOW=5FCOM?= =?UTF-8?q?PACT=5FMODE=20=E3=82=92view=5Fvariable=E3=81=B8=E7=A7=BB?= =?UTF-8?q?=E5=8B=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 8 ++++---- .../_createSettingBoxCompactModeButton.py | 2 +- .../setting_box_containers/_SettingBoxGenerator.py | 7 +++---- .../createSettingBox_AdvancedSettings.py | 2 +- .../setting_box_appearance/createSettingBox_Appearance.py | 2 +- .../setting_box_others/createSettingBox_Others.py | 2 +- .../setting_box_transcription/createSettingBox_Mic.py | 2 +- .../setting_box_transcription/createSettingBox_Speaker.py | 2 +- .../createSettingBox_Translation.py | 2 +- 9 files changed, 14 insertions(+), 15 deletions(-) diff --git a/view.py b/view.py index 13de0d26..571653e5 100644 --- a/view.py +++ b/view.py @@ -32,7 +32,6 @@ class View(): self.settings.config_window = SimpleNamespace( ctm=all_ctm.config_window, uism=all_uism.config_window, - IS_CONFIG_WINDOW_COMPACT_MODE=config.IS_CONFIG_WINDOW_COMPACT_MODE, **common_args ) @@ -102,6 +101,8 @@ class View(): # Config Window ACTIVE_SETTING_BOX_TAB_ATTR_NAME="side_menu_tab_appearance", CALLBACK_SELECTED_SETTING_BOX_TAB=None, + IS_CONFIG_WINDOW_COMPACT_MODE=config.IS_CONFIG_WINDOW_COMPACT_MODE, + # Appearance Tab VAR_LABEL_TRANSPARENCY=StringVar(value="Transparency"), VAR_DESC_TRANSPARENCY=StringVar(value="Change the window's transparency. 50% to 100%. (Default: 100%)"), @@ -603,9 +604,8 @@ class View(): - @staticmethod - def reloadConfigWindowSettingBoxContainer(): - vrct_gui.config_window.settings.IS_CONFIG_WINDOW_COMPACT_MODE = config.IS_CONFIG_WINDOW_COMPACT_MODE + def reloadConfigWindowSettingBoxContainer(self): + self.view_variable.IS_CONFIG_WINDOW_COMPACT_MODE = config.IS_CONFIG_WINDOW_COMPACT_MODE vrct_gui.config_window.reloadConfigWindowSettingBoxContainer() diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py index bc61811b..c9fbfd06 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py @@ -60,7 +60,7 @@ def _createSettingBoxCompactModeButton(parent_widget, config_window, settings, v progress_color=settings.ctm.SB__SWITCH_BOX_ACTIVE_BG_COLOR, # SB__SWITCH_BOX_ACTIVE_BG_COLOR is for SB. change it later. ) - config_window.setting_box_compact_mode_switch_box.select() if settings.IS_CONFIG_WINDOW_COMPACT_MODE else config_window.setting_box_compact_mode_switch_box.deselect() + config_window.setting_box_compact_mode_switch_box.select() if view_variable.IS_CONFIG_WINDOW_COMPACT_MODE else config_window.setting_box_compact_mode_switch_box.deselect() config_window.setting_box_compact_mode_switch_box.grid(row=0, column=0) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 70afcedd..6c7f4715 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -5,9 +5,8 @@ from vrct_gui.ui_utils import createButtonWithImage from typing import Union class _SettingBoxGenerator(): - def __init__(self, parent_widget, config_window, settings): - - self.IS_CONFIG_WINDOW_COMPACT_MODE = settings.IS_CONFIG_WINDOW_COMPACT_MODE + def __init__(self, parent_widget, config_window, settings, view_variable): + self.view_variable = view_variable self.config_window = config_window self.parent_widget = parent_widget self.settings = settings @@ -50,7 +49,7 @@ class _SettingBoxGenerator(): ) setting_box_label.grid(row=0, column=0, padx=0, pady=0, sticky="ew") - if for_var_desc_text == None or self.IS_CONFIG_WINDOW_COMPACT_MODE is True: + if for_var_desc_text == None or self.view_variable.IS_CONFIG_WINDOW_COMPACT_MODE is True: pass else: self.setting_box_desc = CTkLabel( diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py index 28895d3c..6dcdca4e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py @@ -3,7 +3,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settings, view_variable): - sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings) + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) createSettingBoxEntry = sbg.createSettingBoxEntry diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index ab596c34..d655a26d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -3,7 +3,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, view_variable): - sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings) + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxSlider = sbg.createSettingBoxSlider diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index afa79980..62715802 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -3,7 +3,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_variable): - sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings) + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) createSettingBoxCheckbox = sbg.createSettingBoxCheckbox createSettingBoxEntry = sbg.createSettingBoxEntry diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 852f92e7..1bb45128 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -3,7 +3,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_variable): - sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings) + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxCheckbox = sbg.createSettingBoxCheckbox createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 064535a7..8688ac10 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -3,7 +3,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_variable): - sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings) + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxCheckbox = sbg.createSettingBoxCheckbox createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index 48b01e95..c8b502a0 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -3,7 +3,7 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Translation(setting_box_wrapper, config_window, settings, view_variable): - sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings) + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) createSettingBoxEntry = sbg.createSettingBoxEntry From bd9f0bb58c088042199475be99e8a57ce125c995 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 20 Sep 2023 23:20:42 +0900 Subject: [PATCH 176/355] [Refactor] remove the code that is no longer in use --- vrct_gui/_changeConfigWindowWidgetsStatus.py | 3 --- vrct_gui/_changeMainWindowWidgetsStatus.py | 3 --- vrct_gui/config_window/widgets/createConfigWindowTitle.py | 3 --- .../setting_box_containers/_SettingBoxGenerator.py | 2 +- .../setting_box_appearance/createSettingBox_Appearance.py | 3 --- .../setting_box_transcription/createSettingBox_Mic.py | 2 -- .../setting_box_transcription/createSettingBox_Speaker.py | 1 - .../widgets/_create_sidebar/createSidebarFeatures.py | 3 +-- .../widgets/_create_sidebar/createSidebarLanguagesSettings.py | 4 ++-- vrct_gui/main_window/widgets/create_entry_message_box.py | 1 - .../main_window/widgets/create_minimize_sidebar_button.py | 2 +- vrct_gui/main_window/widgets/create_textbox.py | 2 +- vrct_gui/vrct_gui.py | 2 -- 13 files changed, 6 insertions(+), 25 deletions(-) diff --git a/vrct_gui/_changeConfigWindowWidgetsStatus.py b/vrct_gui/_changeConfigWindowWidgetsStatus.py index 6eb2bd57..da2da440 100644 --- a/vrct_gui/_changeConfigWindowWidgetsStatus.py +++ b/vrct_gui/_changeConfigWindowWidgetsStatus.py @@ -1,8 +1,5 @@ from customtkinter import CTkImage -from .ui_utils import getImageFileFromUiUtils - - def _changeConfigWindowWidgetsStatus(config_window, settings, view_variable, status, target_names): if target_names == "All": target_names = ["mic_energy_threshold_check_button", "speaker_energy_threshold_check_button"] diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index eeea8cee..43599b6e 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -1,8 +1,5 @@ from customtkinter import CTkImage -from .ui_utils import getImageFileFromUiUtils - - def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, target_names): COMPACT_MODE_ICON_SIZE_TUPLES = (settings.COMPACT_MODE_ICON_SIZE, settings.COMPACT_MODE_ICON_SIZE) diff --git a/vrct_gui/config_window/widgets/createConfigWindowTitle.py b/vrct_gui/config_window/widgets/createConfigWindowTitle.py index 4c10dbce..302d8ccc 100644 --- a/vrct_gui/config_window/widgets/createConfigWindowTitle.py +++ b/vrct_gui/config_window/widgets/createConfigWindowTitle.py @@ -1,8 +1,5 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkImage -from ...ui_utils import getImageFileFromUiUtils - - def createConfigWindowTitle(config_window, settings): config_window.grid_columnconfigure(0, weight=0, minsize=settings.uism.TOP_BAR_SIDE__WIDTH) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 6c7f4715..8e002b55 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -65,7 +65,7 @@ class _SettingBoxGenerator(): self.setting_box_desc.grid(row=1, column=0, padx=0, pady=(self.settings.uism.SB__DESC_TOP_PADY,0), sticky="ew") - def createSettingBoxDropdownMenu(self, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, variable=None, dropdown_menu_attr_name=None, dropdown_menu_values=None): + def createSettingBoxDropdownMenu(self, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, variable=None, dropdown_menu_values=None): (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) setting_box_dropdown_menu_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index d655a26d..25a6c237 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -42,7 +42,6 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi for_var_label_text=view_variable.VAR_LABEL_APPEARANCE_THEME, for_var_desc_text=view_variable.VAR_DESC_APPEARANCE_THEME, optionmenu_attr_name="sb__optionmenu_appearance_theme", - dropdown_menu_attr_name="sb__dropdown_appearance_theme", dropdown_menu_values=view_variable.LIST_APPEARANCE_THEME, command=lambda value: optionmenu_appearance_theme_callback(value), variable=view_variable.VAR_APPEARANCE_THEME, @@ -56,7 +55,6 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi for_var_label_text=view_variable.VAR_LABEL_UI_SCALING, for_var_desc_text=view_variable.VAR_DESC_UI_SCALING, optionmenu_attr_name="sb__optionmenu_ui_scaling", - dropdown_menu_attr_name="sb__dropdown_ui_scaling", dropdown_menu_values=view_variable.LIST_UI_SCALING, command=lambda value: optionmenu_ui_scaling_callback(value), variable=view_variable.VAR_UI_SCALING, @@ -81,7 +79,6 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi for_var_label_text=view_variable.VAR_LABEL_UI_LANGUAGE, for_var_desc_text=view_variable.VAR_DESC_UI_LANGUAGE, optionmenu_attr_name="sb__optionmenu_ui_language", - dropdown_menu_attr_name="sb__dropdown_ui_language", dropdown_menu_values=view_variable.LIST_UI_LANGUAGE, command=lambda value: optionmenu_ui_language_callback(value), variable=view_variable.VAR_UI_LANGUAGE, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 1bb45128..66160591 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -46,7 +46,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_label_text=view_variable.VAR_LABEL_MIC_HOST, for_var_desc_text=view_variable.VAR_DESC_MIC_HOST, optionmenu_attr_name="sb__optionmenu_mic_host", - dropdown_menu_attr_name="sb__dropdown_mic_host", dropdown_menu_values=view_variable.LIST_MIC_HOST, command=lambda value: optionmenu_mic_host_callback(value), variable=view_variable.VAR_MIC_HOST, @@ -58,7 +57,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_label_text=view_variable.VAR_LABEL_MIC_DEVICE, for_var_desc_text=view_variable.VAR_DESC_MIC_DEVICE, optionmenu_attr_name="sb__optionmenu_mic_device", - dropdown_menu_attr_name="sb__dropdown_mic_device", dropdown_menu_values=view_variable.LIST_MIC_DEVICE, command=lambda value: optionmenu_input_mic_device_callback(value), variable=view_variable.VAR_MIC_DEVICE, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 8688ac10..0a04eca7 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -40,7 +40,6 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ for_var_label_text=view_variable.VAR_LABEL_SPEAKER_DEVICE, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_DEVICE, optionmenu_attr_name="sb__optionmenu_speaker_device", - dropdown_menu_attr_name="sb__dropdown_speaker_device", dropdown_menu_values=view_variable.LIST_SPEAKER_DEVICE, command=lambda value: optionmenu_input_speaker_device_callback(value), variable=view_variable.VAR_SPEAKER_DEVICE, diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py index eeb373d9..1785f6bb 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py @@ -1,10 +1,9 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkSwitch, CTkImage -from ....ui_utils import getImageFileFromUiUtils, openImageKeepAspectRatio, retag, getLatestHeight, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressAndReleaseFunction +from ....ui_utils import openImageKeepAspectRatio, retag, getLatestHeight, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressAndReleaseFunction from utils import callFunctionIfCallable - def createSidebarFeatures(settings, main_window, view_variable): def toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, mark): diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 24dcd842..9501f9f6 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -1,6 +1,6 @@ -from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkImage +from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkImage -from ....ui_utils import getImageFileFromUiUtils, bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, bindButtonFunctionAndColor, switchActiveTabAndPassiveTab, switchTabsColor +from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor from utils import callFunctionIfCallable diff --git a/vrct_gui/main_window/widgets/create_entry_message_box.py b/vrct_gui/main_window/widgets/create_entry_message_box.py index 7bdfb491..0e95c550 100644 --- a/vrct_gui/main_window/widgets/create_entry_message_box.py +++ b/vrct_gui/main_window/widgets/create_entry_message_box.py @@ -1,4 +1,3 @@ - from customtkinter import CTkFont, CTkFrame, CTkEntry def createEntryMessageBox(settings, main_window): diff --git a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py index 8a08c1f0..c5025b76 100644 --- a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py +++ b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py @@ -1,7 +1,7 @@ from customtkinter import CTkFrame, CTkLabel, CTkImage -from ...ui_utils import getImageFileFromUiUtils, bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction +from ...ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction from utils import callFunctionIfCallable diff --git a/vrct_gui/main_window/widgets/create_textbox.py b/vrct_gui/main_window/widgets/create_textbox.py index 404220a4..76d50a8b 100644 --- a/vrct_gui/main_window/widgets/create_textbox.py +++ b/vrct_gui/main_window/widgets/create_textbox.py @@ -1,6 +1,6 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkTextbox -from ...ui_utils import getLatestWidth, getLongestText, bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, _setDefaultActiveTab, switchActiveTabAndPassiveTab, switchTabsColor +from ...ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, _setDefaultActiveTab, switchActiveTabAndPassiveTab, switchTabsColor def createTextbox(settings, main_window, view_variable): diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index ae343c2c..9701eb2e 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -12,8 +12,6 @@ from .main_window import createMainWindowWidgets from .config_window import ConfigWindow from .ui_utils import _setDefaultActiveTab -from .main_window.widgets import createMinimizeSidebarButton - from utils import callFunctionIfCallable class VRCT_GUI(CTk): From 48fd43b2a9b2e18d87ba9b1f75b2fec681204cb4 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 21 Sep 2023 14:55:09 +0900 Subject: [PATCH 177/355] =?UTF-8?q?[bugfix]=20Config=20Window:=20attribute?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=83=9F=E3=82=B9=E4=BF=AE=E6=AD=A3=E3=82=84?= =?UTF-8?q?=E8=A1=A8=E8=A8=98=E6=8F=BA=E3=82=8C=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting_box_appearance/createSettingBox_Appearance.py | 2 +- .../setting_box_translation/createSettingBox_Translation.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index 25a6c237..7109496e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -29,7 +29,7 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi config_window.sb__transparency = createSettingBoxSlider( for_var_label_text=view_variable.VAR_LABEL_TRANSPARENCY, for_var_desc_text=view_variable.VAR_DESC_TRANSPARENCY, - slider_attr_name="sb__transparency_slider", + slider_attr_name="sb__slider_transparency", slider_range=view_variable.SLIDER_RANGE_TRANSPARENCY, command=lambda value: slider_transparency_callback(value), variable=view_variable.VAR_TRANSPARENCY, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index c8b502a0..343f309d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -15,7 +15,7 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings, v config_window.sb__deepl_authkey = createSettingBoxEntry( for_var_label_text=view_variable.VAR_LABEL_DEEPL_AUTH_KEY, for_var_desc_text=view_variable.VAR_DESC_DEEPL_AUTH_KEY, - entry_attr_name="sb__deepl_authkey", + entry_attr_name="sb__entry_deepl_authkey", entry_width=settings.uism.SB__ENTRY_WIDTH_300, entry_bind__Any_KeyRelease=lambda value: deepl_authkey_callback(value), entry_textvariable=view_variable.VAR_DEEPL_AUTH_KEY, From 617f1a4d08630e5020d660f39f04ff4198f2a760 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 21 Sep 2023 16:00:55 +0900 Subject: [PATCH 178/355] =?UTF-8?q?[typo]=20Config=20Window:=20=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E9=A0=85=E7=9B=AE=E3=81=AEtypo=E3=82=84=E5=B0=91?= =?UTF-8?q?=E3=81=97=E6=96=87=E6=B3=95=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/view.py b/view.py index 571653e5..60a654a9 100644 --- a/view.py +++ b/view.py @@ -157,7 +157,7 @@ class View(): VAR_MIC_DEVICE=StringVar(value=config.CHOICE_MIC_DEVICE), VAR_LABEL_MIC_ENERGY_THRESHOLD=StringVar(value="Mic Energy Threshold"), - VAR_DESC_MIC_ENERGY_THRESHOLD=StringVar(value="Slider to modify the threshold for activating voice input.\nPress the microphone button to start input and speak something, so you can adjust it while monitoring the actual volume. 0 to 2000 (Default: 300)"), + VAR_DESC_MIC_ENERGY_THRESHOLD=StringVar(value="Slider to modify the threshold for activating voice input.\nPress the microphone button to initiate input and speak, allowing you to adjust it while monitoring the actual volume. Range: 0 to 2000 (Default: 300)"), SLIDER_RANGE_MIC_ENERGY_THRESHOLD=(0, config.MAX_MIC_ENERGY_THRESHOLD), CALLBACK_CHECK_MIC_THRESHOLD=None, VAR_MIC_ENERGY_THRESHOLD__SLIDER=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), @@ -196,8 +196,8 @@ class View(): CALLBACK_SET_SPEAKER_DEVICE=None, VAR_SPEAKER_DEVICE=StringVar(value=config.CHOICE_SPEAKER_DEVICE), - VAR_LABEL_SPEAKER_ENERGY_THRESHOLD=StringVar(value="Mic Energy Threshold"), - VAR_DESC_SPEAKER_ENERGY_THRESHOLD=StringVar(value="Slider to modify the threshold for activating voice input.\nPress the headphones mark button to start input and speak something, so you can adjust it while monitoring the actual volume. 0 to 4000 (Default: 300)"), + VAR_LABEL_SPEAKER_ENERGY_THRESHOLD=StringVar(value="Speaker Energy Threshold"), + VAR_DESC_SPEAKER_ENERGY_THRESHOLD=StringVar(value="Slider to modify the threshold for activating voice input.\nPress the headphones mark button to start input and speak something, so you can adjust it while monitoring the actual volume. Range: 0 to 4000 (Default: 300)"), SLIDER_RANGE_SPEAKER_ENERGY_THRESHOLD=(0, config.MAX_SPEAKER_ENERGY_THRESHOLD), CALLBACK_CHECK_SPEAKER_THRESHOLD=None, VAR_SPEAKER_ENERGY_THRESHOLD__SLIDER=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), From 0344c9ae34d5e6faffc688a3b1e380fbf60da54e Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 21 Sep 2023 19:24:54 +0900 Subject: [PATCH 179/355] =?UTF-8?q?[bugfix]=20Config=20Window:=20=E8=AC=8E?= =?UTF-8?q?=E3=81=AE=E5=90=84=E9=A0=85=E7=9B=AE=E9=96=93=E3=81=AB=E3=80=81?= =?UTF-8?q?=E6=83=B3=E5=AE=9A=E3=82=88=E3=82=8A+1px=E3=83=9C=E3=83=BC?= =?UTF-8?q?=E3=83=80=E3=83=BC=E5=85=A5=E3=81=A3=E3=81=A6=E3=81=97=E3=81=BE?= =?UTF-8?q?=E3=81=86=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3(=E7=84=A1?= =?UTF-8?q?=E7=90=86=E3=82=84=E3=82=8A)=20=E5=81=B6=E7=84=B6=E8=A6=8B?= =?UTF-8?q?=E3=81=A4=E3=81=91=E3=81=9F=E6=96=B9=E6=B3=95=E3=81=A7=E3=81=99?= =?UTF-8?q?=E3=80=82=E8=A8=AD=E5=AE=9A=E7=94=BB=E9=9D=A2=E6=94=B9=E4=BF=AE?= =?UTF-8?q?=E3=81=AF=E3=81=84=E3=81=9A=E3=82=8C=E5=BF=85=E8=A6=81=E3=81=AA?= =?UTF-8?q?=E3=81=AE=E3=81=A7=E3=80=81=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88?= =?UTF-8?q?=E3=81=9A=E3=81=A8=E3=81=84=E3=81=86=E4=BF=AE=E6=AD=A3=E3=81=A7?= =?UTF-8?q?=E3=81=99=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting_box_containers/_SettingBoxGenerator.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 8e002b55..07da4153 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -14,7 +14,7 @@ class _SettingBoxGenerator(): def _createSettingBoxFrame(self, for_var_label_text, for_var_desc_text): setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) - # setting_box_frame = CTkFrame(parent_widget, corner_radius=0, fg_color="gray", width=0, height=0) + # setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color="gray", width=0, height=0) # "pady=(0,1)" is for bottom padding. It can be removed(override) when you do like "self.attr_name.grid(row=row, pady=0)" # setting_box_frame.grid(column=0, padx=0, pady=0, sticky="ew") @@ -30,6 +30,10 @@ class _SettingBoxGenerator(): # setting_box_frame_wrapper.grid(column=0, padx=0, pady=0) setting_box_frame_wrapper.grid(row=0, column=0, padx=self.settings.uism.SB__IPADX, pady=self.settings.uism.SB__IPADY, sticky="ew") + setting_box_frame_wrapper_border = CTkFrame(setting_box_frame, corner_radius=0, fg_color="red", width=0, height=0) + setting_box_frame_wrapper_border.grid(row=1, column=0, sticky="ew") + + self._setSettingBoxLabels(setting_box_frame_wrapper, for_var_label_text, for_var_desc_text) return (setting_box_frame, setting_box_frame_wrapper) From 9410dc8291704f1144ec5128e52e8b0f9a3ca0fa Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 21 Sep 2023 22:42:10 +0900 Subject: [PATCH 180/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20=E6=96=87?= =?UTF-8?q?=E5=AD=97=E8=B5=B7=E3=81=93=E3=81=97=E3=81=AEstop=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=81=AE=E3=83=90=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=0Bif=20self.mic=5Fenergy=5Frecorder=20!=3D=20None:=E3=81=93?= =?UTF-8?q?=E3=81=AE=E5=87=A6=E7=90=86=E3=81=8C=E5=95=8F=E9=A1=8C=E3=81=A0?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E3=81=AE=E3=81=A7=E9=96=A2=E9=80=A3=E3=81=97?= =?UTF-8?q?=E3=81=A6=E5=85=A8=E9=83=A8=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/model.py b/model.py index 19dc293d..26a01bef 100644 --- a/model.py +++ b/model.py @@ -55,8 +55,12 @@ class Model: def init(self): self.logger = None + self.mic_print_transcript = None + self.mic_audio_recorder = None self.mic_energy_recorder = None self.mic_energy_plot_progressbar = None + self.spk_print_transcript = None + self.spk_audio_recorder = None self.speaker_energy_recorder = None self.speaker_energy_plot_progressbar = None self.translator = Translator() @@ -284,9 +288,10 @@ class Model: def stopMicTranscript(self): if isinstance(self.mic_print_transcript, threadFnc): self.mic_print_transcript.stop() - if self.mic_audio_recorder.stop != None: + self.mic_print_transcript = None + if isinstance(self.mic_audio_recorder, SelectedMicRecorder): self.mic_audio_recorder.stop() - self.mic_audio_recorder.stop = None + self.mic_audio_recorder = None def startCheckMicEnergy(self, fnc): def sendMicEnergy(): @@ -309,9 +314,10 @@ class Model: def stopCheckMicEnergy(self): if isinstance(self.mic_energy_plot_progressbar, threadFnc): self.mic_energy_plot_progressbar.stop() - if self.mic_energy_recorder != None: + self.mic_energy_plot_progressbar = None + if isinstance(self.mic_energy_recorder, SelectedMicEnergyRecorder): self.mic_energy_recorder.stop() - self.mic_energy_recorder.stop = None + self.mic_energy_recorder = None def startSpeakerTranscript(self, fnc): spk_audio_queue = Queue() @@ -350,9 +356,10 @@ class Model: def stopSpeakerTranscript(self): if isinstance(self.spk_print_transcript, threadFnc): self.spk_print_transcript.stop() - if self.spk_audio_recorder.stop != None: + self.spk_print_transcript = None + if isinstance(self.spk_audio_recorder, SelectedSpeakerRecorder): self.spk_audio_recorder.stop() - self.spk_audio_recorder.stop = None + self.spk_audio_recorder = None def startCheckSpeakerEnergy(self, fnc): def sendSpeakerEnergy(): @@ -375,9 +382,10 @@ class Model: def stopCheckSpeakerEnergy(self): if isinstance(self.speaker_energy_plot_progressbar, threadFnc): self.speaker_energy_plot_progressbar.stop() - if self.speaker_energy_recorder != None: + self.speaker_energy_plot_progressbar = None + if isinstance(self.speaker_energy_recorder, SelectedSpeakeEnergyRecorder): self.speaker_energy_recorder.stop() - self.speaker_energy_recorder.stop != None + self.speaker_energy_recorder = None def notificationXSOverlay(self, message): xsoverlayForVRCT(content=f"{message}") From bcf9eba5e3d009f78d265e31db9fdb9afa459972 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 22 Sep 2023 16:44:12 +0900 Subject: [PATCH 181/355] =?UTF-8?q?=F0=9F=94=A7=20[Add]=20build.bat?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit pyinstallerでexe生成するためのbat --- build.bat | 1 + 1 file changed, 1 insertion(+) create mode 100644 build.bat diff --git a/build.bat b/build.bat new file mode 100644 index 00000000..9ee59eff --- /dev/null +++ b/build.bat @@ -0,0 +1 @@ +pyinstaller --onedir --onefile --windowed --clean --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --name VRCT --exclude-module numpy --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py \ No newline at end of file From 34c61291bb02c3e287af16a731a0630c6de68a71 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 23 Sep 2023 14:31:35 +0900 Subject: [PATCH 182/355] =?UTF-8?q?[Update]=20Main=20Window:=20Update?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E3=81=AEUI=E8=BF=BD=E5=8A=A0=E3=80=82?= =?UTF-8?q?=E8=A6=8B=E3=81=9F=E7=9B=AE=E3=81=A0=E3=81=91=E3=80=82(?= =?UTF-8?q?=E5=B8=B8=E3=81=AB=E8=A1=A8=E7=A4=BA=E7=8A=B6=E6=85=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- img/refresh_icon.png | Bin 0 -> 1487 bytes view.py | 3 + .../main_window/createMainWindowWidgets.py | 57 +++++++++++++++++- .../main_window/widgets/create_sidebar.py | 10 +++ vrct_gui/ui_managers/ImageFileManager.py | 1 + 5 files changed, 68 insertions(+), 3 deletions(-) create mode 100644 img/refresh_icon.png diff --git a/img/refresh_icon.png b/img/refresh_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..c5acad15a2097d399ea98438bb784d5d25bde461 GIT binary patch literal 1487 zcmV;=1u*)FP)GiuAvY1wdH`4h z0B-_7gwyO^UBI@>XOySOM4Ke)A)5!~RWD@{5OIPbI~;5C-dq*|O_6~W$#nXpB-*_c;@Mz`gI3kjb9LLk zM9Bm+g#53kg@_T+J2|Q)BHls3C}Vinq9~J9tKOIb0C@2D&sS5LRxKsf5s;gR-U9&k z3>m~Rm2>Cy*3QR87od~Bony&JhIp29ek|JBX=r1*L;h+Z#1TofD_aO6PNiJMNw<66@h#v8pHh#AW*Kg9+;Yo zxr6>n0D!2c^U(_la0dEz3MtQLgrvgx`eBdvZZXN{!q!WtEl-n)+W-J7t+ox9w(d>k z$}6~pBvH?zWW=wRgFFC0JefF7M0G?|7f&XR=gZ40pdr*>MMSlkG0yq1%Zcp-FMEDKcm?Bmicw#0!atC13mcd9yZOJrS)N^DwP)UK90rm%OPrhX*Q>TKXOltu{G<6IMm7 zVE_P8k9UGI)?u_UXRMiXW_+&p5$gKJ}5mlyyc4E;lhKo>=x_cQj!M% zkRg?q{`4~<+DJqjXVR%?V|ZZ6zr^P7fFl=q zhIsmUzj0Y-PASs83Krty1o9~4O>(J z0JFx|bYiAef5BmDGb7>{L+o8?wFM$u_ALJkj%+l9`l~b{>UA^VEdZ#5#jlut2Y_+? p71Pb@tp^Nk_%g~Uqm2JI{sUJKZNPuA Date: Sat, 23 Sep 2023 15:34:11 +0900 Subject: [PATCH 183/355] =?UTF-8?q?[Update]=20Main=20Window:=20=E8=89=B2?= =?UTF-8?q?=E3=82=84UI=E3=82=B9=E3=82=B1=E3=83=BC=E3=83=AB=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=AB=E5=AF=BE=E5=BF=9C=E3=81=99=E3=82=8B=E3=81=9F?= =?UTF-8?q?=E3=82=81=E3=81=AB=E5=A4=89=E6=95=B0=E8=BF=BD=E5=8A=A0=E3=80=82?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main_window/createMainWindowWidgets.py | 29 +++++++++---------- vrct_gui/ui_managers/ColorThemeManager.py | 7 +++++ vrct_gui/ui_managers/UiScalingManager.py | 12 +++++++- 3 files changed, 32 insertions(+), 16 deletions(-) diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 7f7eaa2e..b684d3ac 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -41,26 +41,26 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): + # Update Available Button vrct_gui.update_available_container = CTkFrame( vrct_gui.main_topbar_container, - corner_radius=settings.uism.HELP_AND_INFO_BUTTON_CORNER_RADIUS, + corner_radius=settings.uism.UPDATE_AVAILABLE_BUTTON_CORNER_RADIUS, fg_color=settings.ctm.MAIN_BG_COLOR, cursor="hand2", ) - vrct_gui.update_available_container.grid(row=0, column=3, padx=(0,4), pady=settings.uism.HELP_AND_INFO_BUTTON_PADY, sticky="nse") + vrct_gui.update_available_container.grid(row=0, column=3, padx=settings.uism.UPDATE_AVAILABLE_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="nse") vrct_gui.update_available_container.rowconfigure((0,2), weight=1) - vrct_gui.update_available_icon = CTkLabel( vrct_gui.update_available_container, text=None, corner_radius=0, height=0, - image=CTkImage(settings.image_file.REFRESH_ICON.rotate(25), size=(settings.uism.HELP_AND_INFO_BUTTON_SIZE-6,settings.uism.HELP_AND_INFO_BUTTON_SIZE-6)), + image=CTkImage(settings.image_file.REFRESH_ICON.rotate(25), size=settings.uism.UPDATE_AVAILABLE_BUTTON_SIZE) ) - vrct_gui.update_available_icon.grid(row=1, column=0, padx=(6,4), pady=0) + vrct_gui.update_available_icon.grid(row=1, column=0, padx=(settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX, settings.uism.UPDATE_AVAILABLE_PADX_BETWEEN_LABEL_AND_ICON), pady=0) vrct_gui.update_available_label = CTkLabel( @@ -68,15 +68,12 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): textvariable=view_variable.VAR_UPDATE_AVAILABLE, height=0, corner_radius=0, - font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal"), + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.UPDATE_AVAILABLE_BUTTON_FONT_SIZE, weight="normal"), anchor="e", - text_color="#61b4a7", + text_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_TEXT_COLOR, ) # This "right padx +1" is for fixing a bug that sticks out from the frame. I don't know why that happens... - vrct_gui.update_available_label.grid(row=1, column=1, padx=(0,6+1), pady=0) - - - + vrct_gui.update_available_label.grid(row=1, column=1, padx=(0,settings.uism.UPDATE_AVAILABLE_BUTTON_IPADX+1), pady=0) bindButtonFunctionAndColor( @@ -85,14 +82,16 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): vrct_gui.update_available_label, vrct_gui.update_available_icon, ], - enter_color=settings.ctm.HELP_AND_INFO_BUTTON_HOVERED_BG_COLOR, - leave_color=settings.ctm.HELP_AND_INFO_BUTTON_BG_COLOR, - clicked_color=settings.ctm.HELP_AND_INFO_BUTTON_CLICKED_BG_COLOR, + enter_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_HOVERED_BG_COLOR, + leave_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_BG_COLOR, + clicked_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_CLICKED_BG_COLOR, buttonReleasedFunction=vrct_gui.openHelpAndInfoWindow, ) + + # Help and Info button vrct_gui.help_and_info_button_container = createButtonWithImage( parent_widget=vrct_gui.main_topbar_container, @@ -105,7 +104,7 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): button_command=vrct_gui.openHelpAndInfoWindow, corner_radius=settings.uism.HELP_AND_INFO_BUTTON_CORNER_RADIUS, ) - vrct_gui.help_and_info_button_container.grid(row=0, column=4, padx=settings.uism.HELP_AND_INFO_BUTTON_PADX, pady=settings.uism.HELP_AND_INFO_BUTTON_PADY, sticky="e") + vrct_gui.help_and_info_button_container.grid(row=0, column=4, padx=settings.uism.HELP_AND_INFO_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="e") createSidebar(settings, vrct_gui, view_variable) diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 9c288b55..e73eac81 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -172,6 +172,13 @@ class ColorThemeManager(): self.main.MINIMIZE_SIDEBAR_BUTTON_CLICKED_BG_COLOR = self.DARK_900_COLOR # self.main.MINIMIZE_SIDEBAR_BUTTON_DISABLE_COLOR = self.DARK_900_COLOR + + self.main.UPDATE_AVAILABLE_BUTTON_BG_COLOR = self.main.MAIN_BG_COLOR + self.main.UPDATE_AVAILABLE_BUTTON_HOVERED_BG_COLOR = self.DARK_850_COLOR + self.main.UPDATE_AVAILABLE_BUTTON_CLICKED_BG_COLOR = self.DARK_950_COLOR + # self.main.UPDATE_AVAILABLE_BUTTON_DISABLE_COLOR = self.DARK_900_COLOR + self.main.UPDATE_AVAILABLE_BUTTON_TEXT_COLOR = self.PRIMARY_300_COLOR + self.main.HELP_AND_INFO_BUTTON_BG_COLOR = self.main.MAIN_BG_COLOR self.main.HELP_AND_INFO_BUTTON_HOVERED_BG_COLOR = self.DARK_850_COLOR self.main.HELP_AND_INFO_BUTTON_CLICKED_BG_COLOR = self.DARK_950_COLOR diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index a5769c12..ebac9bec 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -75,11 +75,21 @@ class UiScalingManager(): self.main.SIDEBAR_CONFIG_BUTTON_PADY = self._calculateUiSize(10) self.main.SIDEBAR_CONFIG_BUTTON_IPADY = self._calculateUiSize(8) + self.main.TOP_BAR_BUTTON_PADY = (self._calculateUiSize(6),0) + + self.main.UPDATE_AVAILABLE_BUTTON_CORNER_RADIUS = self._calculateUiSize(6) + self.main.UPDATE_AVAILABLE_BUTTON_SIZE = (self._calculateUiSize(18), self._calculateUiSize(18)) + self.main.UPDATE_AVAILABLE_BUTTON_FONT_SIZE = self._calculateUiSize(12) + self.main.UPDATE_AVAILABLE_BUTTON_PADX = (0, self._calculateUiSize(4)) + self.main.UPDATE_AVAILABLE_BUTTON_IPADX = self._calculateUiSize(6) + self.main.UPDATE_AVAILABLE_ICON_PADX = (self._calculateUiSize(6), self._calculateUiSize(4)) + self.main.UPDATE_AVAILABLE_PADX_BETWEEN_LABEL_AND_ICON = self._calculateUiSize(4) + + self.main.HELP_AND_INFO_BUTTON_CORNER_RADIUS = self._calculateUiSize(6) self.main.HELP_AND_INFO_BUTTON_SIZE = self._calculateUiSize(24) self.main.HELP_AND_INFO_BUTTON_PADX = (0, self._calculateUiSize(6)) - self.main.HELP_AND_INFO_BUTTON_PADY = (self._calculateUiSize(6),0) self.main.HELP_AND_INFO_BUTTON_IPADXY = self._calculateUiSize(6) self.main.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_X = int(self.main.TEXTBOX_PADX/2+self.main.TEXTBOX_CORNER_RADIUS*2) From 79e70089cd48d01b5b8a8e1b35122eabbc698139 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 23 Sep 2023 15:50:00 +0900 Subject: [PATCH 184/355] =?UTF-8?q?[Chore]=20Main=20Window:=20=E8=89=B2?= =?UTF-8?q?=E8=AA=BF=E6=95=B4=E3=80=82=E3=83=88=E3=83=83=E3=83=97=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E3=83=9C=E3=82=BF=E3=83=B3=E9=A1=9E=E3=81=AE?= =?UTF-8?q?=E8=89=B2=E6=8C=87=E5=AE=9A=E3=81=BE=E3=81=A8=E3=82=81=E3=80=82?= =?UTF-8?q?=E3=82=AF=E3=83=AA=E3=83=83=E3=82=AF=E6=99=82=E3=81=AE=E8=89=B2?= =?UTF-8?q?=E3=82=92=E5=B0=91=E3=81=97=E3=81=A0=E3=81=91=E8=96=84=E3=81=8F?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/ui_managers/ColorThemeManager.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index e73eac81..2b5833ef 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -173,16 +173,22 @@ class ColorThemeManager(): # self.main.MINIMIZE_SIDEBAR_BUTTON_DISABLE_COLOR = self.DARK_900_COLOR - self.main.UPDATE_AVAILABLE_BUTTON_BG_COLOR = self.main.MAIN_BG_COLOR - self.main.UPDATE_AVAILABLE_BUTTON_HOVERED_BG_COLOR = self.DARK_850_COLOR - self.main.UPDATE_AVAILABLE_BUTTON_CLICKED_BG_COLOR = self.DARK_950_COLOR - # self.main.UPDATE_AVAILABLE_BUTTON_DISABLE_COLOR = self.DARK_900_COLOR + + self.main.TOP_BAR_BUTTON_BG_COLOR = self.main.MAIN_BG_COLOR + self.main.TOP_BAR_BUTTON_HOVERED_BG_COLOR = self.DARK_850_COLOR + self.main.TOP_BAR_BUTTON_CLICKED_BG_COLOR = self.DARK_900_COLOR + # self.main.TOP_BAR_BUTTON_DISABLE_COLOR = self.DARK_900_COLOR + + self.main.UPDATE_AVAILABLE_BUTTON_BG_COLOR = self.main.TOP_BAR_BUTTON_BG_COLOR + self.main.UPDATE_AVAILABLE_BUTTON_HOVERED_BG_COLOR = self.main.TOP_BAR_BUTTON_HOVERED_BG_COLOR + self.main.UPDATE_AVAILABLE_BUTTON_CLICKED_BG_COLOR = self.main.TOP_BAR_BUTTON_CLICKED_BG_COLOR + # self.main.UPDATE_AVAILABLE_BUTTON_DISABLE_COLOR = self.main.TOP_BAR_BUTTON_DISABLE_COLOR self.main.UPDATE_AVAILABLE_BUTTON_TEXT_COLOR = self.PRIMARY_300_COLOR - self.main.HELP_AND_INFO_BUTTON_BG_COLOR = self.main.MAIN_BG_COLOR - self.main.HELP_AND_INFO_BUTTON_HOVERED_BG_COLOR = self.DARK_850_COLOR - self.main.HELP_AND_INFO_BUTTON_CLICKED_BG_COLOR = self.DARK_950_COLOR - # self.main.HELP_AND_INFO_BUTTON_DISABLE_COLOR = self.DARK_900_COLOR + self.main.HELP_AND_INFO_BUTTON_BG_COLOR = self.main.TOP_BAR_BUTTON_BG_COLOR + self.main.HELP_AND_INFO_BUTTON_HOVERED_BG_COLOR = self.main.TOP_BAR_BUTTON_HOVERED_BG_COLOR + self.main.HELP_AND_INFO_BUTTON_CLICKED_BG_COLOR = self.main.TOP_BAR_BUTTON_CLICKED_BG_COLOR + # self.main.HELP_AND_INFO_BUTTON_DISABLE_COLOR = self.main.TOP_BAR_BUTTON_DISABLE_COLOR From c072a5a40698c1ee29505321df92b322a0cc82b0 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 23 Sep 2023 16:09:09 +0900 Subject: [PATCH 185/355] =?UTF-8?q?[Update]=20Main=20Window:=20Update?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=92=E3=82=AF?= =?UTF-8?q?=E3=83=AA=E3=83=83=E3=82=AF=E3=81=99=E3=82=8B=E3=81=A8=E3=80=81?= =?UTF-8?q?=E3=82=AF=E3=83=A9=E3=82=A4=E3=82=A2=E3=83=B3=E3=83=88=E5=81=B4?= =?UTF-8?q?=E3=81=A7=E8=A8=AD=E5=AE=9A=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=82=8B=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB=E3=83=88=E3=83=96?= =?UTF-8?q?=E3=83=A9=E3=82=A6=E3=82=B6=E3=82=92=E9=96=8B=E3=81=8F=E3=80=82?= =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=AF=E5=85=88=E3=81=AFBooth=20VRCT?= =?UTF-8?q?=E3=83=9A=E3=83=BC=E3=82=B8=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/main_window/createMainWindowWidgets.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index b684d3ac..5015485b 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -4,6 +4,7 @@ from customtkinter import CTkFrame, CTkLabel, CTkFont, CTkImage from ..ui_utils import createButtonWithImage, getImagePath, bindButtonFunctionAndColor +import webbrowser def createMainWindowWidgets(vrct_gui, settings, view_variable): vrct_gui.protocol("WM_DELETE_WINDOW", vrct_gui.quitVRCT) @@ -85,7 +86,7 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): enter_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_HOVERED_BG_COLOR, leave_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_BG_COLOR, clicked_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_CLICKED_BG_COLOR, - buttonReleasedFunction=vrct_gui.openHelpAndInfoWindow, + buttonReleasedFunction=lambda e: webbrowser.open_new_tab("https://booth.pm/ja/items/4814313"), ) From ed795cfd9fd08ba2e20ada4ed79bfe04815b87a6 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 23 Sep 2023 17:13:41 +0900 Subject: [PATCH 186/355] =?UTF-8?q?[Update]=20Main=20Window:=20Update?= =?UTF-8?q?=E9=80=9A=E7=9F=A5UI=20=E3=83=87=E3=83=95=E3=82=A9=E3=83=AB?= =?UTF-8?q?=E3=83=88=E3=81=AF=E9=9D=9E=E8=A1=A8=E7=A4=BA=E3=81=A7=E3=80=81?= =?UTF-8?q?main.py=E5=81=B4=E3=81=A7config.UPDATE=5FFLAG=E3=82=92=E8=A6=8B?= =?UTF-8?q?=E3=81=A6=E8=A1=A8=E7=A4=BA=E3=81=99=E3=82=8B=E3=81=8B=E3=81=A9?= =?UTF-8?q?=E3=81=86=E3=81=8B=E3=81=AE=E9=96=A2=E6=95=B0=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=80=82(=E5=AE=9F=E9=9A=9B=E3=81=ABconfig=E5=A4=89=E6=95=B0?= =?UTF-8?q?=E3=82=92=E8=A6=8B=E3=82=8B=E3=81=8B=E3=81=A9=E3=81=86=E3=81=8B?= =?UTF-8?q?=E3=81=AF=E5=88=A5=E6=8B=85=E5=BD=93=20misya=20=E3=81=B8?= =?UTF-8?q?=E5=BC=95=E3=81=8D=E7=B6=99=E3=81=8E)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 3 +++ view.py | 3 +++ vrct_gui/main_window/createMainWindowWidgets.py | 1 + 3 files changed, 7 insertions(+) diff --git a/main.py b/main.py index 5a35e54c..303a7671 100644 --- a/main.py +++ b/main.py @@ -668,5 +668,8 @@ view.register( }, ) +if config.UPDATE_FLAG is True: + view.showUpdateAvailableButton() + if __name__ == "__main__": view.startMainLoop() \ No newline at end of file diff --git a/view.py b/view.py index 45d0c337..174e883f 100644 --- a/view.py +++ b/view.py @@ -392,6 +392,9 @@ class View(): # self._insertSampleConversationToTextbox() + @staticmethod + def showUpdateAvailableButton(): + vrct_gui.update_available_container.grid() @staticmethod def setMainWindowAllWidgetsStatusToNormal(): diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 5015485b..b10bdb16 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -50,6 +50,7 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): cursor="hand2", ) vrct_gui.update_available_container.grid(row=0, column=3, padx=settings.uism.UPDATE_AVAILABLE_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="nse") + vrct_gui.update_available_container.grid_remove() vrct_gui.update_available_container.rowconfigure((0,2), weight=1) From a9692a2ee972d1638a087a3b68c216e1d4312f55 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 24 Sep 2023 05:36:39 +0900 Subject: [PATCH 187/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20model=20:=20upda?= =?UTF-8?q?te=E7=B5=90=E6=9E=9C=E3=82=92return=E3=81=A7=E5=87=BA=E5=8A=9B?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/model.py b/model.py index 26a01bef..1bb20dbd 100644 --- a/model.py +++ b/model.py @@ -219,15 +219,15 @@ class Model: fnc() @staticmethod - def checkSoftwareUpdated(fnc): + def checkSoftwareUpdated(): # check update + update_flag = False response = requests_get(config.GITHUB_URL) - tag_name = response.json()["tag_name"] - if tag_name != config.VERSION: - try: - fnc(True) - except: - pass + new_version = response.json()["name"] + if new_version != config.VERSION: + update_flag = True + print("software version", "now:", config.VERSION, "new:", new_version) + return update_flag @staticmethod def getListInputHost(): From 7662dd4bf5761ae9368d396b1dc3ed28508b18d9 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 24 Sep 2023 05:38:48 +0900 Subject: [PATCH 188/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Main=20WIndow=20?= =?UTF-8?q?:=20update=E5=87=A6=E7=90=86=E3=82=92model=E3=81=AE=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=81=A6=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updateの結果から直接viewを変更する --- main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 303a7671..03c684aa 100644 --- a/main.py +++ b/main.py @@ -583,7 +583,8 @@ if config.STARTUP_OSC_ENABLED_CHECK is True and config.ENABLE_SEND_MESSAGE_TO_VR model.checkOSCStarted(view.printToTextbox_OSCError) # check Software Updated -model.checkSoftwareUpdated(callbackChangeStatusSoftwareUpdated) +if model.checkSoftwareUpdated() is True: + view.showUpdateAvailableButton() # init logger if config.ENABLE_LOGGER is True: From 95a9adadefc1990b8c796997344ffad25663ab78 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 24 Sep 2023 05:41:00 +0900 Subject: [PATCH 189/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Main=20Window=20?= =?UTF-8?q?:=20UPDATE=5FFLAG=E3=81=B8=E3=81=AE=E5=87=A6=E7=90=86=E3=82=92?= =?UTF-8?q?=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 10 ---------- main.py | 6 ------ 2 files changed, 16 deletions(-) diff --git a/config.py b/config.py index 42c1c404..dd16d9c4 100644 --- a/config.py +++ b/config.py @@ -399,15 +399,6 @@ class Config: self._STARTUP_OSC_ENABLED_CHECK = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property - def UPDATE_FLAG(self): - return self._UPDATE_FLAG - - @UPDATE_FLAG.setter - def UPDATE_FLAG(self, value): - if type(value) is bool: - self._UPDATE_FLAG = value - @property def GITHUB_URL(self): return self._GITHUB_URL @@ -519,7 +510,6 @@ class Config: self._ENABLE_NOTICE_XSOVERLAY = False self._ENABLE_SEND_MESSAGE_TO_VRC = True self._STARTUP_OSC_ENABLED_CHECK = True - self._UPDATE_FLAG = False self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" # self._BREAK_KEYSYM_LIST = [ # "Delete", "Select", "Up", "Down", "Next", "End", "Print", diff --git a/main.py b/main.py index 03c684aa..73c62eb7 100644 --- a/main.py +++ b/main.py @@ -223,9 +223,6 @@ def callbackSelectedLanguagePresetTab(selected_tab_no): def callbackSetAuthKeys(keys): config.AUTH_KEYS = keys -def callbackChangeStatusSoftwareUpdated(value): - config.UPDATE_FLAG = value - # command func def callbackToggleTranslation(is_turned_on): config.ENABLE_TRANSLATION = is_turned_on @@ -669,8 +666,5 @@ view.register( }, ) -if config.UPDATE_FLAG is True: - view.showUpdateAvailableButton() - if __name__ == "__main__": view.startMainLoop() \ No newline at end of file From bfda7a6e2523448be82b9bb755533787664bdadb Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 24 Sep 2023 20:42:40 +0900 Subject: [PATCH 190/355] =?UTF-8?q?[Update]=20Update=E9=80=9A=E7=9F=A5?= =?UTF-8?q?=E3=83=9C=E3=82=BF=E3=83=B3=E3=80=81Help=20and=20Info=E3=83=9C?= =?UTF-8?q?=E3=82=BF=E3=83=B3=E3=82=92=E6=8A=BC=E3=81=97=E3=81=9F=E3=81=A8?= =?UTF-8?q?=E3=81=8D=E3=81=ABview.py=E3=81=A7=E5=AE=9A=E7=BE=A9=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=82=8B=E3=82=B3=E3=83=BC=E3=83=AB=E3=83=90?= =?UTF-8?q?=E3=83=83=E3=82=AF=E9=96=A2=E6=95=B0=E3=81=8C=E5=91=BC=E3=81=B0?= =?UTF-8?q?=E3=82=8C=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=80=82=20?= =?UTF-8?q?=E5=AE=9F=E9=9A=9B=E3=81=AB=E3=83=AA=E3=83=B3=E3=82=AF=E5=85=88?= =?UTF-8?q?=E3=81=AB=E9=A3=9B=E3=81=B6=E3=81=8CURL=E3=81=AF=E4=BB=AE?= =?UTF-8?q?=E7=BD=AE=E3=81=8D=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 20 +++++++++++++++++++ .../main_window/createMainWindowWidgets.py | 7 +++---- vrct_gui/vrct_gui.py | 6 ------ 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/view.py b/view.py index 174e883f..26874d53 100644 --- a/view.py +++ b/view.py @@ -1,6 +1,8 @@ from typing import Union from types import SimpleNamespace from tkinter import font as tk_font +import webbrowser + from languages import selectable_languages from customtkinter import StringVar, IntVar, BooleanVar, END as CTK_END, get_appearance_mode @@ -46,6 +48,12 @@ class View(): CALLBACK_OPEN_CONFIG_WINDOW=None, CALLBACK_CLOSE_CONFIG_WINDOW=None, + # Open Help and Information Page + CALLBACK_CLICKED_HELP_AND_INFO=self.openWebPage_Booth, + + # Open Update Page + CALLBACK_CLICKED_UPDATE_AVAILABLE=self.openWebPage_VrctDocuments, + # Main Window # Sidebar @@ -392,6 +400,18 @@ class View(): # self._insertSampleConversationToTextbox() + @staticmethod + def openWebPage(url:str): + webbrowser.open_new_tab(url) + + def openWebPage_Booth(self): + self.openWebPage("https://booth.pm/ja/items/4814313") + self._printToTextbox_Info("Opened Booth page in your web browser.") + + def openWebPage_VrctDocuments(self): + self.openWebPage("https://booth.pm/ja/items/4814313") # temporally, this url is Booth link. + self._printToTextbox_Info("Opened the VRCT Documents page in your web browser.") + @staticmethod def showUpdateAvailableButton(): vrct_gui.update_available_container.grid() diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index b10bdb16..f926c9de 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -2,10 +2,9 @@ from .widgets import createSidebar, createMinimizeSidebarButton, createTextbox, from customtkinter import CTkFrame, CTkLabel, CTkFont, CTkImage +from utils import callFunctionIfCallable from ..ui_utils import createButtonWithImage, getImagePath, bindButtonFunctionAndColor -import webbrowser - def createMainWindowWidgets(vrct_gui, settings, view_variable): vrct_gui.protocol("WM_DELETE_WINDOW", vrct_gui.quitVRCT) @@ -87,7 +86,7 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): enter_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_HOVERED_BG_COLOR, leave_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_BG_COLOR, clicked_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_CLICKED_BG_COLOR, - buttonReleasedFunction=lambda e: webbrowser.open_new_tab("https://booth.pm/ja/items/4814313"), + buttonReleasedFunction=lambda e: callFunctionIfCallable(view_variable.CALLBACK_CLICKED_HELP_AND_INFO), ) @@ -103,7 +102,7 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): button_image_file=settings.image_file.HELP_ICON, button_image_size=settings.uism.HELP_AND_INFO_BUTTON_SIZE, button_ipadxy=settings.uism.HELP_AND_INFO_BUTTON_IPADXY, - button_command=vrct_gui.openHelpAndInfoWindow, + button_command=lambda e: callFunctionIfCallable(view_variable.CALLBACK_CLICKED_UPDATE_AVAILABLE), corner_radius=settings.uism.HELP_AND_INFO_BUTTON_CORNER_RADIUS, ) vrct_gui.help_and_info_button_container.grid(row=0, column=4, padx=settings.uism.HELP_AND_INFO_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="e") diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 9701eb2e..68898a37 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -111,12 +111,6 @@ class VRCT_GUI(CTk): - - def openHelpAndInfoWindow(self, e): - self.information_window.deiconify() - self.information_window.focus_set() - self.information_window.focus() - def changeMainWindowWidgetsStatus(self, status, target_names): _changeMainWindowWidgetsStatus( vrct_gui=self, From b259a2a3d8b8e47b970823a49e66c27da598a8ad Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 25 Sep 2023 05:39:06 +0900 Subject: [PATCH 191/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Config=20:=20con?= =?UTF-8?q?fig.json=E3=81=AB=E4=BF=9D=E5=AD=98=E3=81=99=E3=82=8B=E3=83=87?= =?UTF-8?q?=E3=83=BC=E3=82=BF=E3=82=92=E4=BB=95=E5=88=86=E3=81=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 保存する必要のある変数は@json_serializable('XXX')でデコレートするように変更 --- config.py | 305 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 174 insertions(+), 131 deletions(-) diff --git a/config.py b/config.py index dd16d9c4..62d098bf 100644 --- a/config.py +++ b/config.py @@ -11,6 +11,13 @@ from models.translation.translation_languages import translatorEngine, translati from models.transcription.transcription_languages import transcription_lang from models.transcription.transcription_utils import getInputDevices, getOutputDevices, getDefaultInputDevice, getDefaultOutputDevice +json_serializable_vars = {} +def json_serializable(var_name): + def decorator(func): + json_serializable_vars[var_name] = func + return func + return decorator + def saveJson(path, key, value): with open(path, "r", encoding="utf-8") as fp: json_data = load(fp) @@ -28,6 +35,7 @@ class Config: cls._instance.load_config() return cls._instance + # Read Only @property def VERSION(self): return self._VERSION @@ -36,6 +44,27 @@ class Config: def PATH_CONFIG(self): return self._PATH_CONFIG + @property + def GITHUB_URL(self): + return self._GITHUB_URL + + @property + def BOOTH_URL(self): + return self._BOOTH_URL + + @property + def DOCUMENTS_URL(self): + return self._DOCUMENTS_URL + + @property + def MAX_MIC_ENERGY_THRESHOLD(self): + return self._MAX_MIC_ENERGY_THRESHOLD + + @property + def MAX_SPEAKER_ENERGY_THRESHOLD(self): + return self._MAX_SPEAKER_ENERGY_THRESHOLD + + # Read Write @property def ENABLE_TRANSLATION(self): return self._ENABLE_TRANSLATION @@ -73,6 +102,88 @@ class Config: self._ENABLE_FOREGROUND = value @property + def SOURCE_COUNTRY(self): + return self._SOURCE_COUNTRY + + @SOURCE_COUNTRY.setter + def SOURCE_COUNTRY(self, value): + if type(value) is str: + self._SOURCE_COUNTRY = value + + @property + def SOURCE_LANGUAGE(self): + return self._SOURCE_LANGUAGE + + @SOURCE_LANGUAGE.setter + def SOURCE_LANGUAGE(self, value): + if type(value) is str: + self._SOURCE_LANGUAGE = value + + @property + def TARGET_COUNTRY(self): + return self._TARGET_COUNTRY + + @TARGET_COUNTRY.setter + def TARGET_COUNTRY(self, value): + if type(value) is str: + self._TARGET_COUNTRY = value + + @property + def TARGET_LANGUAGE(self): + return self._TARGET_LANGUAGE + + @TARGET_LANGUAGE.setter + def TARGET_LANGUAGE(self, value): + if type(value) is str: + self._TARGET_LANGUAGE = value + + @property + def CHOICE_TRANSLATOR(self): + return self._CHOICE_TRANSLATOR + + @CHOICE_TRANSLATOR.setter + def CHOICE_TRANSLATOR(self, value): + if value in translatorEngine: + self._CHOICE_TRANSLATOR = value + + # Save Json Data + ## Main Window + @property + @json_serializable('SELECTED_TAB_NO') + def SELECTED_TAB_NO(self): + return self._SELECTED_TAB_NO + + @SELECTED_TAB_NO.setter + def SELECTED_TAB_NO(self, value): + if type(value) is str: + self._SELECTED_TAB_NO = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SELECTED_TAB_YOUR_LANGUAGES') + def SELECTED_TAB_YOUR_LANGUAGES(self): + return self._SELECTED_TAB_YOUR_LANGUAGES + + @SELECTED_TAB_YOUR_LANGUAGES.setter + def SELECTED_TAB_YOUR_LANGUAGES(self, value): + if type(value) is dict: + self._SELECTED_TAB_YOUR_LANGUAGES = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + @property + @json_serializable('SELECTED_TAB_TARGET_LANGUAGES') + def SELECTED_TAB_TARGET_LANGUAGES(self): + return self._SELECTED_TAB_TARGET_LANGUAGES + + @SELECTED_TAB_TARGET_LANGUAGES.setter + def SELECTED_TAB_TARGET_LANGUAGES(self, value): + if type(value) is dict: + self._SELECTED_TAB_TARGET_LANGUAGES = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + + ## Config Window + @property + @json_serializable('TRANSPARENCY') def TRANSPARENCY(self): return self._TRANSPARENCY @@ -83,6 +194,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('APPEARANCE_THEME') def APPEARANCE_THEME(self): return self._APPEARANCE_THEME @@ -93,6 +205,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('UI_SCALING') def UI_SCALING(self): return self._UI_SCALING @@ -103,6 +216,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('FONT_FAMILY') def FONT_FAMILY(self): return self._FONT_FAMILY @@ -116,6 +230,7 @@ class Config: root.destroy() @property + @json_serializable('UI_LANGUAGE') def UI_LANGUAGE(self): return self._UI_LANGUAGE @@ -126,56 +241,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property - def CHOICE_TRANSLATOR(self): - return self._CHOICE_TRANSLATOR - - @CHOICE_TRANSLATOR.setter - def CHOICE_TRANSLATOR(self, value): - if value in translatorEngine: - self._CHOICE_TRANSLATOR = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def SOURCE_LANGUAGE(self): - return self._SOURCE_LANGUAGE - - @SOURCE_LANGUAGE.setter - def SOURCE_LANGUAGE(self, value): - if type(value) is str: - self._SOURCE_LANGUAGE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def SOURCE_COUNTRY(self): - return self._SOURCE_COUNTRY - - @SOURCE_COUNTRY.setter - def SOURCE_COUNTRY(self, value): - if type(value) is str: - self._SOURCE_COUNTRY = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def TARGET_LANGUAGE(self): - return self._TARGET_LANGUAGE - - @TARGET_LANGUAGE.setter - def TARGET_LANGUAGE(self, value): - if type(value) is str: - self._TARGET_LANGUAGE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def TARGET_COUNTRY(self): - return self._TARGET_COUNTRY - - @TARGET_COUNTRY.setter - def TARGET_COUNTRY(self, value): - if type(value) is str: - self._TARGET_COUNTRY = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property + @json_serializable('CHOICE_MIC_HOST') def CHOICE_MIC_HOST(self): return self._CHOICE_MIC_HOST @@ -186,6 +252,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('CHOICE_MIC_DEVICE') def CHOICE_MIC_DEVICE(self): return self._CHOICE_MIC_DEVICE @@ -196,6 +263,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('INPUT_MIC_ENERGY_THRESHOLD') def INPUT_MIC_ENERGY_THRESHOLD(self): return self._INPUT_MIC_ENERGY_THRESHOLD @@ -206,6 +274,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD') def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self): return self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD @@ -216,6 +285,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('INPUT_MIC_RECORD_TIMEOUT') def INPUT_MIC_RECORD_TIMEOUT(self): return self._INPUT_MIC_RECORD_TIMEOUT @@ -226,6 +296,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('INPUT_MIC_PHRASE_TIMEOUT') def INPUT_MIC_PHRASE_TIMEOUT(self): return self._INPUT_MIC_PHRASE_TIMEOUT @@ -236,6 +307,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('INPUT_MIC_MAX_PHRASES') def INPUT_MIC_MAX_PHRASES(self): return self._INPUT_MIC_MAX_PHRASES @@ -246,6 +318,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('INPUT_MIC_WORD_FILTER') def INPUT_MIC_WORD_FILTER(self): return self._INPUT_MIC_WORD_FILTER @@ -256,6 +329,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('CHOICE_SPEAKER_DEVICE') def CHOICE_SPEAKER_DEVICE(self): return self._CHOICE_SPEAKER_DEVICE @@ -268,6 +342,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('INPUT_SPEAKER_ENERGY_THRESHOLD') def INPUT_SPEAKER_ENERGY_THRESHOLD(self): return self._INPUT_SPEAKER_ENERGY_THRESHOLD @@ -278,6 +353,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD') def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self): return self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD @@ -288,6 +364,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('INPUT_SPEAKER_RECORD_TIMEOUT') def INPUT_SPEAKER_RECORD_TIMEOUT(self): return self._INPUT_SPEAKER_RECORD_TIMEOUT @@ -298,6 +375,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('INPUT_SPEAKER_PHRASE_TIMEOUT') def INPUT_SPEAKER_PHRASE_TIMEOUT(self): return self._INPUT_SPEAKER_PHRASE_TIMEOUT @@ -308,6 +386,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('INPUT_SPEAKER_MAX_PHRASES') def INPUT_SPEAKER_MAX_PHRASES(self): return self._INPUT_SPEAKER_MAX_PHRASES @@ -318,6 +397,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('OSC_IP_ADDRESS') def OSC_IP_ADDRESS(self): return self._OSC_IP_ADDRESS @@ -328,6 +408,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('OSC_PORT') def OSC_PORT(self): return self._OSC_PORT @@ -338,6 +419,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('AUTH_KEYS') def AUTH_KEYS(self): return self._AUTH_KEYS @@ -350,6 +432,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.AUTH_KEYS) @property + @json_serializable('MESSAGE_FORMAT') def MESSAGE_FORMAT(self): return self._MESSAGE_FORMAT @@ -360,6 +443,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('ENABLE_AUTO_CLEAR_MESSAGE_BOX') def ENABLE_AUTO_CLEAR_MESSAGE_BOX(self): return self._ENABLE_AUTO_CLEAR_MESSAGE_BOX @@ -370,6 +454,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('ENABLE_NOTICE_XSOVERLAY') def ENABLE_NOTICE_XSOVERLAY(self): return self._ENABLE_NOTICE_XSOVERLAY @@ -380,6 +465,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('ENABLE_SEND_MESSAGE_TO_VRC') def ENABLE_SEND_MESSAGE_TO_VRC(self): return self._ENABLE_SEND_MESSAGE_TO_VRC @@ -390,6 +476,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property + @json_serializable('STARTUP_OSC_ENABLED_CHECK') def STARTUP_OSC_ENABLED_CHECK(self): return self._STARTUP_OSC_ENABLED_CHECK @@ -400,52 +487,7 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property - def GITHUB_URL(self): - return self._GITHUB_URL - - # @property - # def BREAK_KEYSYM_LIST(self): - # return self._BREAK_KEYSYM_LIST - - @property - def MAX_MIC_ENERGY_THRESHOLD(self): - return self._MAX_MIC_ENERGY_THRESHOLD - - @property - def MAX_SPEAKER_ENERGY_THRESHOLD(self): - return self._MAX_SPEAKER_ENERGY_THRESHOLD - - @property - def SELECTED_TAB_NO(self): - return self._SELECTED_TAB_NO - - @SELECTED_TAB_NO.setter - def SELECTED_TAB_NO(self, value): - if type(value) is str: - self._SELECTED_TAB_NO = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def SELECTED_TAB_YOUR_LANGUAGES(self): - return self._SELECTED_TAB_YOUR_LANGUAGES - - @SELECTED_TAB_YOUR_LANGUAGES.setter - def SELECTED_TAB_YOUR_LANGUAGES(self, value): - if type(value) is dict: - self._SELECTED_TAB_YOUR_LANGUAGES = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def SELECTED_TAB_TARGET_LANGUAGES(self): - return self._SELECTED_TAB_TARGET_LANGUAGES - - @SELECTED_TAB_TARGET_LANGUAGES.setter - def SELECTED_TAB_TARGET_LANGUAGES(self, value): - if type(value) is dict: - self._SELECTED_TAB_TARGET_LANGUAGES = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property + @json_serializable('ENABLE_LOGGER') def ENABLE_LOGGER(self): return self._ENABLE_LOGGER @@ -455,8 +497,8 @@ class Config: self._ENABLE_LOGGER = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - # Config Window @property + @json_serializable('IS_CONFIG_WINDOW_COMPACT_MODE') def IS_CONFIG_WINDOW_COMPACT_MODE(self): return self._IS_CONFIG_WINDOW_COMPACT_MODE @@ -467,33 +509,58 @@ class Config: saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) def init_config(self): - self._VERSION = "1.3.2" + # Read Only + self._VERSION = "2.0.0" self._PATH_CONFIG = os_path.join(os_path.dirname(sys.argv[0]), "config.json") + self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" + self._BOOTH_URL = "https://misyaguziya.booth.pm/" + self._DOCUMENTS_URL = "https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246" + self._MAX_MIC_ENERGY_THRESHOLD = 2000 + self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 + + # Read Write self._ENABLE_TRANSLATION = False self._ENABLE_TRANSCRIPTION_SEND = False self._ENABLE_TRANSCRIPTION_RECEIVE = False self._ENABLE_FOREGROUND = False - self._TRANSPARENCY = 100 - self._APPEARANCE_THEME = "System" - self._UI_SCALING = "100%" - self._FONT_FAMILY = "Yu Gothic UI" - self._UI_LANGUAGE = "en" + + # Save Json Data self._CHOICE_TRANSLATOR = translatorEngine[0] self._SOURCE_LANGUAGE = "Japanese" self._SOURCE_COUNTRY = "Japan" self._TARGET_LANGUAGE = "English" self._TARGET_COUNTRY = "United States" + + ## Main Window + self._SELECTED_TAB_NO = "1" + self._SELECTED_TAB_YOUR_LANGUAGES = { + "1":"Japanese\n(Japan)", + "2":"Japanese\n(Japan)", + "3":"Japanese\n(Japan)", + } + self._SELECTED_TAB_TARGET_LANGUAGES = { + "1":"English\n(United States)", + "2":"English\n(United States)", + "3":"English\n(United States)", + } + + ## Config Window + self._TRANSPARENCY = 100 + self._APPEARANCE_THEME = "System" + self._UI_SCALING = "100%" + self._FONT_FAMILY = "Yu Gothic UI" + self._UI_LANGUAGE = "en" self._CHOICE_MIC_HOST = getDefaultInputDevice()["host"]["name"] self._CHOICE_MIC_DEVICE = getDefaultInputDevice()["device"]["name"] self._INPUT_MIC_ENERGY_THRESHOLD = 300 - self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = True + self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = False self._INPUT_MIC_RECORD_TIMEOUT = 3 self._INPUT_MIC_PHRASE_TIMEOUT = 3 self._INPUT_MIC_MAX_PHRASES = 10 self._INPUT_MIC_WORD_FILTER = [] self._CHOICE_SPEAKER_DEVICE = getDefaultOutputDevice()["name"] self._INPUT_SPEAKER_ENERGY_THRESHOLD = 300 - self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = True + self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = False self._INPUT_SPEAKER_RECORD_TIMEOUT = 3 self._INPUT_SPEAKER_PHRASE_TIMEOUT = 3 self._INPUT_SPEAKER_MAX_PHRASES = 10 @@ -510,27 +577,7 @@ class Config: self._ENABLE_NOTICE_XSOVERLAY = False self._ENABLE_SEND_MESSAGE_TO_VRC = True self._STARTUP_OSC_ENABLED_CHECK = True - self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" - # self._BREAK_KEYSYM_LIST = [ - # "Delete", "Select", "Up", "Down", "Next", "End", "Print", - # "Prior","Insert","Home", "Left", "Clear", "Right", "Linefeed" - # ] - self._MAX_MIC_ENERGY_THRESHOLD = 2000 - self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 - self._SELECTED_TAB_NO = "1" - self._SELECTED_TAB_YOUR_LANGUAGES = { - "1":"Japanese\n(Japan)", - "2":"Japanese\n(Japan)", - "3":"Japanese\n(Japan)", - } - self._SELECTED_TAB_TARGET_LANGUAGES = { - "1":"English\n(United States)", - "2":"English\n(United States)", - "3":"English\n(United States)", - } self._ENABLE_LOGGER = False - - # Config Window self._IS_CONFIG_WINDOW_COMPACT_MODE = False def load_config(self): @@ -542,13 +589,9 @@ class Config: setattr(self, key, config[key]) with open(self.PATH_CONFIG, 'w', encoding="utf-8") as fp: - setter_methods = [ - name for name, obj in vars(type(self)).items() - if isinstance(obj, property) and obj.fset is not None - ] config = {} - for method in setter_methods: - config[method] = getattr(self, method) + for var_name, var_func in json_serializable_vars.items(): + config[var_name] = var_func(self) json_dump(config, fp, indent=4, ensure_ascii=False) config = Config() \ No newline at end of file From 06ba68ecdd44abb754226f09af45d3c129fa479c Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 25 Sep 2023 05:40:28 +0900 Subject: [PATCH 192/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Main=20Init=20:?= =?UTF-8?q?=20config=E3=81=A7=E4=BF=9D=E5=AD=98=E3=81=99=E3=82=8B=E5=A4=89?= =?UTF-8?q?=E6=95=B0=E5=89=8A=E9=99=A4=E3=81=AB=E4=BC=B4=E3=81=84=E3=80=81?= =?UTF-8?q?=E5=88=9D=E6=9C=9F=E5=8C=96=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/main.py b/main.py index 73c62eb7..5c2aa257 100644 --- a/main.py +++ b/main.py @@ -181,6 +181,20 @@ def messageBoxPressKeyAny(e): model.oscStartSendTyping() # func select languages +def initSetLanguageAndCountry(): + select = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] + language, country = model.getLanguageAndCountry(select) + config.SOURCE_LANGUAGE = language + config.SOURCE_COUNTRY = country + + select = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] + language, country = model.getLanguageAndCountry(select) + config.TARGET_LANGUAGE = language + config.TARGET_COUNTRY = country + + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) + def setYourLanguageAndCountry(select): languages = config.SELECTED_TAB_YOUR_LANGUAGES languages[config.SELECTED_TAB_NO] = select @@ -566,6 +580,8 @@ def callbackSetOscPort(value): view.createGUI() # init config +initSetLanguageAndCountry() + if model.authenticationTranslator(callbackSetAuthKeys) is False: # error update Auth key view.printToTextbox_AuthenticationError() From 17504793ef485d3724c8c2436cfaf3a80f1e282e Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 25 Sep 2023 07:07:28 +0900 Subject: [PATCH 193/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Config=20:=20?= =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=81=AE=E4=BD=8D=E7=BD=AE?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/config.py b/config.py index 62d098bf..5aa121e6 100644 --- a/config.py +++ b/config.py @@ -523,14 +523,13 @@ class Config: self._ENABLE_TRANSCRIPTION_SEND = False self._ENABLE_TRANSCRIPTION_RECEIVE = False self._ENABLE_FOREGROUND = False - - # Save Json Data self._CHOICE_TRANSLATOR = translatorEngine[0] self._SOURCE_LANGUAGE = "Japanese" self._SOURCE_COUNTRY = "Japan" self._TARGET_LANGUAGE = "English" self._TARGET_COUNTRY = "United States" + # Save Json Data ## Main Window self._SELECTED_TAB_NO = "1" self._SELECTED_TAB_YOUR_LANGUAGES = { From abcc7ef80e78624dfc872408445a508a44d0b92e Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 25 Sep 2023 12:42:23 +0900 Subject: [PATCH 194/355] =?UTF-8?q?[Update]=20Update=20Available=20Button?= =?UTF-8?q?=E3=81=A8Help=20and=20Info=20Button=E3=81=8C=E6=8A=BC=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AE=E3=83=AA=E3=83=B3?= =?UTF-8?q?=E3=82=AF=E5=85=88(URL)=E3=82=92config=E3=81=8B=E3=82=89?= =?UTF-8?q?=E8=AA=AD=E3=81=BF=E5=8F=96=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/view.py b/view.py index 26874d53..36c5cdc0 100644 --- a/view.py +++ b/view.py @@ -405,11 +405,11 @@ class View(): webbrowser.open_new_tab(url) def openWebPage_Booth(self): - self.openWebPage("https://booth.pm/ja/items/4814313") + self.openWebPage(config.BOOTH_URL) self._printToTextbox_Info("Opened Booth page in your web browser.") def openWebPage_VrctDocuments(self): - self.openWebPage("https://booth.pm/ja/items/4814313") # temporally, this url is Booth link. + self.openWebPage(config.DOCUMENTS_URL) self._printToTextbox_Info("Opened the VRCT Documents page in your web browser.") @staticmethod From e86a9c604878d80a2c4d24647ab015957c291f11 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 25 Sep 2023 13:18:44 +0900 Subject: [PATCH 195/355] =?UTF-8?q?[Update]=20Config=20Window:=20Setting?= =?UTF-8?q?=20box,=20Font=20Family.=20=E3=82=A2=E3=83=AB=E3=83=95=E3=82=A1?= =?UTF-8?q?=E3=83=99=E3=83=83=E3=83=88=E9=A0=86=E3=81=ABsort=E3=81=97?= =?UTF-8?q?=E3=80=81@(=E7=B8=A6=E6=9B=B8=E3=81=8D)=E3=81=AE=E3=83=95?= =?UTF-8?q?=E3=82=A9=E3=83=B3=E3=83=88=E3=82=92=E6=8E=92=E9=99=A4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/view.py b/view.py index 36c5cdc0..860aa633 100644 --- a/view.py +++ b/view.py @@ -135,7 +135,7 @@ class View(): VAR_LABEL_FONT_FAMILY=StringVar(value="Font Family"), VAR_DESC_FONT_FAMILY=StringVar(value="(Default: Yu Gothic UI)"), - LIST_FONT_FAMILY=list(tk_font.families()), + LIST_FONT_FAMILY=self.getAvailableFonts(), CALLBACK_SET_FONT_FAMILY=None, VAR_FONT_FAMILY=StringVar(value=config.FONT_FAMILY), @@ -400,6 +400,13 @@ class View(): # self._insertSampleConversationToTextbox() + @staticmethod + def getAvailableFonts(): + available_fonts = list(tk_font.families()) + available_fonts.sort() + filtered_available_fonts = list(filter(lambda x: x.startswith("@") is False, available_fonts)) + return filtered_available_fonts + @staticmethod def openWebPage(url:str): webbrowser.open_new_tab(url) From 14cb8f11caa3c2311b4bcf9faef2c6a6e5916d37 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 25 Sep 2023 23:01:23 +0900 Subject: [PATCH 196/355] =?UTF-8?q?[bugfix]=20message=20format=E3=81=AEcal?= =?UTF-8?q?lback=E9=96=A2=E6=95=B0=E6=8C=87=E5=AE=9A=E3=83=9F=E3=82=B9?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting_box_others/createSettingBox_Others.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index 62715802..d6be70b4 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -25,7 +25,7 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v callFunctionIfCallable(view_variable.CALLBACK_SET_STARTUP_OSC_ENABLED_CHECK, checkbox_box_widget.get()) def entry_message_format_callback(value): - callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY, value) + callFunctionIfCallable(view_variable.CALLBACK_SET_MESSAGE_FORMAT, value) row=0 From b0d734e19bd5b6e1802ae2f4a981aeb6a01a4c46 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 25 Sep 2023 22:58:18 +0900 Subject: [PATCH 197/355] =?UTF-8?q?[Update]=20Localization.=20add=20python?= =?UTF-8?q?-i18n=20into=20requirements.txt.=20=E8=8B=B1=E8=AA=9E=E3=81=A8?= =?UTF-8?q?=E6=97=A5=E6=9C=AC=E8=AA=9E=E3=81=AE=E4=B8=80=E9=83=A8=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C=E3=80=82=E8=A8=AD=E5=AE=9A=E9=A0=85=E7=9B=AE=E3=81=AE?= =?UTF-8?q?Description=E3=81=AA=E3=81=A9=E4=B8=80=E9=83=A8=E7=9C=81?= =?UTF-8?q?=E3=81=84=E3=81=9F=E3=82=8A=E8=AA=BF=E6=95=B4=E3=81=82=E3=82=8A?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 100 ++++++++++++++++++++++++++++++ locales/ja.yml | 39 ++++++++++++ requirements.txt | 3 +- view.py | 154 +++++++++++++++++++++++++---------------------- 4 files changed, 222 insertions(+), 74 deletions(-) create mode 100644 locales/en.yml create mode 100644 locales/ja.yml diff --git a/locales/en.yml b/locales/en.yml new file mode 100644 index 00000000..038cf578 --- /dev/null +++ b/locales/en.yml @@ -0,0 +1,100 @@ +main_window: + translation: Translation + transcription_send: Voice2Chatbox + transcription_receive: Speaker2Log + foreground: Foreground + + language_settings: Language Settings + your_language: Your Language + both_direction_desc: Translate Each Other + target_language: Target Language + + textbox_tab_all: All + textbox_tab_sent: Sent + textbox_tab_received: Received + textbox_tab_system: System + + update_available: New version is here! + +selectable_language_window: + your_language: Your Language + target_language: Target Language + go_back_button: Go Back + +config_window: + transparency: + label: Transparency + desc: Change the main window's transparency. + appearance_theme: + label: Theme + desc: Change the color theme. If you selected "System", it will adjust based on your Windows theme. + ui_size: + label: UI Size + font_family: + label: Font Family + ui_language: + label: UI Language + + deepl_auth_key: + label: DeepL Auth Key + + mic_host: + label: Mic Host + mic_device: + label: Mic Device + mic_energy_threshold: + label: Mic Energy Threshold + desc: Slider to modify the threshold for activating voice input. Press the microphone button to initiate input and speak, allowing you to adjust it while monitoring the actual volume. + mic_dynamic_energy_threshold: + label: Mic Dynamic Energy Threshold + desc: When this feature is selected, it will automatically adjust in a way that works well, based on the set Mic Energy Threshold. + mic_record_timeout: + label: Mic Record Timeout + mic_phrase_timeout: + label: Mic Phrase Timeout + mic_max_phrase: + label: Mic Max Phrases + desc: It will stop recording and send the recordings when the set count of phrase(s) is reached. + mic_word_filter: + label: Mic Word Filter + desc: "It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC" + + speaker_device: + label: Speaker Device + speaker_energy_threshold: + label: Speaker Energy Threshold + desc: Slider to modify the threshold for activating voice input. Press the headphones mark button to start input and speak something, so you can adjust it while monitoring the actual volume. + speaker_dynamic_energy_threshold: + label: Speaker Dynamic Energy Threshold + desc: When this feature is selected, it will automatically adjust in a way that works well, based on the set Speaker Energy Threshold. + speaker_record_timeout: + label: Speaker Record Timeout + speaker_phrase_timeout: + label: Speaker Phrase Timeout + speaker_max_phrase: + label: Speaker Max Phrases + desc: It will stop recording and receive the recordings when the set count of phrase(s) is reached. + + auto_clear_the_message_box: + label: Auto Clear The Message Box + desc: Clear the message box after sending your message. + notice_xsoverlay: + label: Notification XSOverlay (VR Only) + desc: Notify received messages by using XSOverlay's notification feature. + auto_export_message_logs: + label: Auto Export Message Logs + desc: Automatically export the conversation messages as a text file. + message_format: + label: Message Format + desc: You can change the decoration of the message you want to send. + send_message_to_vrc: + label: Send Message To VRChat + desc: There is a way to use it without sending messages to VRChat. That is not covered by support, though. + startup_osc_enabled_check: + label: Check If OSC Is Enabled At Startup + desc: Every time VRCT is started up, your character in VRChat moves forward ever so slightly. If your character is seated, they might even stand up. Unfortunately, this is the only method we've found to check if OSC is enabled at startup... Sorry about that. (Remember to turn on OSC yourself when you want to send messages to VRChat.) + + osc_ip_address: + label: OSC IP Address + osc_port: + label: OSC Port \ No newline at end of file diff --git a/locales/ja.yml b/locales/ja.yml new file mode 100644 index 00000000..cf9499d1 --- /dev/null +++ b/locales/ja.yml @@ -0,0 +1,39 @@ +main_window: + translation: 翻訳 + transcription_send: マイク->チャットボックス + transcription_receive: スピーカー->ログ + foreground: 最前面表示 + + language_settings: 言語設定 + your_language: あなたの言語 + both_direction_desc: 双方向に翻訳 + target_language: 相手の言語 + + textbox_tab_all: 全て + textbox_tab_sent: 送信 + textbox_tab_received: 受信 + textbox_tab_system: システム + + update_available: 新しいバージョンが出ました! + +selectable_language_window: + your_language: あなたの言語 + target_language: 相手の言語 + go_back_button: 戻る + +config_window: + transparency: + label: 透明度 + desc: メイン画面の透明度を変更できます。 + appearance_theme: + label: 外観テーマ + desc: カラーテーマを変更できます。「System」を選択した場合、Windowsのテーマに基づいて自動的に「Dark」か「Light」テーマを判断し、適用します。 + ui_size: + label: UIのサイズ + font_family: + label: 使用フォント + ui_language: + label: UIの言語 + + deepl_auth_key: + label: DeepL 認証キー \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 0b8705f5..bf9b297b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,4 +4,5 @@ python-osc customtkinter deepl flashtext -pyyaml \ No newline at end of file +pyyaml +python-i18n \ No newline at end of file diff --git a/view.py b/view.py index 860aa633..89f8f124 100644 --- a/view.py +++ b/view.py @@ -1,7 +1,9 @@ +from os import path as os_path from typing import Union from types import SimpleNamespace from tkinter import font as tk_font import webbrowser +import i18n from languages import selectable_languages @@ -19,6 +21,14 @@ class View(): all_uism = UiScalingManager(config.UI_SCALING) image_file = ImageFileManager(theme) + i18n.load_path.append(os_path.join(os_path.dirname(__file__), "locales")) + i18n.set("fallback", "en") # The fallback language is English. + i18n.set("skip_locale_root_data", True) + i18n.set("filename_format", "{locale}.{format}") + i18n.set("enable_memoization", True) + + i18n.set("locale", config.UI_LANGUAGE) + common_args = { "image_file": image_file, "FONT_FAMILY": config.FONT_FAMILY, @@ -62,50 +72,49 @@ class View(): CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE=None, # Sidebar Features - VAR_LABEL_TRANSLATION=StringVar(value="Translation"), + VAR_LABEL_TRANSLATION=StringVar(value=i18n.t("main_window.translation")), CALLBACK_TOGGLE_TRANSLATION=None, - VAR_LABEL_TRANSCRIPTION_SEND=StringVar(value="Voice2Chatbox"), + VAR_LABEL_TRANSCRIPTION_SEND=StringVar(value=i18n.t("main_window.transcription_send")), CALLBACK_TOGGLE_TRANSCRIPTION_SEND=None, - VAR_LABEL_TRANSCRIPTION_RECEIVE=StringVar(value="Speaker2Log"), + VAR_LABEL_TRANSCRIPTION_RECEIVE=StringVar(value=i18n.t("main_window.transcription_receive")), CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE=None, - VAR_LABEL_FOREGROUND=StringVar(value="Foreground"), + VAR_LABEL_FOREGROUND=StringVar(value=i18n.t("main_window.foreground")), CALLBACK_TOGGLE_FOREGROUND=None, # Sidebar Language Settings - VAR_LABEL_LANGUAGE_SETTINGS=StringVar(value="Language Settings"), # JA: 言語設定 + VAR_LABEL_LANGUAGE_SETTINGS=StringVar(value=i18n.t("main_window.language_settings")), LIST_SELECTABLE_LANGUAGES=[], CALLBACK_SELECTED_LANGUAGE_PRESET_TAB=None, - VAR_LABEL_YOUR_LANGUAGE=StringVar(value="Your Language"), # JA: あなたの言語 + VAR_LABEL_YOUR_LANGUAGE=StringVar(value=i18n.t("main_window.your_language")), VAR_YOUR_LANGUAGE = StringVar(value="Japanese\n(Japan)"), CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW=None, IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW=False, CALLBACK_SELECTED_YOUR_LANGUAGE=None, - VAR_LABEL_BOTH_DIRECTION_DESC=StringVar(value="Translate Each Other"), # JA: 双方向に翻訳 + VAR_LABEL_BOTH_DIRECTION_DESC=StringVar(value=i18n.t("main_window.both_direction_desc")), - VAR_LABEL_TARGET_LANGUAGE=StringVar(value="Target Language"), # JA: 相手の言語 + VAR_LABEL_TARGET_LANGUAGE=StringVar(value=i18n.t("main_window.target_language")), VAR_TARGET_LANGUAGE = StringVar(value="English\n(United States)"), CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW=None, IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW=False, CALLBACK_SELECTED_TARGET_LANGUAGE=None, - VAR_LABEL_TEXTBOX_ALL=StringVar(value="All"), # JA: 全て - VAR_LABEL_TEXTBOX_SENT=StringVar(value="Sent"), # JA: 送信 - VAR_LABEL_TEXTBOX_RECEIVED=StringVar(value="Received"), # JA: 受信 - VAR_LABEL_TEXTBOX_SYSTEM=StringVar(value="System"), # JA: システム + VAR_LABEL_TEXTBOX_ALL=StringVar(value=i18n.t("main_window.textbox_tab_all")), + VAR_LABEL_TEXTBOX_SENT=StringVar(value=i18n.t("main_window.textbox_tab_sent")), + VAR_LABEL_TEXTBOX_RECEIVED=StringVar(value=i18n.t("main_window.textbox_tab_received")), + VAR_LABEL_TEXTBOX_SYSTEM=StringVar(value=i18n.t("main_window.textbox_tab_system")), - VAR_UPDATE_AVAILABLE=StringVar(value="New version is here!"), # JA: 新しいバージョンが出ました! - # VAR_UPDATE_AVAILABLE=StringVar(value="新しいバージョンが出ました!"), + VAR_UPDATE_AVAILABLE=StringVar(value=i18n.t("main_window.update_available")), # Selectable Language Window VAR_TITLE_LABEL_SELECTABLE_LANGUAGE=StringVar(value=""), - VAR_GO_BACK_LABEL_SELECTABLE_LANGUAGE=StringVar(value="Go Back"), + VAR_GO_BACK_LABEL_SELECTABLE_LANGUAGE=StringVar(value=i18n.t("selectable_language_window.go_back_button")), @@ -115,156 +124,155 @@ class View(): IS_CONFIG_WINDOW_COMPACT_MODE=config.IS_CONFIG_WINDOW_COMPACT_MODE, # Appearance Tab - VAR_LABEL_TRANSPARENCY=StringVar(value="Transparency"), - VAR_DESC_TRANSPARENCY=StringVar(value="Change the window's transparency. 50% to 100%. (Default: 100%)"), + VAR_LABEL_TRANSPARENCY=StringVar(value=i18n.t("config_window.transparency.label")), + VAR_DESC_TRANSPARENCY=StringVar(value=i18n.t("config_window.transparency.desc")), SLIDER_RANGE_TRANSPARENCY=(50, 100), CALLBACK_SET_TRANSPARENCY=None, VAR_TRANSPARENCY=IntVar(value=config.TRANSPARENCY), - VAR_LABEL_APPEARANCE_THEME=StringVar(value="Theme"), - VAR_DESC_APPEARANCE_THEME=StringVar(value="Change the color theme from \"Light\" and \"Dark\". If you select \"System\", It will adjust based on your Windows theme. (Default: System)"), + VAR_LABEL_APPEARANCE_THEME=StringVar(value=i18n.t("config_window.appearance_theme.label")), + VAR_DESC_APPEARANCE_THEME=StringVar(value=i18n.t("config_window.appearance_theme.desc")), LIST_APPEARANCE_THEME=["Light", "Dark", "System"], CALLBACK_SET_APPEARANCE_THEME=None, VAR_APPEARANCE_THEME=StringVar(value=config.APPEARANCE_THEME), - VAR_LABEL_UI_SCALING=StringVar(value="UI Size"), - VAR_DESC_UI_SCALING=StringVar(value="(Default: 100%)"), + VAR_LABEL_UI_SCALING=StringVar(value=i18n.t("config_window.ui_size.label")), + VAR_DESC_UI_SCALING=None, LIST_UI_SCALING=["80%", "90%", "100%", "110%", "120%"], CALLBACK_SET_UI_SCALING=None, VAR_UI_SCALING=StringVar(value=config.UI_SCALING), - VAR_LABEL_FONT_FAMILY=StringVar(value="Font Family"), - VAR_DESC_FONT_FAMILY=StringVar(value="(Default: Yu Gothic UI)"), + VAR_LABEL_FONT_FAMILY=StringVar(value=i18n.t("config_window.font_family.label")), + VAR_DESC_FONT_FAMILY=None, LIST_FONT_FAMILY=self.getAvailableFonts(), CALLBACK_SET_FONT_FAMILY=None, VAR_FONT_FAMILY=StringVar(value=config.FONT_FAMILY), - VAR_LABEL_UI_LANGUAGE=StringVar(value="UI Language"), - VAR_DESC_UI_LANGUAGE=StringVar(value="(Default: English)"), + VAR_LABEL_UI_LANGUAGE=StringVar(value=i18n.t("config_window.ui_language.label")), + VAR_DESC_UI_LANGUAGE=None, LIST_UI_LANGUAGE=list(selectable_languages.values()), CALLBACK_SET_UI_LANGUAGE=None, VAR_UI_LANGUAGE=StringVar(value=selectable_languages[config.UI_LANGUAGE]), # Translation Tab - VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value="DeepL Auth Key"), + VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value=i18n.t("config_window.deepl_auth_key.label")), VAR_DESC_DEEPL_AUTH_KEY=None, - # VAR_DESC_DEEPL_AUTH_KEY=StringVar(value=""), CALLBACK_SET_DEEPL_AUTH_KEY=None, VAR_DEEPL_AUTH_KEY=StringVar(value=config.AUTH_KEYS["DeepL(auth)"]), # Transcription Tab (Mic) - VAR_LABEL_MIC_HOST=StringVar(value="Mic Host"), - VAR_DESC_MIC_HOST=StringVar(value="Select the mic host. (Default: ?)"), + VAR_LABEL_MIC_HOST=StringVar(value=i18n.t("config_window.mic_host.label")), + VAR_DESC_MIC_HOST=None, LIST_MIC_HOST=[], CALLBACK_SET_MIC_HOST=None, VAR_MIC_HOST=StringVar(value=config.CHOICE_MIC_HOST), - VAR_LABEL_MIC_DEVICE=StringVar(value="Mic Device"), - VAR_DESC_MIC_DEVICE=StringVar(value="Select the mic devise. (Default: ?)"), + VAR_LABEL_MIC_DEVICE=StringVar(value=i18n.t("config_window.mic_device.label")), + VAR_DESC_MIC_DEVICE=None, LIST_MIC_DEVICE=[], CALLBACK_SET_MIC_DEVICE=None, VAR_MIC_DEVICE=StringVar(value=config.CHOICE_MIC_DEVICE), - VAR_LABEL_MIC_ENERGY_THRESHOLD=StringVar(value="Mic Energy Threshold"), - VAR_DESC_MIC_ENERGY_THRESHOLD=StringVar(value="Slider to modify the threshold for activating voice input.\nPress the microphone button to initiate input and speak, allowing you to adjust it while monitoring the actual volume. Range: 0 to 2000 (Default: 300)"), + VAR_LABEL_MIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_energy_threshold.label")), + VAR_DESC_MIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_energy_threshold.desc")), SLIDER_RANGE_MIC_ENERGY_THRESHOLD=(0, config.MAX_MIC_ENERGY_THRESHOLD), CALLBACK_CHECK_MIC_THRESHOLD=None, VAR_MIC_ENERGY_THRESHOLD__SLIDER=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), VAR_MIC_ENERGY_THRESHOLD__ENTRY=StringVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), - VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value="Mic Dynamic Energy Threshold"), - VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value="When this feature is selected, it will automatically adjust in a way that works well, based on the set Mic Energy Threshold."), + VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_dynamic_energy_threshold.label")), + VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_dynamic_energy_threshold.desc")), CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD=None, VAR_MIC_DYNAMIC_ENERGY_THRESHOLD=BooleanVar(value=config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD), - VAR_LABEL_MIC_RECORD_TIMEOUT=StringVar(value="Mic Record Timeout"), - VAR_DESC_MIC_RECORD_TIMEOUT=StringVar(value="(Default: 3)"), + VAR_LABEL_MIC_RECORD_TIMEOUT=StringVar(value=i18n.t("config_window.mic_record_timeout.label")), + VAR_DESC_MIC_RECORD_TIMEOUT=None, CALLBACK_SET_MIC_RECORD_TIMEOUT=None, VAR_MIC_RECORD_TIMEOUT=StringVar(value=config.INPUT_MIC_RECORD_TIMEOUT), - VAR_LABEL_MIC_PHRASE_TIMEOUT=StringVar(value="Mic Phrase Timeout"), - VAR_DESC_MIC_PHRASE_TIMEOUT=StringVar(value="(Default: 3)"), + VAR_LABEL_MIC_PHRASE_TIMEOUT=StringVar(value=i18n.t("config_window.mic_phrase_timeout.label")), + VAR_DESC_MIC_PHRASE_TIMEOUT=None, CALLBACK_SET_MIC_PHRASE_TIMEOUT=None, VAR_MIC_PHRASE_TIMEOUT=StringVar(value=config.INPUT_MIC_PHRASE_TIMEOUT), - VAR_LABEL_MIC_MAX_PHRASES=StringVar(value="Mic Max Phrases"), - VAR_DESC_MIC_MAX_PHRASES=StringVar(value="It will stop recording and send the recordings when the set count of phrase(s) is reached. (Default: 10)"), + VAR_LABEL_MIC_MAX_PHRASES=StringVar(value=i18n.t("config_window.mic_max_phrase.label")), + VAR_DESC_MIC_MAX_PHRASES=StringVar(value=i18n.t("config_window.mic_max_phrase.desc")), CALLBACK_SET_MIC_MAX_PHRASES=None, VAR_MIC_MAX_PHRASES=StringVar(value=config.INPUT_MIC_MAX_PHRASES), - VAR_LABEL_MIC_WORD_FILTER=StringVar(value="Mic Word Filter"), - VAR_DESC_MIC_WORD_FILTER=StringVar(value="It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC"), + VAR_LABEL_MIC_WORD_FILTER=StringVar(value=i18n.t("config_window.mic_word_filter.label")), + VAR_DESC_MIC_WORD_FILTER=StringVar(value=i18n.t("config_window.mic_word_filter.desc")), CALLBACK_SET_MIC_WORD_FILTER=None, VAR_MIC_WORD_FILTER=StringVar(value=",".join(config.INPUT_MIC_WORD_FILTER) if len(config.INPUT_MIC_WORD_FILTER) > 0 else ""), # Transcription Tab (Speaker) - VAR_LABEL_SPEAKER_DEVICE=StringVar(value="Speaker Device"), - VAR_DESC_SPEAKER_DEVICE=StringVar(value="Select the speaker devise. (Default: ?)"), + VAR_LABEL_SPEAKER_DEVICE=StringVar(value=i18n.t("config_window.speaker_device.label")), + VAR_DESC_SPEAKER_DEVICE=None, LIST_SPEAKER_DEVICE=[], CALLBACK_SET_SPEAKER_DEVICE=None, VAR_SPEAKER_DEVICE=StringVar(value=config.CHOICE_SPEAKER_DEVICE), - VAR_LABEL_SPEAKER_ENERGY_THRESHOLD=StringVar(value="Speaker Energy Threshold"), - VAR_DESC_SPEAKER_ENERGY_THRESHOLD=StringVar(value="Slider to modify the threshold for activating voice input.\nPress the headphones mark button to start input and speak something, so you can adjust it while monitoring the actual volume. Range: 0 to 4000 (Default: 300)"), + VAR_LABEL_SPEAKER_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_energy_threshold.label")), + VAR_DESC_SPEAKER_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_energy_threshold.desc")), SLIDER_RANGE_SPEAKER_ENERGY_THRESHOLD=(0, config.MAX_SPEAKER_ENERGY_THRESHOLD), CALLBACK_CHECK_SPEAKER_THRESHOLD=None, VAR_SPEAKER_ENERGY_THRESHOLD__SLIDER=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), VAR_SPEAKER_ENERGY_THRESHOLD__ENTRY=StringVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), - VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value="Speaker Dynamic Energy Threshold"), - VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value="When this feature is selected, it will automatically adjust in a way that works well, based on the set Speaker Energy Threshold."), + VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_dynamic_energy_threshold.label")), + VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_dynamic_energy_threshold.desc")), CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=None, VAR_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=BooleanVar(value=config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD), - VAR_LABEL_SPEAKER_RECORD_TIMEOUT=StringVar(value="Speaker Record Timeout"), - VAR_DESC_SPEAKER_RECORD_TIMEOUT=StringVar(value="(Default: 3)"), + VAR_LABEL_SPEAKER_RECORD_TIMEOUT=StringVar(value=i18n.t("config_window.speaker_record_timeout.label")), + VAR_DESC_SPEAKER_RECORD_TIMEOUT=None, CALLBACK_SET_SPEAKER_RECORD_TIMEOUT=None, VAR_SPEAKER_RECORD_TIMEOUT=StringVar(value=config.INPUT_SPEAKER_RECORD_TIMEOUT), - VAR_LABEL_SPEAKER_PHRASE_TIMEOUT=StringVar(value="Speaker Phrase Timeout"), - VAR_DESC_SPEAKER_PHRASE_TIMEOUT=StringVar(value="It will stop recording and receive the recordings when the set second(s) is reached. (Default: 3)"), + VAR_LABEL_SPEAKER_PHRASE_TIMEOUT=StringVar(value=i18n.t("config_window.speaker_phrase_timeout.label")), + VAR_DESC_SPEAKER_PHRASE_TIMEOUT=None, CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT=None, VAR_SPEAKER_PHRASE_TIMEOUT=StringVar(value=config.INPUT_SPEAKER_PHRASE_TIMEOUT), - VAR_LABEL_SPEAKER_MAX_PHRASES=StringVar(value="Speaker Max Phrases"), - VAR_DESC_SPEAKER_MAX_PHRASES=StringVar(value="It will stop recording and receive the recordings when the set count of phrase(s) is reached. (Default: 10)"), + VAR_LABEL_SPEAKER_MAX_PHRASES=StringVar(value=i18n.t("config_window.speaker_max_phrase.label")), + VAR_DESC_SPEAKER_MAX_PHRASES=StringVar(value=i18n.t("config_window.speaker_max_phrase.desc")), CALLBACK_SET_SPEAKER_MAX_PHRASES=None, VAR_SPEAKER_MAX_PHRASES=StringVar(value=config.INPUT_SPEAKER_MAX_PHRASES), # Others Tab - VAR_LABEL_ENABLE_AUTO_CLEAR_MESSAGE_BOX=StringVar(value="Auto Clear The Message Box"), - VAR_DESC_ENABLE_AUTO_CLEAR_MESSAGE_BOX=StringVar(value="Clear the message box after sending your message."), + VAR_LABEL_ENABLE_AUTO_CLEAR_MESSAGE_BOX=StringVar(value=i18n.t("config_window.auto_clear_the_message_box.label")), + VAR_DESC_ENABLE_AUTO_CLEAR_MESSAGE_BOX=StringVar(value=i18n.t("config_window.auto_clear_the_message_box.desc")), CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX=None, VAR_ENABLE_AUTO_CLEAR_MESSAGE_BOX=BooleanVar(value=config.ENABLE_AUTO_CLEAR_MESSAGE_BOX), - VAR_LABEL_ENABLE_NOTICE_XSOVERLAY=StringVar(value="Notification XSOverlay (VR Only)"), - VAR_DESC_ENABLE_NOTICE_XSOVERLAY=StringVar(value="Notify received messages by using XSOverlay's notification feature."), + VAR_LABEL_ENABLE_NOTICE_XSOVERLAY=StringVar(value=i18n.t("config_window.notice_xsoverlay.label")), + VAR_DESC_ENABLE_NOTICE_XSOVERLAY=StringVar(value=i18n.t("config_window.notice_xsoverlay.desc")), CALLBACK_SET_ENABLE_NOTICE_XSOVERLAY=None, VAR_ENABLE_NOTICE_XSOVERLAY=BooleanVar(value=config.ENABLE_NOTICE_XSOVERLAY), - VAR_LABEL_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=StringVar(value="Auto Export Message Logs"), - VAR_DESC_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=StringVar(value="Automatically export the conversation messages as a text file."), + VAR_LABEL_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=StringVar(value=i18n.t("config_window.auto_export_message_logs.label")), + VAR_DESC_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=StringVar(value=i18n.t("config_window.auto_export_message_logs.desc")), CALLBACK_SET_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=None, VAR_ENABLE_AUTO_EXPORT_MESSAGE_LOGS=BooleanVar(value=config.ENABLE_LOGGER), - VAR_LABEL_MESSAGE_FORMAT=StringVar(value="Message Format"), - VAR_DESC_MESSAGE_FORMAT=StringVar(value="You can change the decoration of the message you want to send. (Default: \"[message]([translation])\" )"), + VAR_LABEL_MESSAGE_FORMAT=StringVar(value=i18n.t("config_window.message_format.label")), + VAR_DESC_MESSAGE_FORMAT=StringVar(value=i18n.t("config_window.message_format.desc")), CALLBACK_SET_MESSAGE_FORMAT=None, VAR_MESSAGE_FORMAT=StringVar(value=config.MESSAGE_FORMAT), - VAR_LABEL_ENABLE_SEND_MESSAGE_TO_VRC=StringVar(value="Send Message To VRChat"), - VAR_DESC_ENABLE_SEND_MESSAGE_TO_VRC=StringVar(value="There is a way to use it without sending messages to VRChat.\nThat is not covered by support, though."), + VAR_LABEL_ENABLE_SEND_MESSAGE_TO_VRC=StringVar(value=i18n.t("config_window.send_message_to_vrc.label")), + VAR_DESC_ENABLE_SEND_MESSAGE_TO_VRC=StringVar(value=i18n.t("config_window.send_message_to_vrc.desc")), CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC=None, VAR_ENABLE_SEND_MESSAGE_TO_VRC=BooleanVar(value=config.ENABLE_SEND_MESSAGE_TO_VRC), - VAR_LABEL_STARTUP_OSC_ENABLED_CHECK=StringVar(value="Check If OSC Is Enabled At Startup"), - VAR_DESC_STARTUP_OSC_ENABLED_CHECK=StringVar(value="Every time VRCT is started up, your character in VRChat moves forward ever so slightly. If your character is seated, they might even stand up. Unfortunately, this is the only method we've found to check if OSC is enabled at startup... Sorry about that. (Remember to turn on OSC yourself when you want to send messages to VRChat.)"), + VAR_LABEL_STARTUP_OSC_ENABLED_CHECK=StringVar(value=i18n.t("config_window.startup_osc_enabled_check.label")), + VAR_DESC_STARTUP_OSC_ENABLED_CHECK=StringVar(value=i18n.t("config_window.startup_osc_enabled_check.desc")), CALLBACK_SET_STARTUP_OSC_ENABLED_CHECK=None, VAR_STARTUP_OSC_ENABLED_CHECK=BooleanVar(value=config.STARTUP_OSC_ENABLED_CHECK), @@ -272,13 +280,13 @@ class View(): # Advanced Settings Tab - VAR_LABEL_OSC_IP_ADDRESS=StringVar(value="OSC IP Address"), - VAR_DESC_OSC_IP_ADDRESS=StringVar(value="(Default: 127.0.0.1)"), + VAR_LABEL_OSC_IP_ADDRESS=StringVar(value=i18n.t("config_window.osc_ip_address.label")), + VAR_DESC_OSC_IP_ADDRESS=None, CALLBACK_SET_OSC_IP_ADDRESS=None, VAR_OSC_IP_ADDRESS=StringVar(value=config.OSC_IP_ADDRESS), - VAR_LABEL_OSC_PORT=StringVar(value="OSC Port"), - VAR_DESC_OSC_PORT=StringVar(value="(Default: 9000)"), + VAR_LABEL_OSC_PORT=StringVar(value=i18n.t("config_window.osc_port.label")), + VAR_DESC_OSC_PORT=None, CALLBACK_SET_OSC_PORT=None, VAR_OSC_PORT=StringVar(value=config.OSC_PORT), ) @@ -456,10 +464,10 @@ class View(): vrct_gui.recreateMainWindowSidebar() def openSelectableLanguagesWindow_YourLanguage(self, _e): - self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set("Your Language") + self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set(i18n.t("selectable_language_window.your_language")) vrct_gui.openSelectableLanguagesWindow("your_language") def openSelectableLanguagesWindow_TargetLanguage(self, _e): - self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set("Target Language") + self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set(i18n.t("selectable_language_window.target_language")) vrct_gui.openSelectableLanguagesWindow("target_language") From d58ad09708845e664fc8644c6de16bb7607db281 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 26 Sep 2023 11:39:35 +0900 Subject: [PATCH 198/355] =?UTF-8?q?[Update]=20Config=20Window:=20Localizat?= =?UTF-8?q?ion.=20=E5=90=84=E9=A0=85=E7=9B=AE=E3=81=AE=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=88=E3=83=AB=E3=82=92=E5=A4=89=E6=95=B0=E5=8C=96=E3=80=81?= =?UTF-8?q?=E5=A4=9A=E8=A8=80=E8=AA=9E=E5=AF=BE=E5=BF=9C=E3=80=82=EF=BC=88?= =?UTF-8?q?=E3=81=9D=E3=82=8C=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=81=A6?= =?UTF-8?q?=E5=88=87=E3=82=8A=E6=9B=BF=E3=81=88=E5=87=A6=E7=90=86=E3=82=82?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 14 +++++++-- locales/ja.yml | 15 ++++++++-- view.py | 19 +++++++++--- .../_createSettingBoxTitle.py | 4 +-- .../createSettingBoxTopBar.py | 2 +- .../_addConfigSideMenuItem.py | 13 ++++---- .../_createSettingBoxContainer.py | 8 ++--- .../createSideMenuAndSettingsBoxContainers.py | 30 +++++++++++-------- 8 files changed, 71 insertions(+), 34 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 038cf578..7a9e6b1c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -17,11 +17,21 @@ main_window: update_available: New version is here! selectable_language_window: - your_language: Your Language - target_language: Target Language + title_your_language: Select Your Language + title_target_language: Select Target Language go_back_button: Go Back config_window: + config_title: Settings + side_menu_labels: + appearance: Appearance + translation: Translation + transcription: Transcription + transcription_mic: Mic + transcription_speaker: Speaker + others: Others + advanced_settings: Advanced Settings + transparency: label: Transparency desc: Change the main window's transparency. diff --git a/locales/ja.yml b/locales/ja.yml index cf9499d1..c964cf18 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -17,11 +17,21 @@ main_window: update_available: 新しいバージョンが出ました! selectable_language_window: - your_language: あなたの言語 - target_language: 相手の言語 + title_your_language: あなたの言語 + title_target_language: 相手の言語 go_back_button: 戻る config_window: + config_title: 設定 + side_menu_labels: + appearance: デザイン + translation: 翻訳 + transcription: 音声認識 + transcription_mic: マイク + transcription_speaker: スピーカー + others: その他 + advanced_settings: 高度な設定 + transparency: label: 透明度 desc: メイン画面の透明度を変更できます。 @@ -35,5 +45,6 @@ config_window: ui_language: label: UIの言語 + tab_translation: 翻訳 deepl_auth_key: label: DeepL 認証キー \ No newline at end of file diff --git a/view.py b/view.py index 89f8f124..613fe0a4 100644 --- a/view.py +++ b/view.py @@ -123,7 +123,17 @@ class View(): CALLBACK_SELECTED_SETTING_BOX_TAB=None, IS_CONFIG_WINDOW_COMPACT_MODE=config.IS_CONFIG_WINDOW_COMPACT_MODE, - # Appearance Tab + # Side Menu Labels + VAR_SIDE_MENU_LABEL_APPEARANCE=StringVar(value=i18n.t("config_window.side_menu_labels.appearance")), + VAR_SIDE_MENU_LABEL_TRANSLATION=StringVar(value=i18n.t("config_window.side_menu_labels.translation")), + VAR_SIDE_MENU_LABEL_TRANSCRIPTION=StringVar(value=i18n.t("config_window.side_menu_labels.transcription")), + VAR_SECOND_TITLE_TRANSCRIPTION_MIC=StringVar(value=i18n.t("config_window.side_menu_labels.transcription_mic")), + VAR_SECOND_TITLE_TRANSCRIPTION_SPEAKER=StringVar(value=i18n.t("config_window.side_menu_labels.transcription_speaker")), + VAR_SIDE_MENU_LABEL_OTHERS=StringVar(value=i18n.t("config_window.side_menu_labels.others")), + VAR_SIDE_MENU_LABEL_ADVANCED_SETTINGS=StringVar(value=i18n.t("config_window.side_menu_labels.advanced_settings")), + + VAR_CURRENT_ACTIVE_CONFIG_TITLE=StringVar(value=""), + VAR_LABEL_TRANSPARENCY=StringVar(value=i18n.t("config_window.transparency.label")), VAR_DESC_TRANSPARENCY=StringVar(value=i18n.t("config_window.transparency.desc")), SLIDER_RANGE_TRANSPARENCY=(50, 100), @@ -155,7 +165,7 @@ class View(): VAR_UI_LANGUAGE=StringVar(value=selectable_languages[config.UI_LANGUAGE]), - # Translation Tab + VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value=i18n.t("config_window.deepl_auth_key.label")), VAR_DESC_DEEPL_AUTH_KEY=None, CALLBACK_SET_DEEPL_AUTH_KEY=None, @@ -163,6 +173,7 @@ class View(): # Transcription Tab (Mic) + VAR_TAB_SECOND_LABEL_TRANSCRIPTION_MIC=StringVar(value=i18n.t("config_window.tab_transcription.label")), VAR_LABEL_MIC_HOST=StringVar(value=i18n.t("config_window.mic_host.label")), VAR_DESC_MIC_HOST=None, LIST_MIC_HOST=[], @@ -464,10 +475,10 @@ class View(): vrct_gui.recreateMainWindowSidebar() def openSelectableLanguagesWindow_YourLanguage(self, _e): - self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set(i18n.t("selectable_language_window.your_language")) + self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set(i18n.t("selectable_language_window.title_your_language")) vrct_gui.openSelectableLanguagesWindow("your_language") def openSelectableLanguagesWindow_TargetLanguage(self, _e): - self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set(i18n.t("selectable_language_window.target_language")) + self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set(i18n.t("selectable_language_window.title_target_language")) vrct_gui.openSelectableLanguagesWindow("target_language") diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxTitle.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxTitle.py index 7e7479b7..824da444 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxTitle.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxTitle.py @@ -1,6 +1,6 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel -def _createSettingBoxTitle(parent_widget, config_window, settings): +def _createSettingBoxTitle(parent_widget, config_window, settings, view_variable): parent_widget.grid_columnconfigure(0, weight=1) config_window.main_current_active_config_title_container = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) @@ -11,7 +11,7 @@ def _createSettingBoxTitle(parent_widget, config_window, settings): config_window.main_current_active_config_title = CTkLabel( config_window.main_current_active_config_title_container, height=0, - text=None, + textvariable=view_variable.VAR_CURRENT_ACTIVE_CONFIG_TITLE, anchor="w", font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TOP_BAR_MAIN__TITLE_FONT_SIZE, weight="bold"), text_color=settings.ctm.LABELS_TEXT_COLOR diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py index 8ec955d6..d663d17d 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py @@ -10,6 +10,6 @@ def createSettingBoxTopBar(config_window, settings, view_variable): config_window.setting_box_top_bar.grid(row=0, column=1, sticky="nsew") - _createSettingBoxTitle(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings) + _createSettingBoxTitle(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings, view_variable=view_variable) _createSettingBoxCompactModeButton(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings, view_variable=view_variable) \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py index 22e9d372..4e5ec347 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py @@ -51,8 +51,8 @@ def _addConfigSideMenuItem(config_window, settings, view_variable, side_menu_set config_window.main_setting_box_scrollable_container._parent_canvas.yview_moveto("0") - def switchToTargetSettingBoxContainer(e, text, target_active_tab_widget_attr_name, target_setting_box_container_attr_name): - config_window.main_current_active_config_title.configure(text=text) + def switchToTargetSettingBoxContainer(textvariable, target_active_tab_widget_attr_name, target_setting_box_container_attr_name): + view_variable.VAR_CURRENT_ACTIVE_CONFIG_TITLE.set(textvariable.get()) target_active_tab_widget = getattr(config_window, target_active_tab_widget_attr_name) switchSettingBoxContainerTabFunction(target_active_tab_widget) switchSettingBoxContainer(target_setting_box_container_attr_name) @@ -65,11 +65,10 @@ def _addConfigSideMenuItem(config_window, settings, view_variable, side_menu_set side_menu_tab_attr_name = side_menu_settings["side_menu_tab_attr_name"] label_attr_name = side_menu_settings["label_attr_name"] selected_mark_attr_name = side_menu_settings["selected_mark_attr_name"] - text = side_menu_settings["text"] + textvariable = side_menu_settings["textvariable"] setting_box_container_attr_name = side_menu_settings["setting_box_container_settings"]["setting_box_container_attr_name"] - command = lambda e: switchToTargetSettingBoxContainer( - e=e, - text=text, + command = lambda _e: switchToTargetSettingBoxContainer( + textvariable=textvariable, target_active_tab_widget_attr_name=side_menu_tab_attr_name, target_setting_box_container_attr_name=setting_box_container_attr_name, ) @@ -84,7 +83,7 @@ def _addConfigSideMenuItem(config_window, settings, view_variable, side_menu_set label_widget = CTkLabel( frame_widget, - text=text, + textvariable=textvariable, height=0, corner_radius=0, font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SIDE_MENU_LABELS_FONT_SIZE, weight="normal"), diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py index 0a007e41..6467786a 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py @@ -4,12 +4,12 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel def _createSettingBoxContainer(config_window, settings, view_variable, setting_box_container_settings): - def createSectionTitle(container_widget, section_title): + def createSectionTitle(container_widget, var_section_title): setting_box_wrapper_section_title_frame = CTkFrame(container_widget, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) setting_box_wrapper_section_title = CTkLabel( setting_box_wrapper_section_title_frame, - text=section_title, + textvariable=var_section_title, anchor="w", height=0, font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SB__SECTION_TITLE_FONT_SIZE, weight="normal"), @@ -35,10 +35,10 @@ def _createSettingBoxContainer(config_window, settings, view_variable, setting_b setting_box_and_section_title_wrapper = CTkFrame(setting_box_container_widget, fg_color=settings.ctm.SB__WRAPPER_BG_COLOR, corner_radius=0, width=0, height=0) - if setting_box_setting["section_title"] is not None: + if setting_box_setting["var_section_title"] is not None: setting_box_wrapper_section_title_frame= createSectionTitle( container_widget=setting_box_and_section_title_wrapper, - section_title=setting_box_setting["section_title"], + var_section_title=setting_box_setting["var_section_title"], ) setting_box_wrapper_section_title_frame.grid(row=0, column=0, sticky="ew", padx=0, pady=0) if i == 0: SB__TOP_PADY = settings.uism.SB__TOP_PADY_IF_WITH_SECTION_TITLE diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index 390220d4..6b8fa141 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -52,11 +52,11 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl "side_menu_tab_attr_name": "side_menu_tab_appearance", "label_attr_name": "label_appearance", "selected_mark_attr_name": "selected_mark_appearance", - "text": "Appearance", + "textvariable": view_variable.VAR_SIDE_MENU_LABEL_APPEARANCE, "setting_box_container_settings": { "setting_box_container_attr_name": "setting_box_container_appearance", "setting_boxes": [ - { "section_title": None, "setting_box": createSettingBox_Appearance }, + { "var_section_title": None, "setting_box": createSettingBox_Appearance }, ] }, }, @@ -64,11 +64,11 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl "side_menu_tab_attr_name": "side_menu_tab_translation", "label_attr_name": "label_translation", "selected_mark_attr_name": "selected_mark_translation", - "text": "Translation", + "textvariable": view_variable.VAR_SIDE_MENU_LABEL_TRANSLATION, "setting_box_container_settings": { "setting_box_container_attr_name": "setting_box_container_translation", "setting_boxes": [ - { "section_title": None, "setting_box": createSettingBox_Translation }, + { "var_section_title": None, "setting_box": createSettingBox_Translation }, ] }, }, @@ -76,12 +76,18 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl "side_menu_tab_attr_name": "side_menu_tab_transcription", "label_attr_name": "label_transcription", "selected_mark_attr_name": "selected_mark_transcription", - "text": "Transcription", + "textvariable": view_variable.VAR_SIDE_MENU_LABEL_TRANSCRIPTION, "setting_box_container_settings": { "setting_box_container_attr_name": "setting_box_container_transcription", "setting_boxes": [ - { "section_title": "Mic", "setting_box": createSettingBox_Mic }, - { "section_title": "Speaker", "setting_box": createSettingBox_Speaker }, + { + "var_section_title": view_variable.VAR_SECOND_TITLE_TRANSCRIPTION_MIC, + "setting_box": createSettingBox_Mic + }, + { + "var_section_title": view_variable.VAR_SECOND_TITLE_TRANSCRIPTION_SPEAKER, + "setting_box": createSettingBox_Speaker + }, ] }, }, @@ -89,11 +95,11 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl "side_menu_tab_attr_name": "side_menu_tab_others", "label_attr_name": "label_others", "selected_mark_attr_name": "selected_mark_others", - "text": "Others", + "textvariable": view_variable.VAR_SIDE_MENU_LABEL_OTHERS, "setting_box_container_settings": { "setting_box_container_attr_name": "setting_box_container_others", "setting_boxes": [ - { "section_title": None, "setting_box": createSettingBox_Others }, + { "var_section_title": None, "setting_box": createSettingBox_Others }, ] }, }, @@ -101,11 +107,11 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl "side_menu_tab_attr_name": "side_menu_tab_advanced", "label_attr_name": "label_advanced", "selected_mark_attr_name": "selected_mark_advanced", - "text": "Advanced Settings", + "textvariable": view_variable.VAR_SIDE_MENU_LABEL_ADVANCED_SETTINGS, "setting_box_container_settings": { "setting_box_container_attr_name": "setting_box_container_advanced", "setting_boxes": [ - { "section_title": None, "setting_box": createSettingBox_AdvancedSettings }, + { "var_section_title": None, "setting_box": createSettingBox_AdvancedSettings }, ] }, }, @@ -138,7 +144,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl if sm_and_sbc_setting["side_menu_tab_attr_name"] == view_variable.ACTIVE_SETTING_BOX_TAB_ATTR_NAME: # Set default active side menu tab - config_window.main_current_active_config_title.configure(text=sm_and_sbc_setting["text"]) + view_variable.VAR_CURRENT_ACTIVE_CONFIG_TITLE.set(sm_and_sbc_setting["textvariable"].get()) config_window.current_active_side_menu_tab = getattr(config_window, sm_and_sbc_setting["side_menu_tab_attr_name"]) _setDefaultActiveTab( active_tab_widget=config_window.current_active_side_menu_tab, From cae92e2252f41b0934bdf5f24351f3806f0c6e57 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 26 Sep 2023 12:40:48 +0900 Subject: [PATCH 199/355] =?UTF-8?q?[Update]=20Config=20Window:=20Localizat?= =?UTF-8?q?ion.=20=E3=81=A8=E3=82=8A=E3=81=82=E3=81=88=E3=81=9A=E3=81=AE?= =?UTF-8?q?=E6=97=A5=E6=9C=AC=E8=AA=9E=E3=81=95=E3=82=89=E3=81=AB=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=EF=BC=88=E8=A1=A8=E8=A8=98=E3=82=86=E3=82=8C=E3=81=82?= =?UTF-8?q?=E3=82=8A=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 3 +-- locales/ja.yml | 62 +++++++++++++++++++++++++++++++++++++++++++++++++- view.py | 2 +- 3 files changed, 63 insertions(+), 4 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 7a9e6b1c..2c6ed497 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -49,7 +49,7 @@ config_window: label: DeepL Auth Key mic_host: - label: Mic Host + label: Mic Host/Driver mic_device: label: Mic Device mic_energy_threshold: @@ -87,7 +87,6 @@ config_window: auto_clear_the_message_box: label: Auto Clear The Message Box - desc: Clear the message box after sending your message. notice_xsoverlay: label: Notification XSOverlay (VR Only) desc: Notify received messages by using XSOverlay's notification feature. diff --git a/locales/ja.yml b/locales/ja.yml index c964cf18..040bb36c 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -47,4 +47,64 @@ config_window: tab_translation: 翻訳 deepl_auth_key: - label: DeepL 認証キー \ No newline at end of file + label: DeepL 認証キー + + mic_host: + label: マイク(ホスト/ドライバー) + mic_device: + label: マイク (デバイス) + mic_energy_threshold: + label: 音声取得のしきい値 + desc: スライダーを調整してしきい値を決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。 + mic_dynamic_energy_threshold: + label: 音声取得のしきい値の自動調整 + desc: 有効にすると、設定されたしきい値に応じて、ある程度自動的に調節されます。 + mic_record_timeout: + label: マイク音声の区切りの無音時間 + mic_phrase_timeout: + label: 文字起こしする音声時間の上限 + mic_max_phrase: + label: 保留する単語の上限(マイク) + desc: It will stop recording and send the recordings when the set count of phrase(s) is reached. + mic_word_filter: + label: ワードフィルタ + desc: "設定された単語を検出すると、その文章は送信されません。\n設定の例: AAA,BBB,CCC" + + speaker_device: + label: スピーカー(デバイス) + speaker_energy_threshold: + label: 音声取得のしきい値 + desc: スライダーを調整してしきい値を決められます。スピーカーのアイコンを押すと、設定されたデバイスから音を聞き取り、音量を確認しながら調節できます。 + speaker_dynamic_energy_threshold: + label: 音声取得のしきい値の自動調整 + desc: 有効にすると、設定されたしきい値に応じて、ある程度自動的に調節されます。 + speaker_record_timeout: + label: スピーカー音声の区切りの無音時間 + speaker_phrase_timeout: + label: 文字起こしする音声時間の上限 + speaker_max_phrase: + label: 保留する単語の上限 + desc: It will stop recording and receive the recordings when the set count of phrase(s) is reached. + + auto_clear_the_message_box: + label: 送信後はチャットボックスを空にする + notice_xsoverlay: + label: XSOverlayの通知機能を有効 (VR限定) + desc: 文字起こし(受信)されたメッセージをXSOverlayの機能を使って通知として受け取れます。 + auto_export_message_logs: + label: 会話ログを自動的に保存する + desc: テキストファイルとしてログが保存されます。保存先は/logs/...(調整中) + message_format: + label: 送信するメッセージのフォーマット + desc: VRChatで相手に実際に見えるフォーマットを変更できます。 + send_message_to_vrc: + label: VRChatにメッセージを送信する + desc: VRChatにメッセージを送信せずに使う方法があります。サポート対象外ですが。 + startup_osc_enabled_check: + label: 起動時にOSCが有効になっているか確認する + desc: 起動時に毎回、キャラクターがほんの少し前進します。もしsit判定などある場所に座っている場合、立ってしまうかもしれません。残念ながら今のところ私達はOSCがVRChat側で有効になっているか確認する方法がこれしか見つけられていません。ごめんね。(このチェック機能をオフにする場合、自分でOSCをオンにすることを忘れないでね。) + + osc_ip_address: + label: OSC IP Address + osc_port: + label: OSC Port \ No newline at end of file diff --git a/view.py b/view.py index 613fe0a4..2f47a7fd 100644 --- a/view.py +++ b/view.py @@ -256,7 +256,7 @@ class View(): # Others Tab VAR_LABEL_ENABLE_AUTO_CLEAR_MESSAGE_BOX=StringVar(value=i18n.t("config_window.auto_clear_the_message_box.label")), - VAR_DESC_ENABLE_AUTO_CLEAR_MESSAGE_BOX=StringVar(value=i18n.t("config_window.auto_clear_the_message_box.desc")), + VAR_DESC_ENABLE_AUTO_CLEAR_MESSAGE_BOX=None, CALLBACK_SET_ENABLE_AUTO_CLEAR_MESSAGE_BOX=None, VAR_ENABLE_AUTO_CLEAR_MESSAGE_BOX=BooleanVar(value=config.ENABLE_AUTO_CLEAR_MESSAGE_BOX), From 0efd82b04997fb4fcf381d27ba7c36675fc62658 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 26 Sep 2023 15:48:47 +0900 Subject: [PATCH 200/355] =?UTF-8?q?[Update/bugfix/Refactor]=20Main=20Windo?= =?UTF-8?q?w:=20Sidebar=20Compact=20Mode=20=E6=9C=89=E5=8A=B9=E7=84=A1?= =?UTF-8?q?=E5=8A=B9=E5=80=A4=E3=81=8Cjson=E3=81=AB=E4=BF=9D=E5=AD=98?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=8B=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=81=E8=B5=B7?= =?UTF-8?q?=E5=8B=95=E6=99=82=E3=82=82=E4=BF=9D=E6=8C=81=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=80=82=20=E3=82=B3?= =?UTF-8?q?=E3=83=B3=E3=83=91=E3=82=AF=E3=83=88=E3=83=A2=E3=83=BC=E3=83=89?= =?UTF-8?q?=E6=9C=89=E5=8A=B9=E7=84=A1=E5=8A=B9=E9=96=A2=E6=95=B0=E5=88=86?= =?UTF-8?q?=E3=81=91=E3=80=82=20main.py=E3=81=A7=E5=8F=97=E3=81=91?= =?UTF-8?q?=E5=8F=96=E3=82=8Bmain=20window=E7=B3=BB=20callback=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E3=81=AEkey=E3=82=92=E3=81=BE=E3=81=A8=E3=82=81?= =?UTF-8?q?=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 12 ++++ main.py | 18 +++-- view.py | 65 +++++++++---------- .../widgets/create_minimize_sidebar_button.py | 4 +- vrct_gui/vrct_gui.py | 22 +++---- 5 files changed, 69 insertions(+), 52 deletions(-) diff --git a/config.py b/config.py index 5aa121e6..bf6f3a6b 100644 --- a/config.py +++ b/config.py @@ -181,6 +181,17 @@ class Config: self._SELECTED_TAB_TARGET_LANGUAGES = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property + @json_serializable('IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE') + def IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE(self): + return self._IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE + + @IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE.setter + def IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE(self, value): + if type(value) is bool: + self._IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + ## Config Window @property @json_serializable('TRANSPARENCY') @@ -542,6 +553,7 @@ class Config: "2":"English\n(United States)", "3":"English\n(United States)", } + self._IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = False ## Config Window self._TRANSPARENCY = 100 diff --git a/main.py b/main.py index 5c2aa257..f09db07d 100644 --- a/main.py +++ b/main.py @@ -270,6 +270,14 @@ def callbackToggleForeground(is_turned_on): view.printToTextbox_disableForeground() view.foregroundOff() +def callbackEnableMainWindowSidebarCompactMode(): + config.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = True + view.enableMainWindowSidebarCompactMode() + +def callbackDisableMainWindowSidebarCompactMode(): + config.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = False + view.disableMainWindowSidebarCompactMode() + # Config Window def callbackOpenConfigWindow(): view.setMainWindowAllWidgetsStatusToDisabled() @@ -610,22 +618,20 @@ view.register( "callback_close_config_window": callbackCloseConfigWindow, }, - sidebar_features_registers={ + main_window_registers={ + "callback_enable_main_window_sidebar_compact_mode": callbackEnableMainWindowSidebarCompactMode, + "callback_disable_main_window_sidebar_compact_mode": callbackDisableMainWindowSidebarCompactMode, + "callback_toggle_translation": callbackToggleTranslation, "callback_toggle_transcription_send": callbackToggleTranscriptionSend, "callback_toggle_transcription_receive": callbackToggleTranscriptionReceive, "callback_toggle_foreground": callbackToggleForeground, - }, - language_presets_registers={ "callback_your_language": setYourLanguageAndCountry, "callback_target_language": setTargetLanguageAndCountry, "values": model.getListLanguageAndCountry(), "callback_selected_language_preset_tab": callbackSelectedLanguagePresetTab, - }, - - entry_message_box_registers={ "bind_Return": messageBoxPressKeyEnter, "bind_Any_KeyPress": messageBoxPressKeyAny, }, diff --git a/view.py b/view.py index 2f47a7fd..94b4e286 100644 --- a/view.py +++ b/view.py @@ -68,7 +68,7 @@ class View(): # Main Window # Sidebar # Sidebar Compact Mode - IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE=False, + IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE=config.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE, CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE=None, # Sidebar Features @@ -307,9 +307,7 @@ class View(): def register( self, window_action_registers=None, - sidebar_features_registers=None, - language_presets_registers=None, - entry_message_box_registers=None, + main_window_registers=None, config_window_registers=None ): @@ -321,39 +319,35 @@ class View(): - self.view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = self._toggleMainWindowSidebarCompactMode - if sidebar_features_registers is not None: - self.view_variable.CALLBACK_TOGGLE_TRANSLATION = sidebar_features_registers.get("callback_toggle_translation", None) - self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = sidebar_features_registers.get("callback_toggle_transcription_send", None) - self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = sidebar_features_registers.get("callback_toggle_transcription_receive", None) - self.view_variable.CALLBACK_TOGGLE_FOREGROUND = sidebar_features_registers.get("callback_toggle_foreground", None) + if main_window_registers is not None: + self.view_variable.CALLBACK_ENABLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = main_window_registers.get("callback_enable_main_window_sidebar_compact_mode", None) + self.view_variable.CALLBACK_DISABLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = main_window_registers.get("callback_disable_main_window_sidebar_compact_mode", None) - if language_presets_registers is not None: - self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE = language_presets_registers.get("callback_your_language", None) - self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE = language_presets_registers.get("callback_target_language", None) - language_presets_registers.get("values", None) and self.updateList_selectableLanguages(language_presets_registers["values"]) - self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = language_presets_registers.get("callback_selected_language_preset_tab", None) + self.view_variable.CALLBACK_TOGGLE_TRANSLATION = main_window_registers.get("callback_toggle_translation", None) + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND = main_window_registers.get("callback_toggle_transcription_send", None) + self.view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE = main_window_registers.get("callback_toggle_transcription_receive", None) + self.view_variable.CALLBACK_TOGGLE_FOREGROUND = main_window_registers.get("callback_toggle_foreground", None) + + self.view_variable.CALLBACK_SELECTED_YOUR_LANGUAGE = main_window_registers.get("callback_your_language", None) + self.view_variable.CALLBACK_SELECTED_TARGET_LANGUAGE = main_window_registers.get("callback_target_language", None) + main_window_registers.get("values", None) and self.updateList_selectableLanguages(main_window_registers["values"]) + + self.view_variable.CALLBACK_SELECTED_LANGUAGE_PRESET_TAB = main_window_registers.get("callback_selected_language_preset_tab", None) + + + entry_message_box = getattr(vrct_gui, "entry_message_box") + entry_message_box.bind("", main_window_registers.get("bind_Return")) + entry_message_box.bind("", main_window_registers.get("bind_Any_KeyPress")) + + + entry_message_box.bind("", self._foregroundOffForcefully) + entry_message_box.bind("", self._foregroundOnForcefully) self.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) - - - self.view_variable.CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_YourLanguage - self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_TargetLanguage - - entry_message_box = getattr(vrct_gui, "entry_message_box") - if entry_message_box_registers is not None: - entry_message_box.bind("", entry_message_box_registers.get("bind_Return")) - entry_message_box.bind("", entry_message_box_registers.get("bind_Any_KeyPress")) - - - entry_message_box.bind("", self._foregroundOffForcefully) - entry_message_box.bind("", self._foregroundOnForcefully) - - # Config Window self.view_variable.CALLBACK_SELECTED_SETTING_BOX_TAB=self._updateActiveSettingBoxTabNo @@ -470,13 +464,18 @@ class View(): vrct_gui.attributes("-topmost", False) - def _toggleMainWindowSidebarCompactMode(self, is_turned_on): - self.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = is_turned_on - vrct_gui.recreateMainWindowSidebar() + def enableMainWindowSidebarCompactMode(self): + self.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = True + vrct_gui.enableMainWindowSidebarCompactMode() + + def disableMainWindowSidebarCompactMode(self): + self.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = False + vrct_gui.disableMainWindowSidebarCompactMode() def openSelectableLanguagesWindow_YourLanguage(self, _e): self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set(i18n.t("selectable_language_window.title_your_language")) vrct_gui.openSelectableLanguagesWindow("your_language") + def openSelectableLanguagesWindow_TargetLanguage(self, _e): self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set(i18n.t("selectable_language_window.title_target_language")) vrct_gui.openSelectableLanguagesWindow("target_language") diff --git a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py index c5025b76..de15cc89 100644 --- a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py +++ b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py @@ -8,10 +8,10 @@ from utils import callFunctionIfCallable def createMinimizeSidebarButton(settings, main_window, view_variable): def enableCompactMode(e): - callFunctionIfCallable(view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE, True) + callFunctionIfCallable(view_variable.CALLBACK_ENABLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE) def disableCompactMode(e): - callFunctionIfCallable(view_variable.CALLBACK_TOGGLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE, False) + callFunctionIfCallable(view_variable.CALLBACK_DISABLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 68898a37..4913c64b 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -147,17 +147,17 @@ class VRCT_GUI(CTk): active_text_color=self.settings.main.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR ) - def recreateMainWindowSidebar(self): - if self._view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE: - self.sidebar_bg_container.grid_remove() - self.sidebar_compact_mode_bg_container.grid() - self.minimize_sidebar_button_container__for_closing.grid_remove() - self.minimize_sidebar_button_container__for_opening.grid() - else: - self.sidebar_compact_mode_bg_container.grid_remove() - self.sidebar_bg_container.grid() - self.minimize_sidebar_button_container__for_opening.grid_remove() - self.minimize_sidebar_button_container__for_closing.grid() + def enableMainWindowSidebarCompactMode(self): + self.sidebar_bg_container.grid_remove() + self.sidebar_compact_mode_bg_container.grid() + self.minimize_sidebar_button_container__for_closing.grid_remove() + self.minimize_sidebar_button_container__for_opening.grid() + + def disableMainWindowSidebarCompactMode(self): + self.sidebar_compact_mode_bg_container.grid_remove() + self.sidebar_bg_container.grid() + self.minimize_sidebar_button_container__for_opening.grid_remove() + self.minimize_sidebar_button_container__for_closing.grid() vrct_gui = VRCT_GUI() \ No newline at end of file From c42257394b425ec0920502c7b813f0b053c619d3 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 26 Sep 2023 15:53:04 +0900 Subject: [PATCH 201/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20build.bat=20:=20?= =?UTF-8?q?locales=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.bat | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.bat b/build.bat index 9ee59eff..e7029fb2 100644 --- a/build.bat +++ b/build.bat @@ -1 +1 @@ -pyinstaller --onedir --onefile --windowed --clean --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --name VRCT --exclude-module numpy --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py \ No newline at end of file +pyinstaller --onedir --onefile --windowed --clean --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --add-data "./locales;locales/" --name VRCT --exclude-module numpy --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py \ No newline at end of file From 5a5d4ad3b804edead0757d16bf73dc7704e3f8f2 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 26 Sep 2023 17:58:06 +0900 Subject: [PATCH 202/355] =?UTF-8?q?[bugfix]=20Main=20Window:=20Selectable?= =?UTF-8?q?=20Language=20Window:=20Callback=E9=96=A2=E6=95=B0=E3=82=92?= =?UTF-8?q?=E8=AA=A4=E3=81=A3=E3=81=A6=E6=B6=88=E3=81=97=E3=81=A6=E3=81=97?= =?UTF-8?q?=E3=81=BE=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=80=82=20=E3=83=A1=E3=83=A2:=20=E6=B6=88?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=81=97=E3=81=BE=E3=81=A3=E3=81=A6=E3=81=9F?= =?UTF-8?q?=E5=95=8F=E9=A1=8C=E3=81=AE=E3=82=B3=E3=83=9F=E3=83=83=E3=83=88?= =?UTF-8?q?=E3=83=8F=E3=83=83=E3=82=B7=E3=83=A5=E3=80=8C0efd82b04997fb4fcf?= =?UTF-8?q?381d27ba7c36675fc62658=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/view.py b/view.py index 94b4e286..96c45696 100644 --- a/view.py +++ b/view.py @@ -348,6 +348,9 @@ class View(): self.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) + self.view_variable.CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_YourLanguage + self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_TargetLanguage + # Config Window self.view_variable.CALLBACK_SELECTED_SETTING_BOX_TAB=self._updateActiveSettingBoxTabNo From 2270cff00616e6ccb16dd626a6d502bd148cfbc5 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 27 Sep 2023 21:44:36 +0900 Subject: [PATCH 203/355] =?UTF-8?q?[fix]=20Config=20Window:=20Compact=20Mo?= =?UTF-8?q?de.=20=E4=BB=8A=E3=81=BE=E3=81=A7Destroy()=E3=81=97=E3=81=A6Wid?= =?UTF-8?q?gets=E3=81=8C=E5=86=8D=E7=94=9F=E6=88=90=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE=E3=82=92=E3=80=81grid=5Fre?= =?UTF-8?q?move=E3=82=92=E4=BD=BF=E3=81=86=E6=96=B9=E6=B3=95=E3=81=AB?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E3=80=81=E3=81=93=E3=82=8C=E3=81=AB=E3=82=88?= =?UTF-8?q?=E3=82=8A=E3=82=B3=E3=83=B3=E3=83=91=E3=82=AF=E3=83=88=E3=83=A2?= =?UTF-8?q?=E3=83=BC=E3=83=89=E5=88=87=E3=82=8A=E6=9B=BF=E3=81=88=E3=81=8C?= =?UTF-8?q?=E3=82=B9=E3=83=A0=E3=83=BC=E3=82=BA=E3=81=AB=E3=81=AA=E3=82=8A?= =?UTF-8?q?=E3=81=BE=E3=81=97=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 4 ++-- view.py | 20 +++++++++++++------ vrct_gui/config_window/ConfigWindow.py | 10 +++------- .../_createSettingBoxCompactModeButton.py | 5 +---- .../_SettingBoxGenerator.py | 9 ++++----- 5 files changed, 24 insertions(+), 24 deletions(-) diff --git a/main.py b/main.py index f09db07d..311827b0 100644 --- a/main.py +++ b/main.py @@ -314,7 +314,7 @@ def callbackEnableConfigWindowCompactMode(): model.stopCheckSpeakerEnergy() view.replaceSpeakerThresholdCheckButton_Passive() - view.reloadConfigWindowSettingBoxContainer() + view.enableConfigWindowCompactMode() def callbackDisableConfigWindowCompactMode(): config.IS_CONFIG_WINDOW_COMPACT_MODE = False @@ -323,7 +323,7 @@ def callbackDisableConfigWindowCompactMode(): model.stopCheckSpeakerEnergy() view.replaceSpeakerThresholdCheckButton_Passive() - view.reloadConfigWindowSettingBoxContainer() + view.disableConfigWindowCompactMode() # Appearance Tab def callbackSetTransparency(value): diff --git a/view.py b/view.py index 96c45696..e1f924ad 100644 --- a/view.py +++ b/view.py @@ -121,7 +121,6 @@ class View(): # Config Window ACTIVE_SETTING_BOX_TAB_ATTR_NAME="side_menu_tab_appearance", CALLBACK_SELECTED_SETTING_BOX_TAB=None, - IS_CONFIG_WINDOW_COMPACT_MODE=config.IS_CONFIG_WINDOW_COMPACT_MODE, # Side Menu Labels VAR_SIDE_MENU_LABEL_APPEARANCE=StringVar(value=i18n.t("config_window.side_menu_labels.appearance")), @@ -412,6 +411,11 @@ class View(): self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window_registers.get("callback_set_osc_ip_address", None) self.view_variable.CALLBACK_SET_OSC_PORT = config_window_registers.get("callback_set_osc_port", None) + # The initial processing after registration. + if config.IS_CONFIG_WINDOW_COMPACT_MODE is True: + self.enableConfigWindowCompactMode() + vrct_gui.config_window.setting_box_compact_mode_switch_box.select() + # Insert sample conversation for testing. # self._insertSampleConversationToTextbox() @@ -593,6 +597,15 @@ class View(): vrct_gui.wm_attributes("-alpha", transparency) + def enableConfigWindowCompactMode(self): + for additional_widget in vrct_gui.config_window.additional_widgets: + additional_widget.grid_remove() + + def disableConfigWindowCompactMode(self): + for additional_widget in vrct_gui.config_window.additional_widgets: + additional_widget.grid() + + def createGUI(self): vrct_gui.createGUI(settings=self.settings, view_variable=self.view_variable) @@ -658,11 +671,6 @@ class View(): - def reloadConfigWindowSettingBoxContainer(self): - self.view_variable.IS_CONFIG_WINDOW_COMPACT_MODE = config.IS_CONFIG_WINDOW_COMPACT_MODE - vrct_gui.config_window.reloadConfigWindowSettingBoxContainer() - - def updateList_MicHost(self, new_mic_host_list:list): self.view_variable.LIST_MIC_HOST = new_mic_host_list vrct_gui.config_window.sb__optionmenu_mic_host.configure(values=new_mic_host_list) diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 8e0a3155..26d94b46 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -23,16 +23,12 @@ class ConfigWindow(CTkToplevel): self.settings = settings self._view_variable = view_variable + # When the configuration window's compact mode is turned on, it will call `grid_remove()` on each widget appended to this array. In the opposite case, `grid()` will be called. + self.additional_widgets = [] + createConfigWindowTitle(config_window=self, settings=self.settings) createSettingBoxTopBar(config_window=self, settings=self.settings, view_variable=self._view_variable) - createSideMenuAndSettingsBoxContainers(config_window=self, settings=self.settings, view_variable=self._view_variable) - - - - - def reloadConfigWindowSettingBoxContainer(self): - self.main_bg_container.destroy() createSideMenuAndSettingsBoxContainers(config_window=self, settings=self.settings, view_variable=self._view_variable) \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py index c9fbfd06..79ae86da 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py @@ -60,7 +60,4 @@ def _createSettingBoxCompactModeButton(parent_widget, config_window, settings, v progress_color=settings.ctm.SB__SWITCH_BOX_ACTIVE_BG_COLOR, # SB__SWITCH_BOX_ACTIVE_BG_COLOR is for SB. change it later. ) - config_window.setting_box_compact_mode_switch_box.select() if view_variable.IS_CONFIG_WINDOW_COMPACT_MODE else config_window.setting_box_compact_mode_switch_box.deselect() - - config_window.setting_box_compact_mode_switch_box.grid(row=0, column=0) - + config_window.setting_box_compact_mode_switch_box.grid(row=0, column=0) \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 07da4153..500b6c6d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -53,10 +53,8 @@ class _SettingBoxGenerator(): ) setting_box_label.grid(row=0, column=0, padx=0, pady=0, sticky="ew") - if for_var_desc_text == None or self.view_variable.IS_CONFIG_WINDOW_COMPACT_MODE is True: - pass - else: - self.setting_box_desc = CTkLabel( + if for_var_desc_text is not None: + setting_box_desc = CTkLabel( setting_box_labels_frame, textvariable=for_var_desc_text, anchor="w", @@ -66,7 +64,8 @@ class _SettingBoxGenerator(): font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__DESC_FONT_SIZE, weight="normal"), text_color=self.settings.ctm.LABELS_DESC_TEXT_COLOR ) - self.setting_box_desc.grid(row=1, column=0, padx=0, pady=(self.settings.uism.SB__DESC_TOP_PADY,0), sticky="ew") + setting_box_desc.grid(row=1, column=0, padx=0, pady=(self.settings.uism.SB__DESC_TOP_PADY,0), sticky="ew") + self.config_window.additional_widgets.append(setting_box_desc) def createSettingBoxDropdownMenu(self, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, variable=None, dropdown_menu_values=None): From af07c85bf4f6c13eb33b0f159b5ef9c5b5dee56a Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 27 Sep 2023 22:22:22 +0900 Subject: [PATCH 204/355] =?UTF-8?q?[Refactor]=20Main=20Window:=20Sidebar?= =?UTF-8?q?=20Compact=20Mode.=20=E5=88=9D=E6=9C=9F=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E3=81=AE=E5=87=A6=E7=90=86=E6=96=B9=E6=B3=95=E3=82=92=E4=BB=96?= =?UTF-8?q?=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=81=A6=E8=AA=BF=E6=95=B4?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/main_window/widgets/create_sidebar.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 51f7ddab..75aecb91 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -26,12 +26,12 @@ def createSidebar(settings, main_window, view_variable): main_window.sidebar_bg_container.grid(row=0, column=0, sticky="nsew") main_window.sidebar_compact_mode_bg_container.grid(row=0, column=0, sticky="nsew") - + main_window.sidebar_bg_container.grid_remove() + main_window.sidebar_compact_mode_bg_container.grid_remove() if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE: - main_window.sidebar_bg_container.grid_remove() + main_window.sidebar_compact_mode_bg_container.grid() else: - main_window.sidebar_compact_mode_bg_container.grid_remove() - + main_window.sidebar_bg_container.grid() # Config Button From 3a4be19bb5744e97ce496aadec98e87ba1796d7b Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 29 Sep 2023 02:51:51 +0900 Subject: [PATCH 205/355] =?UTF-8?q?[Refactor/fix]=20Config=20Window:=20=5F?= =?UTF-8?q?SettingBoxGenerator=E5=91=A8=E3=82=8A=E3=81=AE=E3=83=AA?= =?UTF-8?q?=E3=83=95=E3=82=A1=E3=82=AF=E3=82=BF=E3=83=AA=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E3=80=82=E8=A6=8B=E3=81=9F=E7=9B=AE=E3=81=AF=E5=90=8C=E3=81=98?= =?UTF-8?q?=E3=81=A0=E3=81=91=E3=81=A9=E6=A7=8B=E9=80=A0=E3=82=92=E7=B5=90?= =?UTF-8?q?=E6=A7=8B=E5=A4=89=E3=81=88=E3=81=A6=E3=81=84=E3=82=8B=E3=80=82?= =?UTF-8?q?=20=E3=81=BE=E3=81=9F=E3=80=81ui=5Futils=E3=81=AB=E3=81=82?= =?UTF-8?q?=E3=82=8BcreateButtonWithImage=E3=81=AF=E3=80=81circle=E3=82=92?= =?UTF-8?q?=E5=BB=83=E6=AD=A2=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/config_window/ConfigWindow.py | 2 +- .../_SettingBoxGenerator.py | 199 +++++++----------- vrct_gui/ui_utils/ui_utils.py | 9 +- 3 files changed, 73 insertions(+), 137 deletions(-) diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 26d94b46..45bd1cab 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -8,7 +8,7 @@ from ..ui_utils import getImagePath class ConfigWindow(CTkToplevel): def __init__(self, vrct_gui, settings, view_variable): super().__init__() - self.withdraw() + # self.withdraw() # configure window diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 500b6c6d..0b35cbe8 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -1,6 +1,6 @@ from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar, END as CTK_END -from vrct_gui.ui_utils import createButtonWithImage +from vrct_gui.ui_utils import createButtonWithImage, getLatestWidth from typing import Union @@ -12,7 +12,7 @@ class _SettingBoxGenerator(): self.settings = settings - def _createSettingBoxFrame(self, for_var_label_text, for_var_desc_text): + def _createSettingBoxFrame(self, setting_box_item_frame, for_var_label_text, for_var_desc_text): setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) # setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color="gray", width=0, height=0) @@ -24,24 +24,29 @@ class _SettingBoxGenerator(): # setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color="gray", width=0, height=0) setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=self.settings.uism.SB__MAIN_WIDTH, height=0) setting_box_frame_wrapper.grid(row=0, column=0, padx=self.settings.uism.SB__IPADX, pady=self.settings.uism.SB__IPADY, sticky="ew") - setting_box_frame_wrapper.grid_columnconfigure((0,1), weight=1, minsize=int(self.settings.uism.SB__MAIN_WIDTH / 2)) - + setting_box_frame_wrapper.grid_columnconfigure((0,1), weight=1, minsize=int(self.settings.uism.SB__MAIN_WIDTH / 2), uniform="setting_box") # setting_box_frame_wrapper.grid(column=0, padx=0, pady=0) - setting_box_frame_wrapper.grid(row=0, column=0, padx=self.settings.uism.SB__IPADX, pady=self.settings.uism.SB__IPADY, sticky="ew") + setting_box_frame_wrapper.grid(row=0, column=0, padx=self.settings.uism.SB__IPADX, pady=self.settings.uism.SB__IPADY, sticky="nsew") setting_box_frame_wrapper_border = CTkFrame(setting_box_frame, corner_radius=0, fg_color="red", width=0, height=0) setting_box_frame_wrapper_border.grid(row=1, column=0, sticky="ew") - self._setSettingBoxLabels(setting_box_frame_wrapper, for_var_label_text, for_var_desc_text) - return (setting_box_frame, setting_box_frame_wrapper) + # setting_box_item_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color="black") + setting_box_item_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) + setting_box_item_frame.grid(row=0, column=1, padx=0, sticky="nsew") + setting_box_item_frame.rowconfigure((0,2), weight=1) + setting_box_item_frame.grid_columnconfigure(0, weight=1) + + return (setting_box_frame, setting_box_item_frame) def _setSettingBoxLabels(self, setting_box_frame_wrapper, for_var_label_text, for_var_desc_text=None): + # setting_box_labels_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, fg_color="black", width=0, height=0) setting_box_labels_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) - setting_box_labels_frame.grid(row=0, column=0, padx=0, pady=0, sticky="w") + setting_box_labels_frame.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") setting_box_label = CTkLabel( setting_box_labels_frame, @@ -69,18 +74,23 @@ class _SettingBoxGenerator(): def createSettingBoxDropdownMenu(self, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, variable=None, dropdown_menu_values=None): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(self, for_var_label_text, for_var_desc_text) - setting_box_dropdown_menu_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) - setting_box_dropdown_menu_frame.grid(row=0, column=1, padx=0, sticky="e") - - self.createOption_DropdownMenu( - setting_box_dropdown_menu_frame=setting_box_dropdown_menu_frame, - optionmenu_attr_name=optionmenu_attr_name, - dropdown_menu_values=dropdown_menu_values, - command=command, + option_menu_widget = CTkOptionMenu( + setting_box_item_frame, + height=self.settings.uism.SB__OPTIONMENU_HEIGHT, + width=self.settings.uism.SB__OPTIONMENU_WIDTH, + values=dropdown_menu_values, + button_color=self.settings.ctm.SB__OPTIONMENU_BG_COLOR, + button_hover_color=self.settings.ctm.SB__OPTIONMENU_HOVERED_BG_COLOR, + fg_color=self.settings.ctm.SB__OPTIONMENU_BG_COLOR, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), variable=variable, + command=command, + anchor="w", ) + option_menu_widget.grid(row=1, column=1, sticky="e") + setattr(self.config_window, optionmenu_attr_name, option_menu_widget) return setting_box_frame @@ -88,13 +98,10 @@ class _SettingBoxGenerator(): def createSettingBoxSwitch(self, for_var_label_text, for_var_desc_text, switch_attr_name, is_checked, command): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) - - setting_box_switch_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) - setting_box_switch_frame.grid(row=0, column=1, padx=0, sticky="e") + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(self, for_var_label_text, for_var_desc_text) switch_widget = CTkSwitch( - setting_box_switch_frame, + setting_box_item_frame, text=None, height=0, width=0, @@ -113,20 +120,17 @@ class _SettingBoxGenerator(): switch_widget.select() if is_checked else switch_widget.deselect() - switch_widget.grid(row=0, column=0) + switch_widget.grid(row=1, column=1) return setting_box_frame def createSettingBoxCheckbox(self, for_var_label_text, for_var_desc_text, checkbox_attr_name, variable, command): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) - - setting_box_checkbox_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) - setting_box_checkbox_frame.grid(row=0, column=1, padx=0, sticky="e") + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(self, for_var_label_text, for_var_desc_text) checkbox_widget = CTkCheckBox( - setting_box_checkbox_frame, + setting_box_item_frame, text=None, width=0, checkbox_width=self.settings.uism.SB__CHECKBOX_SIZE, @@ -149,7 +153,7 @@ class _SettingBoxGenerator(): # checkbox_widget.select() if is_checked else checkbox_widget.deselect() - checkbox_widget.grid(row=0, column=0) + checkbox_widget.grid(row=1, column=1) return setting_box_frame @@ -159,13 +163,10 @@ class _SettingBoxGenerator(): def createSettingBoxSlider(self, for_var_label_text, for_var_desc_text, slider_attr_name, slider_range, command, variable, slider_number_of_steps: Union[int, None] = None): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) - - setting_box_slider_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) - setting_box_slider_frame.grid(row=0, column=1, padx=0, sticky="e") + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(self, for_var_label_text, for_var_desc_text) slider_widget = CTkSlider( - setting_box_slider_frame, + setting_box_item_frame, from_=slider_range[0], to=slider_range[1], number_of_steps=slider_number_of_steps, @@ -176,33 +177,30 @@ class _SettingBoxGenerator(): ) setattr(self.config_window, slider_attr_name, slider_widget) - slider_widget.grid(row=0, column=0) + slider_widget.grid(row=1, column=1) return setting_box_frame - def createSettingBoxProgressbarXSlider(self, - for_var_label_text, for_var_desc_text, command, - entry_attr_name, - slider_attr_name, slider_range, - progressbar_attr_name, - passive_button_attr_name, passive_button_command, - active_button_attr_name, active_button_command, - button_image_file, - entry_variable, - slider_variable, + def createSettingBoxProgressbarXSlider( + self, + for_var_label_text, for_var_desc_text, command, + entry_attr_name, + slider_attr_name, slider_range, + progressbar_attr_name, + passive_button_attr_name, passive_button_command, + active_button_attr_name, active_button_command, + button_image_file, + entry_variable, + slider_variable, - slider_number_of_steps: Union[int, None] = None, + slider_number_of_steps: Union[int, None] = None, ): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) - - setting_box_progressbar_x_slider_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) - setting_box_progressbar_x_slider_frame.grid(row=0, column=1, padx=0, sticky="e") - + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(self, for_var_label_text, for_var_desc_text) ENTRY_WIDTH = self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH BAR_WIDTH = self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BAR_WIDTH @@ -216,7 +214,7 @@ class _SettingBoxGenerator(): command(value) entry_widget = CTkEntry( - setting_box_progressbar_x_slider_frame, + setting_box_item_frame, width=ENTRY_WIDTH, height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT, textvariable=entry_variable, @@ -224,7 +222,7 @@ class _SettingBoxGenerator(): ) entry_widget.bind("", adjusted_command__for_entry_bind__Any_KeyRelease) - entry_widget.grid(row=0, column=0, padx=0, pady=0, sticky="e") + entry_widget.grid(row=1, column=1, padx=0, pady=0, sticky="e") setattr(self.config_window, entry_attr_name, entry_widget) @@ -232,7 +230,7 @@ class _SettingBoxGenerator(): SLIDER_BORDER_WIDTH = max(2,self.settings.uism.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_LENGTH) SLIDER_BUTTON_LENGTH = int(SLIDER_BORDER_WIDTH/2) slider_widget = CTkSlider( - setting_box_progressbar_x_slider_frame, + setting_box_item_frame, from_=slider_range[0], to=slider_range[1], number_of_steps=slider_number_of_steps, @@ -250,31 +248,40 @@ class _SettingBoxGenerator(): progress_color=self.settings.ctm.SB__BG_COLOR, border_color=self.settings.ctm.SB__BG_COLOR, ) - slider_widget.grid(row=0, column=0, padx=(0, BAR_PADDING), sticky="e") + slider_widget.grid(row=1, column=1, padx=(0, BAR_PADDING), sticky="e") setattr(self.config_window, slider_attr_name, slider_widget) progressbar_widget = CTkProgressBar( - setting_box_progressbar_x_slider_frame, + setting_box_item_frame, width=BAR_WIDTH, height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__PROGRESSBAR_HEIGHT, corner_radius=0, ) setattr(self.config_window, progressbar_attr_name, progressbar_widget) - progressbar_widget.grid(row=0, column=0, padx=(0, BAR_PADDING), sticky="e") + progressbar_widget.grid(row=1, column=1, padx=(0, BAR_PADDING), sticky="e") progressbar_widget.set(0) - passive_button_wrapper = self._createPassiveButtonForProgressbarXSlider(setting_box_progressbar_x_slider_frame, BUTTON_PADDING, passive_button_command, button_image_file) + passive_button_wrapper = self._createPassiveButtonForProgressbarXSlider(setting_box_item_frame, passive_button_command, button_image_file) setattr(self.config_window, passive_button_attr_name, passive_button_wrapper) - active_button_wrapper = self._createActiveButtonForProgressbarXSlider(setting_box_progressbar_x_slider_frame, BUTTON_PADDING, active_button_command, button_image_file) + active_button_wrapper = self._createActiveButtonForProgressbarXSlider(setting_box_item_frame, active_button_command, button_image_file) setattr(self.config_window, active_button_attr_name, active_button_wrapper) + passive_button_wrapper.grid(row=1, column=1, padx=(0,BUTTON_PADDING), sticky="e") + passive_button_wrapper.configure(corner_radius=int(getLatestWidth(passive_button_wrapper)/2)) + + active_button_wrapper.grid(row=1, column=1, padx=(0,BUTTON_PADDING), sticky="e") + active_button_wrapper.configure(corner_radius=int(getLatestWidth(passive_button_wrapper)/2)) + + passive_button_wrapper.grid_remove() + active_button_wrapper.grid_remove() + passive_button_wrapper.grid() return setting_box_frame @@ -282,16 +289,13 @@ class _SettingBoxGenerator(): def createSettingBoxEntry(self, for_var_label_text, for_var_desc_text, entry_attr_name, entry_width, entry_bind__Any_KeyRelease, entry_textvariable): - (setting_box_frame, setting_box_frame_wrapper) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) - - setting_box_entry_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) - setting_box_entry_frame.grid(row=0, column=1, padx=0, sticky="e") + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(self, for_var_label_text, for_var_desc_text) def adjusted_command__for_entry_bind__Any_KeyRelease(e): entry_bind__Any_KeyRelease(e.widget.get()) entry_widget = CTkEntry( - setting_box_entry_frame, + setting_box_item_frame, width=entry_width, height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT, textvariable=entry_textvariable, @@ -301,7 +305,7 @@ class _SettingBoxGenerator(): setattr(self.config_window, entry_attr_name, entry_widget) - entry_widget.grid(row=0, column=0) + entry_widget.grid(row=1, column=1) return setting_box_frame @@ -405,62 +409,7 @@ class _SettingBoxGenerator(): - - - def createOption_DropdownMenu(self, setting_box_dropdown_menu_frame, optionmenu_attr_name, command, variable, dropdown_menu_values): - - # set the value to the option menu's variable automatically - # def adjustedCommand(selected_value): - # option_menu_widget.set(selected_value) - # command(selected_value) - - option_menu_widget = CTkOptionMenu( - setting_box_dropdown_menu_frame, - height=self.settings.uism.SB__OPTIONMENU_HEIGHT, - width=self.settings.uism.SB__OPTIONMENU_WIDTH, - values=dropdown_menu_values, - button_color=self.settings.ctm.SB__OPTIONMENU_BG_COLOR, - button_hover_color=self.settings.ctm.SB__OPTIONMENU_HOVERED_BG_COLOR, - fg_color=self.settings.ctm.SB__OPTIONMENU_BG_COLOR, - font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), - variable=variable, - command=command, - anchor="w", - ) - option_menu_widget.grid(row=0, column=0, sticky="e") - setattr(self.config_window, optionmenu_attr_name, option_menu_widget) - - # option_menu_widget.configure(command=adjustedCommand) - - # dropdown_menu_widget = CTkScrollableDropdown( - # option_menu_widget, - # justify="left", - # width=self.settings.uism.SB__DROPDOWN_MENU_WIDTH, - # min_show_button_num=6, - # button_pady=0, - # frame_corner_radius=self.settings.uism.SB__DROPDOWN_MENU_FRAME_CORNER_RADIUS, - # max_button_height=self.settings.uism.SB__DROPDOWN_MENU_MAX_BUTTON_HEIGHT, - # max_height=self.settings.uism.SB__DROPDOWN_MENU_FRAME_MAX_HEIGHT, - # font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), - # command=adjustedCommand, - # ) - - # dropdown_menu_widget.bind( - # "", - # lambda e: dropdown_menu_widget._withdraw() if not str(e.widget).startswith(str(dropdown_menu_widget.frame._parent_frame)) else None, - # ) - # dropdown_menu_widget.bind( - # "", - # lambda e: print(e), - # ) - - # setattr(self.config_window, dropdown_menu_attr_name, dropdown_menu_widget) - return option_menu_widget - - - - - def _createPassiveButtonForProgressbarXSlider(self, setting_box_progressbar_x_slider_frame, BUTTON_PADDING, button_command, button_image_file): + def _createPassiveButtonForProgressbarXSlider(self, setting_box_progressbar_x_slider_frame, button_command, button_image_file): button_wrapper = createButtonWithImage( parent_widget=setting_box_progressbar_x_slider_frame, button_fg_color=self.settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR, @@ -470,15 +419,12 @@ class _SettingBoxGenerator(): button_image_size=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE, button_ipadxy=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY, button_command=button_command, - shape="circle", ) - button_wrapper.grid(row=0, column=0, padx=(0,BUTTON_PADDING), sticky="e") - button_wrapper.grid_remove() return button_wrapper - def _createActiveButtonForProgressbarXSlider(self, setting_box_progressbar_x_slider_frame, BUTTON_PADDING, button_command, button_image_file): + def _createActiveButtonForProgressbarXSlider(self, setting_box_progressbar_x_slider_frame, button_command, button_image_file): button_wrapper = createButtonWithImage( parent_widget=setting_box_progressbar_x_slider_frame, button_fg_color=self.settings.ctm.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_COLOR, @@ -488,8 +434,5 @@ class _SettingBoxGenerator(): button_image_size=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE, button_ipadxy=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY, button_command=button_command, - shape="circle", ) - button_wrapper.grid(row=0, column=0, padx=(0,BUTTON_PADDING), sticky="e") - button_wrapper.grid_remove() return button_wrapper \ No newline at end of file diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index 272fbef1..1955fbfa 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -120,7 +120,7 @@ def switchTabsColor(target_widget, tab_buttons, active_bg_color, active_text_col -def createButtonWithImage(parent_widget, button_fg_color, button_enter_color, button_clicked_color, button_image_file, button_image_size, button_ipadxy, button_command, corner_radius: int = 0 ,shape: str = "normal"): +def createButtonWithImage(parent_widget, button_fg_color, button_enter_color, button_clicked_color, button_image_file, button_image_size, button_ipadxy, button_command, corner_radius: int = 0): button_wrapper = CTkFrame(parent_widget, corner_radius=corner_radius, fg_color=button_fg_color, height=0, width=0, cursor="hand2") button_widget = CTkLabel( @@ -139,11 +139,4 @@ def createButtonWithImage(parent_widget, button_fg_color, button_enter_color, bu buttonReleasedFunction=button_command, ) - if shape == "circle": - # To round the corners of the button into a circle - button_wrapper.grid() - button_wrapper.configure(corner_radius=int(getLatestWidth(button_wrapper)/2)) - button_wrapper.grid_remove() - - return button_wrapper From 49ac0a62d49088021be6284b8683ff89b04d3d15 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 29 Sep 2023 02:55:34 +0900 Subject: [PATCH 206/355] =?UTF-8?q?[bugfix]=20Config=20Window=E3=81=AEwith?= =?UTF-8?q?draw()=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=A2=E3=82=A6?= =?UTF-8?q?=E3=83=88=E6=88=BB=E3=81=97=E5=BF=98=E3=82=8C=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/config_window/ConfigWindow.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 45bd1cab..26d94b46 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -8,7 +8,7 @@ from ..ui_utils import getImagePath class ConfigWindow(CTkToplevel): def __init__(self, vrct_gui, settings, view_variable): super().__init__() - # self.withdraw() + self.withdraw() # configure window From 494324fd8f82ecc1e53fce69ef7fbf4af8aeea5e Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 30 Sep 2023 22:41:52 +0900 Subject: [PATCH 207/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model:=20Mic?= =?UTF-8?q?=E3=82=82=E3=81=97=E3=81=8F=E3=81=AFSpeaker=E3=81=8C=E4=B8=80?= =?UTF-8?q?=E3=81=A4=E3=82=82=E6=8E=A5=E7=B6=9A=E3=81=95=E3=82=8C=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=81=AA=E3=81=84=E7=8A=B6=E6=85=8B=E3=81=A7=E3=83=87?= =?UTF-8?q?=E3=83=90=E3=82=A4=E3=82=B9=E6=83=85=E5=A0=B1=E3=82=92=E5=8F=96?= =?UTF-8?q?=E5=BE=97=E3=81=97=E3=81=9F=E5=A0=B4=E5=90=88=E3=81=ABNone?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/transcription/transcription_utils.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/models/transcription/transcription_utils.py b/models/transcription/transcription_utils.py index 4c72a8fa..522cb7a2 100644 --- a/models/transcription/transcription_utils.py +++ b/models/transcription/transcription_utils.py @@ -12,6 +12,8 @@ def getInputDevices(): devices[host["name"]].append(device) else: devices[host["name"]] = [device] + if len(devices) == 0: + devices = {"None": [{"name": "None"}]} return devices def getOutputDevices(): @@ -21,6 +23,8 @@ def getOutputDevices(): for device in p.get_loopback_device_info_generator(): if device["hostApi"] == wasapi_info["index"] and device["isLoopbackDevice"] is True: devices.append(device) + if len(devices) == 0: + devices = [{'name':"None"}] return devices def getDefaultInputDevice(): @@ -33,7 +37,8 @@ def getDefaultInputDevice(): for device_index in range(0, p. get_host_api_info_by_index(host_index)['deviceCount']): device = p.get_device_info_by_host_api_device_index(host_index, device_index) if device["index"] == defaultInputDevice: - return {"host":host, "device": device} + return {"host": host, "device": device} + return {"host": {"name": "None"}, "device": {"name": "None"}} def getDefaultOutputDevice(): with PyAudio() as p: @@ -49,4 +54,5 @@ def getDefaultOutputDevice(): for loopback in p.get_loopback_device_info_generator(): if default_speakers["name"] in loopback["name"]: default_device = loopback - return default_device \ No newline at end of file + return default_device + return {"name":"None"} \ No newline at end of file From 6c04d496c9a7bc5ed6cdaaced786510a23f45baf Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 30 Sep 2023 22:51:23 +0900 Subject: [PATCH 208/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model:=20Mic/Spe?= =?UTF-8?q?aker=E3=81=AE=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9=E3=81=8CNone?= =?UTF-8?q?=E3=81=AE=E5=A0=B4=E5=90=88=E3=81=ABTranscript=E3=82=82?= =?UTF-8?q?=E3=81=97=E3=81=8F=E3=81=AFCheckEnergy=E3=81=AE=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E3=81=97=E3=81=AA=E3=81=84=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E3=83=95=E3=82=A7=E3=83=BC=E3=83=AB=E3=82=BB=E3=83=BC?= =?UTF-8?q?=E3=83=95=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/model.py b/model.py index 1bb20dbd..e398c3ba 100644 --- a/model.py +++ b/model.py @@ -253,6 +253,9 @@ class Model: return False def startMicTranscript(self, fnc): + if config.CHOICE_MIC_HOST == "None" or config.CHOICE_MIC_DEVICE == "None": + return + mic_audio_queue = Queue() device = [device for device in getInputDevices()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] record_timeout = config.INPUT_MIC_RECORD_TIMEOUT @@ -294,6 +297,9 @@ class Model: self.mic_audio_recorder = None def startCheckMicEnergy(self, fnc): + if config.CHOICE_MIC_HOST == "None" or config.CHOICE_MIC_DEVICE == "None": + return + def sendMicEnergy(): if mic_energy_queue.empty() is False: energy = mic_energy_queue.get() @@ -320,6 +326,8 @@ class Model: self.mic_energy_recorder = None def startSpeakerTranscript(self, fnc): + if config.CHOICE_SPEAKER_DEVICE == "None": + return spk_audio_queue = Queue() spk_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] @@ -362,6 +370,9 @@ class Model: self.spk_audio_recorder = None def startCheckSpeakerEnergy(self, fnc): + if config.CHOICE_SPEAKER_DEVICE == "None": + return + def sendSpeakerEnergy(): if speaker_energy_queue.empty() is False: energy = speaker_energy_queue.get() From c287df5c10175a577d54acfe916b08eba1064ecf Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 1 Oct 2023 13:28:37 +0900 Subject: [PATCH 209/355] =?UTF-8?q?[bugfix/Refactor]=20Config=20Window:=20?= =?UTF-8?q?Setting=20Box=E5=91=A8=E3=82=8A=E3=80=82=E8=AC=8E=E3=81=AE1px?= =?UTF-8?q?=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3(=E7=84=A1=E7=90=86?= =?UTF-8?q?=E3=82=84=E3=82=8A)=E3=81=A8=E3=80=81=E8=A6=81=E7=B4=A0?= =?UTF-8?q?=E3=81=AE=E5=B9=85=E3=81=8C=E5=90=88=E3=81=A3=E3=81=A6=E3=81=8A?= =?UTF-8?q?=E3=82=89=E3=81=9A=E3=80=81=E6=96=87=E5=AD=97=E6=95=B0=E3=81=AA?= =?UTF-8?q?=E3=81=A9=E3=81=AE=E5=BD=B1=E9=9F=BF=E3=81=AB=E3=82=88=E3=82=8A?= =?UTF-8?q?=E5=B9=85=E3=81=8C=E5=BA=83=E3=81=8F=E3=81=AA=E3=82=8A=E3=80=81?= =?UTF-8?q?=E6=84=8F=E5=9B=B3=E3=81=97=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84?= =?UTF-8?q?=E7=B7=9A=E3=81=8C=E8=A6=8B=E3=81=88=E3=81=A6=E3=81=84=E3=81=9F?= =?UTF-8?q?=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82=20(row=E3=82=84co?= =?UTF-8?q?lumn,=20sticky=E6=8C=87=E5=AE=9A=E3=80=81=E3=82=BB=E3=82=AF?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E3=82=BF=E3=82=A4=E3=83=88=E3=83=AB?= =?UTF-8?q?=E3=81=AE=E6=8C=87=E5=AE=9A=E6=96=B9=E6=B3=95=E3=81=AE=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=AA=E3=81=A9=E3=80=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_createSettingBoxContainer.py | 59 +++++++++---------- .../_SettingBoxGenerator.py | 58 +++++++++--------- 2 files changed, 59 insertions(+), 58 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py index 6467786a..66b089f0 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py @@ -5,56 +5,55 @@ def _createSettingBoxContainer(config_window, settings, view_variable, setting_b def createSectionTitle(container_widget, var_section_title): - setting_box_wrapper_section_title_frame = CTkFrame(container_widget, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) setting_box_wrapper_section_title = CTkLabel( - setting_box_wrapper_section_title_frame, + container_widget, textvariable=var_section_title, anchor="w", height=0, font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SB__SECTION_TITLE_FONT_SIZE, weight="normal"), text_color=settings.ctm.LABELS_TEXT_COLOR ) - setting_box_wrapper_section_title.grid(row=0, column=0, padx=0, pady=settings.uism.SB__SECTION_TITLE_BOTTOM_PADY) + setting_box_wrapper_section_title.place(relx=0, rely=0) + # setting_box_wrapper_section_title.grid(row=0, column=0, padx=0, pady=settings.uism.SB__SECTION_TITLE_BOTTOM_PADY) - return setting_box_wrapper_section_title_frame + return container_widget # Setting box container setting_box_container_widget = CTkFrame(config_window.main_setting_box_bg_wrapper, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) setattr(config_window, setting_box_container_settings["setting_box_container_attr_name"], setting_box_container_widget) + setting_box_container_widget.grid(row=0, pady=settings.uism.SB__BOTTOM_MARGIN) + setting_box_container_widget.grid_remove() - - setting_boxes_length = len(setting_box_container_settings["setting_boxes"]) - setting_box_row = 0 + # setting_boxes_length = len(setting_box_container_settings["setting_boxes"]) + setting_box_row=0 for i, setting_box_setting in enumerate(setting_box_container_settings["setting_boxes"]): - SB__TOP_PADY = 0 - SB__BOTTOM_PADY = settings.uism.SB__BOTTOM_PADY - - setting_box_and_section_title_wrapper = CTkFrame(setting_box_container_widget, fg_color=settings.ctm.SB__WRAPPER_BG_COLOR, corner_radius=0, width=0, height=0) - - if setting_box_setting["var_section_title"] is not None: - setting_box_wrapper_section_title_frame= createSectionTitle( - container_widget=setting_box_and_section_title_wrapper, - var_section_title=setting_box_setting["var_section_title"], - ) - setting_box_wrapper_section_title_frame.grid(row=0, column=0, sticky="ew", padx=0, pady=0) - if i == 0: SB__TOP_PADY = settings.uism.SB__TOP_PADY_IF_WITH_SECTION_TITLE - - # if the first one of setting boxes, adjust top pady - if i == 0: SB__TOP_PADY = settings.uism.SB__TOP_PADY_IF_WITHOUT_SECTION_TITLE - - # if the last one of setting boxes, remove bottom pady - if i+1 == setting_boxes_length: SB__BOTTOM_PADY = 0 - - # setting_box_wrapper = CTkFrame(setting_box_and_section_title_wrapper, fg_color="red", corner_radius=0, width=0, height=0) - setting_box_wrapper = CTkFrame(setting_box_and_section_title_wrapper, fg_color=settings.ctm.SB__WRAPPER_BG_COLOR, corner_radius=0, width=0, height=0) - setting_box_wrapper.grid(row=1, column=0) + # Top-Padding that can be container the section title + setting_box_top_padding = CTkFrame(setting_box_container_widget, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=60) + setting_box_top_padding.grid(row=setting_box_row, column=0, sticky="ew", padx=0, pady=0) + setting_box_top_padding.grid_columnconfigure(0, weight=1) + setting_box_row+=1 + + if setting_box_setting["var_section_title"] is not None: + setting_box_wrapper_section_title = CTkLabel( + setting_box_top_padding, + textvariable=setting_box_setting["var_section_title"], + anchor="w", + height=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SB__SECTION_TITLE_FONT_SIZE, weight="normal"), + text_color=settings.ctm.LABELS_TEXT_COLOR + ) + setting_box_wrapper_section_title.place(relx=0, rely=0.4, anchor="nw") + + + setting_box_wrapper = CTkFrame(setting_box_container_widget, fg_color=settings.ctm.SB__WRAPPER_BG_COLOR, corner_radius=0, width=0, height=0) + setting_box_wrapper.grid(row=setting_box_row, column=0, sticky="ew") + setting_box_wrapper.grid_columnconfigure(0, weight=1) setting_box_row+=1 - setting_box_and_section_title_wrapper.grid(row=setting_box_row, column=0, sticky="ew", padx=0, pady=(SB__TOP_PADY, SB__BOTTOM_PADY)) if setting_box_setting["setting_box"] is not None: setting_box_setting["setting_box"]( diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 0b35cbe8..8bbf6659 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -1,8 +1,11 @@ +from functools import partial +from typing import Union + from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar, END as CTK_END from vrct_gui.ui_utils import createButtonWithImage, getLatestWidth -from typing import Union +SETTING_BOX_COLUMN = 1 class _SettingBoxGenerator(): def __init__(self, parent_widget, config_window, settings, view_variable): @@ -12,31 +15,31 @@ class _SettingBoxGenerator(): self.settings = settings - def _createSettingBoxFrame(self, setting_box_item_frame, for_var_label_text, for_var_desc_text): + def _createSettingBoxFrame(self, for_var_label_text, for_var_desc_text): setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) - # setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color="gray", width=0, height=0) # "pady=(0,1)" is for bottom padding. It can be removed(override) when you do like "self.attr_name.grid(row=row, pady=0)" # setting_box_frame.grid(column=0, padx=0, pady=0, sticky="ew") setting_box_frame.grid(column=0, padx=0, pady=(0,1), sticky="ew") + setting_box_frame.grid_columnconfigure(0, weight=1) # setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color="gray", width=0, height=0) - setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=self.settings.uism.SB__MAIN_WIDTH, height=0) + setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) setting_box_frame_wrapper.grid(row=0, column=0, padx=self.settings.uism.SB__IPADX, pady=self.settings.uism.SB__IPADY, sticky="ew") - setting_box_frame_wrapper.grid_columnconfigure((0,1), weight=1, minsize=int(self.settings.uism.SB__MAIN_WIDTH / 2), uniform="setting_box") + setting_box_frame_wrapper.grid_columnconfigure(0, weight=0, minsize=int(self.settings.uism.SB__MAIN_WIDTH / 2)) + setting_box_frame_wrapper.grid_columnconfigure(2, weight=1, minsize=int(self.settings.uism.SB__MAIN_WIDTH / 2)) - # setting_box_frame_wrapper.grid(column=0, padx=0, pady=0) - setting_box_frame_wrapper.grid(row=0, column=0, padx=self.settings.uism.SB__IPADX, pady=self.settings.uism.SB__IPADY, sticky="nsew") + setting_box_frame_wrapper_fix_border = CTkFrame(setting_box_frame, corner_radius=0, width=0, height=0) + setting_box_frame_wrapper_fix_border.grid(row=1, column=0, sticky="ew") - setting_box_frame_wrapper_border = CTkFrame(setting_box_frame, corner_radius=0, fg_color="red", width=0, height=0) - setting_box_frame_wrapper_border.grid(row=1, column=0, sticky="ew") + setting_box_frame_wrapper_fix_border2 = CTkFrame(setting_box_frame, corner_radius=0, width=0, height=0) + setting_box_frame_wrapper_fix_border2.grid(row=0, column=1, sticky="ns") self._setSettingBoxLabels(setting_box_frame_wrapper, for_var_label_text, for_var_desc_text) - # setting_box_item_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color="black") setting_box_item_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) - setting_box_item_frame.grid(row=0, column=1, padx=0, sticky="nsew") + setting_box_item_frame.grid(row=0, column=2, padx=0, sticky="nsew") setting_box_item_frame.rowconfigure((0,2), weight=1) setting_box_item_frame.grid_columnconfigure(0, weight=1) @@ -44,7 +47,6 @@ class _SettingBoxGenerator(): def _setSettingBoxLabels(self, setting_box_frame_wrapper, for_var_label_text, for_var_desc_text=None): - # setting_box_labels_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, fg_color="black", width=0, height=0) setting_box_labels_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) setting_box_labels_frame.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") @@ -74,7 +76,7 @@ class _SettingBoxGenerator(): def createSettingBoxDropdownMenu(self, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, variable=None, dropdown_menu_values=None): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(self, for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) option_menu_widget = CTkOptionMenu( setting_box_item_frame, @@ -89,7 +91,7 @@ class _SettingBoxGenerator(): command=command, anchor="w", ) - option_menu_widget.grid(row=1, column=1, sticky="e") + option_menu_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") setattr(self.config_window, optionmenu_attr_name, option_menu_widget) return setting_box_frame @@ -98,7 +100,7 @@ class _SettingBoxGenerator(): def createSettingBoxSwitch(self, for_var_label_text, for_var_desc_text, switch_attr_name, is_checked, command): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(self, for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) switch_widget = CTkSwitch( setting_box_item_frame, @@ -120,14 +122,14 @@ class _SettingBoxGenerator(): switch_widget.select() if is_checked else switch_widget.deselect() - switch_widget.grid(row=1, column=1) + switch_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") return setting_box_frame def createSettingBoxCheckbox(self, for_var_label_text, for_var_desc_text, checkbox_attr_name, variable, command): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(self, for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) checkbox_widget = CTkCheckBox( setting_box_item_frame, @@ -153,7 +155,7 @@ class _SettingBoxGenerator(): # checkbox_widget.select() if is_checked else checkbox_widget.deselect() - checkbox_widget.grid(row=1, column=1) + checkbox_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") return setting_box_frame @@ -163,7 +165,7 @@ class _SettingBoxGenerator(): def createSettingBoxSlider(self, for_var_label_text, for_var_desc_text, slider_attr_name, slider_range, command, variable, slider_number_of_steps: Union[int, None] = None): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(self, for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) slider_widget = CTkSlider( setting_box_item_frame, @@ -177,7 +179,7 @@ class _SettingBoxGenerator(): ) setattr(self.config_window, slider_attr_name, slider_widget) - slider_widget.grid(row=1, column=1) + slider_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") return setting_box_frame @@ -200,7 +202,7 @@ class _SettingBoxGenerator(): ): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(self, for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) ENTRY_WIDTH = self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH BAR_WIDTH = self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BAR_WIDTH @@ -222,7 +224,7 @@ class _SettingBoxGenerator(): ) entry_widget.bind("", adjusted_command__for_entry_bind__Any_KeyRelease) - entry_widget.grid(row=1, column=1, padx=0, pady=0, sticky="e") + entry_widget.grid(row=1, column=SETTING_BOX_COLUMN, padx=0, pady=0, sticky="e") setattr(self.config_window, entry_attr_name, entry_widget) @@ -248,7 +250,7 @@ class _SettingBoxGenerator(): progress_color=self.settings.ctm.SB__BG_COLOR, border_color=self.settings.ctm.SB__BG_COLOR, ) - slider_widget.grid(row=1, column=1, padx=(0, BAR_PADDING), sticky="e") + slider_widget.grid(row=1, column=SETTING_BOX_COLUMN, padx=(0, BAR_PADDING), sticky="e") setattr(self.config_window, slider_attr_name, slider_widget) @@ -261,7 +263,7 @@ class _SettingBoxGenerator(): corner_radius=0, ) setattr(self.config_window, progressbar_attr_name, progressbar_widget) - progressbar_widget.grid(row=1, column=1, padx=(0, BAR_PADDING), sticky="e") + progressbar_widget.grid(row=1, column=SETTING_BOX_COLUMN, padx=(0, BAR_PADDING), sticky="e") progressbar_widget.set(0) @@ -273,10 +275,10 @@ class _SettingBoxGenerator(): active_button_wrapper = self._createActiveButtonForProgressbarXSlider(setting_box_item_frame, active_button_command, button_image_file) setattr(self.config_window, active_button_attr_name, active_button_wrapper) - passive_button_wrapper.grid(row=1, column=1, padx=(0,BUTTON_PADDING), sticky="e") + passive_button_wrapper.grid(row=1, column=SETTING_BOX_COLUMN, padx=(0,BUTTON_PADDING), sticky="e") passive_button_wrapper.configure(corner_radius=int(getLatestWidth(passive_button_wrapper)/2)) - active_button_wrapper.grid(row=1, column=1, padx=(0,BUTTON_PADDING), sticky="e") + active_button_wrapper.grid(row=1, column=SETTING_BOX_COLUMN, padx=(0,BUTTON_PADDING), sticky="e") active_button_wrapper.configure(corner_radius=int(getLatestWidth(passive_button_wrapper)/2)) passive_button_wrapper.grid_remove() @@ -289,7 +291,7 @@ class _SettingBoxGenerator(): def createSettingBoxEntry(self, for_var_label_text, for_var_desc_text, entry_attr_name, entry_width, entry_bind__Any_KeyRelease, entry_textvariable): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(self, for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) def adjusted_command__for_entry_bind__Any_KeyRelease(e): entry_bind__Any_KeyRelease(e.widget.get()) @@ -305,7 +307,7 @@ class _SettingBoxGenerator(): setattr(self.config_window, entry_attr_name, entry_widget) - entry_widget.grid(row=1, column=1) + entry_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") return setting_box_frame From 913b18c5cc1f656712fa7d093c4484aee4ebf4ca Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 1 Oct 2023 13:40:54 +0900 Subject: [PATCH 210/355] [Chore] remove the code that is no longer in use --- .../_createSettingBoxContainer.py | 2 -- .../setting_box_containers/_SettingBoxGenerator.py | 6 +----- 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py index 66b089f0..d54cea60 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py @@ -15,7 +15,6 @@ def _createSettingBoxContainer(config_window, settings, view_variable, setting_b text_color=settings.ctm.LABELS_TEXT_COLOR ) setting_box_wrapper_section_title.place(relx=0, rely=0) - # setting_box_wrapper_section_title.grid(row=0, column=0, padx=0, pady=settings.uism.SB__SECTION_TITLE_BOTTOM_PADY) return container_widget @@ -28,7 +27,6 @@ def _createSettingBoxContainer(config_window, settings, view_variable, setting_b - # setting_boxes_length = len(setting_box_container_settings["setting_boxes"]) setting_box_row=0 for i, setting_box_setting in enumerate(setting_box_container_settings["setting_boxes"]): # Top-Padding that can be container the section title diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 8bbf6659..766e0ccd 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -1,7 +1,7 @@ from functools import partial from typing import Union -from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar, END as CTK_END +from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar from vrct_gui.ui_utils import createButtonWithImage, getLatestWidth @@ -19,12 +19,10 @@ class _SettingBoxGenerator(): setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) # "pady=(0,1)" is for bottom padding. It can be removed(override) when you do like "self.attr_name.grid(row=row, pady=0)" - # setting_box_frame.grid(column=0, padx=0, pady=0, sticky="ew") setting_box_frame.grid(column=0, padx=0, pady=(0,1), sticky="ew") setting_box_frame.grid_columnconfigure(0, weight=1) - # setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color="gray", width=0, height=0) setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) setting_box_frame_wrapper.grid(row=0, column=0, padx=self.settings.uism.SB__IPADX, pady=self.settings.uism.SB__IPADY, sticky="ew") setting_box_frame_wrapper.grid_columnconfigure(0, weight=0, minsize=int(self.settings.uism.SB__MAIN_WIDTH / 2)) @@ -153,8 +151,6 @@ class _SettingBoxGenerator(): ) setattr(self.config_window, checkbox_attr_name, checkbox_widget) - # checkbox_widget.select() if is_checked else checkbox_widget.deselect() - checkbox_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") return setting_box_frame From 584eb7e33a3c611d8eeda4042cb58b5569c9a961 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 2 Oct 2023 14:48:42 +0900 Subject: [PATCH 211/355] [Update] add Modal Window: When the configuration window is opened, cover the main window with a modal window containing a message. --- locales/en.yml | 3 ++ locales/ja.yml | 4 ++ view.py | 9 +++++ vrct_gui/_CreateModalWindow.py | 49 +++++++++++++++++++++++ vrct_gui/ui_managers/ColorThemeManager.py | 4 ++ vrct_gui/ui_managers/UiScalingManager.py | 3 +- vrct_gui/vrct_gui.py | 37 ++++++++++++++++- 7 files changed, 107 insertions(+), 2 deletions(-) create mode 100644 vrct_gui/_CreateModalWindow.py diff --git a/locales/en.yml b/locales/en.yml index 2c6ed497..abfe232f 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -16,6 +16,9 @@ main_window: update_available: New version is here! + modal_message: + opened_config_window: The functionality is temporarily disabled until the settings window is closed. + selectable_language_window: title_your_language: Select Your Language title_target_language: Select Target Language diff --git a/locales/ja.yml b/locales/ja.yml index 040bb36c..4b5f373a 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -16,6 +16,10 @@ main_window: update_available: 新しいバージョンが出ました! + modal_message: + opened_config_window: 設定画面が閉じられるまで、一時的に機能を停止しています。 + + selectable_language_window: title_your_language: あなたの言語 title_target_language: 相手の言語 diff --git a/view.py b/view.py index e1f924ad..a49da319 100644 --- a/view.py +++ b/view.py @@ -53,6 +53,12 @@ class View(): **common_args ) + self.settings.modal_window = SimpleNamespace( + ctm=all_ctm.modal_window, + uism=all_uism.modal_window, + **common_args + ) + self.view_variable = SimpleNamespace( # Open Config Window CALLBACK_OPEN_CONFIG_WINDOW=None, @@ -112,6 +118,9 @@ class View(): VAR_UPDATE_AVAILABLE=StringVar(value=i18n.t("main_window.update_available")), + # Modal Window For Main Window + VAR_LABEL_MODAL_MESSAGE_FOR__MAIN_WINDOW=StringVar(value=i18n.t("main_window.modal_message.opened_config_window")), + # Selectable Language Window VAR_TITLE_LABEL_SELECTABLE_LANGUAGE=StringVar(value=""), VAR_GO_BACK_LABEL_SELECTABLE_LANGUAGE=StringVar(value=i18n.t("selectable_language_window.go_back_button")), diff --git a/vrct_gui/_CreateModalWindow.py b/vrct_gui/_CreateModalWindow.py new file mode 100644 index 00000000..b54bad76 --- /dev/null +++ b/vrct_gui/_CreateModalWindow.py @@ -0,0 +1,49 @@ +from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont + +class _CreateModalWindow(CTkToplevel): + def __init__(self, attach_window, settings, view_variable): + super().__init__() + self.withdraw() + + + self.title("") + self.overrideredirect(True) + + self.wm_attributes("-alpha", 0.5) + self.wm_attributes("-toolwindow", True) + + self.attach_window = attach_window + + + self.configure(fg_color="black") + self.protocol("WM_DELETE_WINDOW", lambda e: self.withdraw()) + + self.settings = settings + self._view_variable = view_variable + + + self.attach_window.update_idletasks() + self.x_pos = self.attach_window.winfo_rootx() + self.y_pos = self.attach_window.winfo_rooty() + self.width_new = self.attach_window.winfo_width() + self.height_new = self.attach_window.winfo_height() + + + self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, self.x_pos, self.y_pos)) + + self.rowconfigure(0,weight=1) + self.columnconfigure(0,weight=1) + self.modal_container = CTkFrame(self, corner_radius=0, fg_color="black", width=0, height=0) + self.modal_container.grid(row=0, column=0, sticky="nsew") + + + self.modal_container_label_wrapper = CTkLabel( + self.modal_container, + textvariable=self._view_variable.VAR_LABEL_MODAL_MESSAGE_FOR__MAIN_WINDOW, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.TEXT_FONT_SIZE, weight="normal"), + anchor="w", + text_color=self.settings.ctm.TEXT_COLOR, + ) + self.modal_container_label_wrapper.place(relx=0.5, rely=0.5, anchor="center") \ No newline at end of file diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 2b5833ef..c712b2d4 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -5,6 +5,7 @@ class ColorThemeManager(): self.main = SimpleNamespace() self.config_window = SimpleNamespace() self.selectable_language_window = SimpleNamespace() + self.modal_window = SimpleNamespace() # old one. But leave it here for now. # self.PRIMARY_100_COLOR = "#c4eac1" @@ -207,6 +208,9 @@ class ColorThemeManager(): self.selectable_language_window.LANGUAGE_BUTTON_BG_CLICKED_COLOR = self.DARK_888_COLOR + # Modal Window (Main Window) + self.modal_window.TEXT_COLOR = self.LIGHT_100_COLOR + # Common self.config_window.BASIC_TEXT_COLOR = self.main.BASIC_TEXT_COLOR diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index ebac9bec..902951b5 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -6,6 +6,7 @@ class UiScalingManager(): self.SCALING_FLOAT = max(scaling_float, 0.4) self.main = SimpleNamespace() self.config_window = SimpleNamespace() + self.modal_window = SimpleNamespace() self._calculatedUiSizes() @@ -96,7 +97,7 @@ class UiScalingManager(): self.main.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y = self._calculateUiSize(26) - + self.modal_window.TEXT_FONT_SIZE = self._calculateUiSize(20) # Top bar common self.config_window.TOP_BAR__HEIGHT = self._calculateUiSize(40) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 4913c64b..54705bb9 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -4,6 +4,7 @@ from customtkinter import CTk, CTkImage from ._CreateSelectableLanguagesWindow import _CreateSelectableLanguagesWindow +from ._CreateModalWindow import _CreateModalWindow from ._changeMainWindowWidgetsStatus import _changeMainWindowWidgetsStatus from ._changeConfigWindowWidgetsStatus import _changeConfigWindowWidgetsStatus from ._printToTextbox import _printToTextbox @@ -17,6 +18,8 @@ from utils import callFunctionIfCallable class VRCT_GUI(CTk): def __init__(self): super().__init__() + self.adjusted_event=None + def createGUI(self, settings, view_variable): self.settings = settings @@ -41,6 +44,14 @@ class VRCT_GUI(CTk): view_variable=self._view_variable ) + self.modal_window = _CreateModalWindow( + attach_window=self, + settings=self.settings.modal_window, + view_variable=self._view_variable + ) + + + def startMainLoop(self): self.mainloop() @@ -52,9 +63,13 @@ class VRCT_GUI(CTk): def openConfigWindow(self, e): callFunctionIfCallable(self._view_variable.CALLBACK_OPEN_CONFIG_WINDOW) + + self.adjustToMainWindowGeometry() + self.modal_window.deiconify() + self.bind("", self.adjustToMainWindowGeometry) + self.config_window.deiconify() self.config_window.focus_set() - self.config_window.focus() self.config_window.grab_set() def closeConfigWindow(self): @@ -62,6 +77,9 @@ class VRCT_GUI(CTk): self.config_window.withdraw() self.config_window.grab_release() + self.modal_window.withdraw() + self.unbind("") + self.adjusted_event=None @@ -160,4 +178,21 @@ class VRCT_GUI(CTk): self.minimize_sidebar_button_container__for_closing.grid() + def adjustToMainWindowGeometry(self, e=None): + self.update_idletasks() + x_pos = self.winfo_rootx() + y_pos = self.winfo_rooty() + width_new = self.winfo_width() + height_new = self.winfo_height() + self.modal_window.geometry("{}x{}+{}+{}".format(width_new, height_new, x_pos, y_pos)) + + self.modal_window.lift() + if self.adjusted_event == str(e): + self.after(150, lambda: self.config_window.lift()) + elif self.adjusted_event is None: + self.after(150, lambda: self.config_window.lift()) + + if e is not None: + self.adjusted_event=str(e) + vrct_gui = VRCT_GUI() \ No newline at end of file From a936740aa4e634043ea2d2fe2ff4a3f4c215e6d4 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 3 Oct 2023 02:18:07 +0900 Subject: [PATCH 212/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model:=20AutoUpd?= =?UTF-8?q?ate=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.bat | 2 +- model.py | 29 ++++++++++++++++++++++++++++- update.bat | 6 ++++++ 3 files changed, 35 insertions(+), 2 deletions(-) create mode 100644 update.bat diff --git a/build.bat b/build.bat index e7029fb2..8b50ff9c 100644 --- a/build.bat +++ b/build.bat @@ -1 +1 @@ -pyinstaller --onedir --onefile --windowed --clean --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --add-data "./locales;locales/" --name VRCT --exclude-module numpy --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py \ No newline at end of file +pyinstaller --onedir --onefile --windowed --clean --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --add-data "./locales;locales/" --add-data "./update.bat;./" --name VRCT --exclude-module numpy --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py \ No newline at end of file diff --git a/model.py b/model.py index e398c3ba..9916228e 100644 --- a/model.py +++ b/model.py @@ -1,6 +1,9 @@ import sys +from zipfile import ZipFile +from subprocess import Popen from os import makedirs -from os import path as os_path +from os import path as os_path, rename as os_rename, mkdir as os_mkdir +from shutil import rmtree from datetime import datetime from logging import getLogger, FileHandler, Formatter, INFO from time import sleep @@ -229,6 +232,30 @@ class Model: print("software version", "now:", config.VERSION, "new:", new_version) return update_flag + @staticmethod + def updateSoftware(): + filename = 'download.zip' + program_name = 'VRCT.exe' + temporary_name = '_VRCT.exe' + tmp_directory_name = 'tmp' + batch_name = 'update.bat' + current_directory = os_path.dirname(sys.argv[0]) + program_directory = os_path.dirname(__file__) + + os_mkdir(os_path.join(current_directory, tmp_directory_name)) + res = requests_get(config.GITHUB_URL) + url = res.json()['assets'][0]['browser_download_url'] + res = requests_get(url, stream=True) + with open(os_path.join(current_directory, tmp_directory_name, filename), 'wb') as file: + for chunk in res.iter_content(chunk_size=1024): + file.write(chunk) + with ZipFile(os_path.join(current_directory, tmp_directory_name, filename)) as zf: + zf.extract(program_name, os_path.join(current_directory, tmp_directory_name)) + os_rename(os_path.join(current_directory, tmp_directory_name, program_name), os_path.join(current_directory, temporary_name)) + rmtree(os_path.join(current_directory, tmp_directory_name)) + command = [os_path.join(program_directory, batch_name), program_name, temporary_name] + Popen(command) + @staticmethod def getListInputHost(): return [host for host in getInputDevices().keys()] diff --git a/update.bat b/update.bat new file mode 100644 index 00000000..cbdef3c4 --- /dev/null +++ b/update.bat @@ -0,0 +1,6 @@ +taskkill /im %1 /F +timeout 2 +del /f %1 +timeout 2 +rename %2 %1 +START "" %1 \ No newline at end of file From 53cb8d90886a814b69224871d306401acc4c19a0 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 5 Oct 2023 10:51:15 +0900 Subject: [PATCH 213/355] =?UTF-8?q?[Update]=20Config=20Window:=20=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E6=A9=9F=E8=83=BD(=E3=83=A1=E3=83=83?= =?UTF-8?q?=E3=82=BB=E3=83=BC=E3=82=B8=E5=86=85=E5=AE=B9=E3=81=AF=E4=BB=AE?= =?UTF-8?q?=E7=BD=AE=E3=81=8D):=20=E3=83=90=E3=83=AA=E3=83=87=E3=83=BC?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E3=81=AB=E3=82=88=E3=82=8A=E7=84=A1?= =?UTF-8?q?=E5=8A=B9=E3=81=AA=E5=80=A4=E3=82=92=E5=85=A5=E5=8A=9B=E3=81=97?= =?UTF-8?q?=E3=81=9F=E5=A0=B4=E5=90=88=E3=81=AB=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=80=82=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=A1=E3=83=83?= =?UTF-8?q?=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=AF=E6=96=B0=E3=81=97=E3=81=8F?= =?UTF-8?q?Window=E3=82=92=E4=BD=9C=E3=81=A3=E3=81=A6=E8=A2=AB=E3=81=9B?= =?UTF-8?q?=E3=82=8B=E5=BD=A2=E3=81=AB=E3=81=97=E3=81=A6=E3=81=84=E3=81=BE?= =?UTF-8?q?=E3=81=99=E3=80=82=E4=BB=96=E3=81=AE=E9=83=A8=E5=88=86=E3=82=92?= =?UTF-8?q?=E3=82=AF=E3=83=AA=E3=83=83=E3=82=AF=E3=81=97=E3=81=9F=E3=82=8A?= =?UTF-8?q?=E3=80=81=E3=83=9B=E3=82=A4=E3=83=BC=E3=83=AB=E3=81=AB=E3=82=88?= =?UTF-8?q?=E3=82=8B=E3=82=B9=E3=82=AF=E3=83=AD=E3=83=BC=E3=83=AB=E3=81=AA?= =?UTF-8?q?=E3=81=A9=E3=81=A7=E7=94=BB=E9=9D=A2=E5=A4=96=E3=81=B8=E3=81=84?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E6=99=82=E3=81=AB=E6=B6=88=E3=81=97=E3=81=9F?= =?UTF-8?q?=E3=82=8A=E3=81=AA=E3=81=A9=E3=81=AE=E5=87=A6=E7=90=86=E3=82=82?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Entry Widget系フォーカスアウト機能:そのWidget外をクリックした時にちゃんとフォーカスアウトし、その際にconfigに保存されている有効な値をセット。 (今のところTranscription項目内のEntry Widgetがある項目のみ) [bugfix] Main Window: Modal Windowのevent unbindに、ちゃんとIDを指定してunbindするように。(vrct_gui.py line 90) --- main.py | 118 ++++++++------- view.py | 95 +++++++++++-- vrct_gui/_CreateErrorWindow.py | 134 ++++++++++++++++++ vrct_gui/config_window/ConfigWindow.py | 5 +- .../_SettingBoxGenerator.py | 12 +- .../createSettingBox_Mic.py | 4 + .../createSettingBox_Speaker.py | 4 + vrct_gui/vrct_gui.py | 26 +++- 8 files changed, 326 insertions(+), 72 deletions(-) create mode 100644 vrct_gui/_CreateErrorWindow.py diff --git a/main.py b/main.py index 311827b0..94d943cd 100644 --- a/main.py +++ b/main.py @@ -388,19 +388,15 @@ def callbackSetMicDevice(value): def callbackSetMicEnergyThreshold(value): print("callbackSetMicEnergyThreshold", value) try: - if 0 > int(value) or int(value) > config.MAX_MIC_ENERGY_THRESHOLD: raise ValueError() + value = int(value) + if 0 <= value and value <= config.MAX_MIC_ENERGY_THRESHOLD: + view.clearErrorMessage() + config.INPUT_MIC_ENERGY_THRESHOLD = value + view.setGuiVariable_MicEnergyThreshold(config.INPUT_MIC_ENERGY_THRESHOLD) + else: + raise ValueError() except: - view.setGuiVariable_MicEnergyThreshold( - slider_value=config.INPUT_MIC_ENERGY_THRESHOLD, - entry_value=None, - ) - return - config.INPUT_MIC_ENERGY_THRESHOLD = int(value) - view.setGuiVariable_MicEnergyThreshold( - slider_value=config.INPUT_MIC_ENERGY_THRESHOLD, - entry_value=str(config.INPUT_MIC_ENERGY_THRESHOLD), - ) - + view.showErrorMessage_MicEnergyThreshold() def callbackSetMicDynamicEnergyThreshold(value): print("callbackSetMicDynamicEnergyThreshold", value) @@ -425,32 +421,41 @@ def callbackCheckMicThreshold(is_turned_on): def callbackSetMicRecordTimeout(value): print("callbackSetMicRecordTimeout", value) try: - if int(value) < 0 or int(value) > config.INPUT_MIC_PHRASE_TIMEOUT: raise ValueError() + value = int(value) + if 0 <= value and value <= config.INPUT_MIC_PHRASE_TIMEOUT: + view.clearErrorMessage() + config.INPUT_MIC_RECORD_TIMEOUT = value + view.setGuiVariable_MicRecordTimeout(str(config.INPUT_MIC_RECORD_TIMEOUT)) + else: + raise ValueError() except: - view.setGuiVariable_MicRecordTimeout(delete=True) - return - config.INPUT_MIC_RECORD_TIMEOUT = int(value) - view.setGuiVariable_MicRecordTimeout(str(config.INPUT_MIC_RECORD_TIMEOUT)) + view.showErrorMessage_MicRecordTimeout() def callbackSetMicPhraseTimeout(value): print("callbackSetMicPhraseTimeout", value) try: - if int(value) < 0 or int(value) < config.INPUT_MIC_RECORD_TIMEOUT: raise ValueError() + value = int(value) + if 0 <= value and value >= config.INPUT_MIC_RECORD_TIMEOUT: + view.clearErrorMessage() + config.INPUT_MIC_PHRASE_TIMEOUT = value + view.setGuiVariable_MicPhraseTimeout(str(config.INPUT_MIC_PHRASE_TIMEOUT)) + else: + raise ValueError() except: - view.setGuiVariable_MicPhraseTimeout(delete=True) - return - config.INPUT_MIC_PHRASE_TIMEOUT = int(value) - view.setGuiVariable_MicPhraseTimeout(str(config.INPUT_MIC_PHRASE_TIMEOUT)) + view.showErrorMessage_MicPhraseTimeout() def callbackSetMicMaxPhrases(value): print("callbackSetMicMaxPhrases", value) try: - if int(value) < 0: raise ValueError() + value = int(value) + if 0 <= value: + view.clearErrorMessage() + config.INPUT_MIC_MAX_PHRASES = value + view.setGuiVariable_MicMaxPhrases(str(config.INPUT_MIC_MAX_PHRASES)) + else: + raise ValueError() except: - view.setGuiVariable_MicMaxPhrases(delete=True) - return - config.INPUT_MIC_MAX_PHRASES = int(value) - view.setGuiVariable_MicMaxPhrases(str(config.INPUT_MIC_MAX_PHRASES)) + view.showErrorMessage_MicMaxPhrases() def callbackSetMicWordFilter(value): print("callbackSetMicWordFilter", value) @@ -476,19 +481,15 @@ def callbackSetSpeakerDevice(value): def callbackSetSpeakerEnergyThreshold(value): print("callbackSetSpeakerEnergyThreshold", value) try: - if 0 > int(value) or int(value) > config.MAX_SPEAKER_ENERGY_THRESHOLD: raise ValueError() + value = int(value) + if 0 <= value and value <= config.MAX_SPEAKER_ENERGY_THRESHOLD: + view.clearErrorMessage() + config.INPUT_SPEAKER_ENERGY_THRESHOLD = value + view.setGuiVariable_SpeakerEnergyThreshold(config.INPUT_SPEAKER_ENERGY_THRESHOLD) + else: + raise ValueError() except: - view.setGuiVariable_SpeakerEnergyThreshold( - slider_value=config.INPUT_SPEAKER_ENERGY_THRESHOLD, - entry_value=None, - ) - return - config.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) - view.setGuiVariable_SpeakerEnergyThreshold( - slider_value=config.INPUT_SPEAKER_ENERGY_THRESHOLD, - entry_value=str(config.INPUT_SPEAKER_ENERGY_THRESHOLD), - ) - + view.showErrorMessage_SpeakerEnergyThreshold() def callbackSetSpeakerDynamicEnergyThreshold(value): print("callbackSetSpeakerDynamicEnergyThreshold", value) @@ -514,32 +515,41 @@ def callbackCheckSpeakerThreshold(is_turned_on): def callbackSetSpeakerRecordTimeout(value): print("callbackSetSpeakerRecordTimeout", value) try: - if int(value) < 0 or int(value) > config.INPUT_SPEAKER_PHRASE_TIMEOUT: raise ValueError() + value = int(value) + if 0 <= value and value <= config.INPUT_SPEAKER_PHRASE_TIMEOUT: + view.clearErrorMessage() + config.INPUT_SPEAKER_RECORD_TIMEOUT = value + view.setGuiVariable_SpeakerRecordTimeout(str(config.INPUT_SPEAKER_RECORD_TIMEOUT)) + else: + raise ValueError() except: - view.setGuiVariable_SpeakerRecordTimeout(delete=True) - return - config.INPUT_SPEAKER_RECORD_TIMEOUT = int(value) - view.setGuiVariable_SpeakerRecordTimeout(str(config.INPUT_SPEAKER_RECORD_TIMEOUT)) + view.showErrorMessage_SpeakerRecordTimeout() def callbackSetSpeakerPhraseTimeout(value): print("callbackSetSpeakerPhraseTimeout", value) try: - if int(value) < 0 or int(value) < config.INPUT_SPEAKER_RECORD_TIMEOUT: raise ValueError() + value = int(value) + if 0 <= value and value >= config.INPUT_SPEAKER_RECORD_TIMEOUT: + view.clearErrorMessage() + config.INPUT_SPEAKER_PHRASE_TIMEOUT = value + view.setGuiVariable_SpeakerPhraseTimeout(str(config.INPUT_SPEAKER_PHRASE_TIMEOUT)) + else: + raise ValueError() except: - view.setGuiVariable_SpeakerPhraseTimeout(delete=True) - return - config.INPUT_SPEAKER_PHRASE_TIMEOUT = int(value) - view.setGuiVariable_SpeakerPhraseTimeout(str(config.INPUT_SPEAKER_PHRASE_TIMEOUT)) + view.showErrorMessage_SpeakerPhraseTimeout() def callbackSetSpeakerMaxPhrases(value): print("callbackSetSpeakerMaxPhrases", value) try: - if int(value) < 0: raise ValueError() + value = int(value) + if 0 <= value: + view.clearErrorMessage() + config.INPUT_SPEAKER_MAX_PHRASES = value + view.setGuiVariable_SpeakerMaxPhrases(str(config.INPUT_SPEAKER_MAX_PHRASES)) + else: + raise ValueError() except: - view.setGuiVariable_SpeakerMaxPhrases(delete=True) - return - config.INPUT_SPEAKER_MAX_PHRASES = int(value) - view.setGuiVariable_SpeakerMaxPhrases(str(config.INPUT_SPEAKER_MAX_PHRASES)) + view.showErrorMessage_SpeakerMaxPhrases() # Others Tab diff --git a/view.py b/view.py index a49da319..88661660 100644 --- a/view.py +++ b/view.py @@ -130,6 +130,8 @@ class View(): # Config Window ACTIVE_SETTING_BOX_TAB_ATTR_NAME="side_menu_tab_appearance", CALLBACK_SELECTED_SETTING_BOX_TAB=None, + VAR_ERROR_MESSAGE=StringVar(value=""), + # Side Menu Labels VAR_SIDE_MENU_LABEL_APPEARANCE=StringVar(value=i18n.t("config_window.side_menu_labels.appearance")), @@ -200,6 +202,7 @@ class View(): CALLBACK_CHECK_MIC_THRESHOLD=None, VAR_MIC_ENERGY_THRESHOLD__SLIDER=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), VAR_MIC_ENERGY_THRESHOLD__ENTRY=StringVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), + CALLBACK_FOCUS_OUT_MIC_ENERGY_THRESHOLD=self.setLatestConfigVariable_MicEnergyThreshold, VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_dynamic_energy_threshold.label")), VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_dynamic_energy_threshold.desc")), @@ -210,16 +213,19 @@ class View(): VAR_DESC_MIC_RECORD_TIMEOUT=None, CALLBACK_SET_MIC_RECORD_TIMEOUT=None, VAR_MIC_RECORD_TIMEOUT=StringVar(value=config.INPUT_MIC_RECORD_TIMEOUT), + CALLBACK_FOCUS_OUT_MIC_RECORD_TIMEOUT=self.setLatestConfigVariable_MicRecordTimeout, VAR_LABEL_MIC_PHRASE_TIMEOUT=StringVar(value=i18n.t("config_window.mic_phrase_timeout.label")), VAR_DESC_MIC_PHRASE_TIMEOUT=None, CALLBACK_SET_MIC_PHRASE_TIMEOUT=None, VAR_MIC_PHRASE_TIMEOUT=StringVar(value=config.INPUT_MIC_PHRASE_TIMEOUT), + CALLBACK_FOCUS_OUT_MIC_PHRASE_TIMEOUT=self.setLatestConfigVariable_MicPhraseTimeout, VAR_LABEL_MIC_MAX_PHRASES=StringVar(value=i18n.t("config_window.mic_max_phrase.label")), VAR_DESC_MIC_MAX_PHRASES=StringVar(value=i18n.t("config_window.mic_max_phrase.desc")), CALLBACK_SET_MIC_MAX_PHRASES=None, VAR_MIC_MAX_PHRASES=StringVar(value=config.INPUT_MIC_MAX_PHRASES), + CALLBACK_FOCUS_OUT_MIC_MAX_PHRASES=self.setLatestConfigVariable_MicMaxPhrases, VAR_LABEL_MIC_WORD_FILTER=StringVar(value=i18n.t("config_window.mic_word_filter.label")), VAR_DESC_MIC_WORD_FILTER=StringVar(value=i18n.t("config_window.mic_word_filter.desc")), @@ -240,6 +246,7 @@ class View(): CALLBACK_CHECK_SPEAKER_THRESHOLD=None, VAR_SPEAKER_ENERGY_THRESHOLD__SLIDER=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), VAR_SPEAKER_ENERGY_THRESHOLD__ENTRY=StringVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), + CALLBACK_FOCUS_OUT_SPEAKER_ENERGY_THRESHOLD=self.setLatestConfigVariable_SpeakerEnergyThreshold, VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_dynamic_energy_threshold.label")), VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_dynamic_energy_threshold.desc")), @@ -250,16 +257,19 @@ class View(): VAR_DESC_SPEAKER_RECORD_TIMEOUT=None, CALLBACK_SET_SPEAKER_RECORD_TIMEOUT=None, VAR_SPEAKER_RECORD_TIMEOUT=StringVar(value=config.INPUT_SPEAKER_RECORD_TIMEOUT), + CALLBACK_FOCUS_OUT_SPEAKER_RECORD_TIMEOUT=self.setLatestConfigVariable_SpeakerRecordTimeout, VAR_LABEL_SPEAKER_PHRASE_TIMEOUT=StringVar(value=i18n.t("config_window.speaker_phrase_timeout.label")), VAR_DESC_SPEAKER_PHRASE_TIMEOUT=None, CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT=None, VAR_SPEAKER_PHRASE_TIMEOUT=StringVar(value=config.INPUT_SPEAKER_PHRASE_TIMEOUT), + CALLBACK_FOCUS_OUT_SPEAKER_PHRASE_TIMEOUT=self.setLatestConfigVariable_SpeakerPhraseTimeout, VAR_LABEL_SPEAKER_MAX_PHRASES=StringVar(value=i18n.t("config_window.speaker_max_phrase.label")), VAR_DESC_SPEAKER_MAX_PHRASES=StringVar(value=i18n.t("config_window.speaker_max_phrase.desc")), CALLBACK_SET_SPEAKER_MAX_PHRASES=None, VAR_SPEAKER_MAX_PHRASES=StringVar(value=config.INPUT_SPEAKER_MAX_PHRASES), + CALLBACK_FOCUS_OUT_SPEAKER_MAX_PHRASES=self.setLatestConfigVariable_SpeakerMaxPhrases, # Others Tab @@ -718,19 +728,22 @@ class View(): - def setGuiVariable_MicEnergyThreshold(self, slider_value:int, entry_value:Union[None, str]=None): - self.view_variable.VAR_MIC_ENERGY_THRESHOLD__SLIDER.set(slider_value) - if entry_value is None: - self._clearEntryBox(vrct_gui.config_window.sb__progressbar_x_slider__entry_mic_energy_threshold) - else: - self.view_variable.VAR_MIC_ENERGY_THRESHOLD__ENTRY.set(entry_value) + def setGuiVariable_MicEnergyThreshold(self, value:int): + self.view_variable.VAR_MIC_ENERGY_THRESHOLD__SLIDER.set(value) + self.view_variable.VAR_MIC_ENERGY_THRESHOLD__ENTRY.set(str(value)) - def setGuiVariable_SpeakerEnergyThreshold(self, slider_value:int, entry_value:Union[None, str]=None): - self.view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__SLIDER.set(slider_value) - if entry_value is None: - self._clearEntryBox(vrct_gui.config_window.sb__progressbar_x_slider__entry_speaker_energy_threshold) - else: - self.view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__ENTRY.set(entry_value) + def setLatestConfigVariable_MicEnergyThreshold(self, _e=None): + self.setGuiVariable_MicEnergyThreshold(config.INPUT_MIC_ENERGY_THRESHOLD) + self.clearErrorMessage() + + + def setGuiVariable_SpeakerEnergyThreshold(self, value:int): + self.view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__SLIDER.set(value) + self.view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__ENTRY.set(str(value)) + + def setLatestConfigVariable_SpeakerEnergyThreshold(self, _e=None): + self.setGuiVariable_SpeakerEnergyThreshold(config.INPUT_SPEAKER_ENERGY_THRESHOLD) + self.clearErrorMessage() @@ -738,35 +751,93 @@ class View(): if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_mic_record_timeout) self.view_variable.VAR_MIC_RECORD_TIMEOUT.set(value) + def setLatestConfigVariable_MicRecordTimeout(self, _e=None): + self.setGuiVariable_MicRecordTimeout(config.INPUT_MIC_RECORD_TIMEOUT) + self.clearErrorMessage() + + def setGuiVariable_MicPhraseTimeout(self, value:str="", delete=False): if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_mic_phrase_timeout) self.view_variable.VAR_MIC_PHRASE_TIMEOUT.set(value) + def setLatestConfigVariable_MicPhraseTimeout(self, _e=None): + self.setGuiVariable_MicPhraseTimeout(config.INPUT_MIC_PHRASE_TIMEOUT) + self.clearErrorMessage() + + def setGuiVariable_MicMaxPhrases(self, value:str="", delete=False): if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_mic_max_phrases) self.view_variable.VAR_MIC_MAX_PHRASES.set(value) + def setLatestConfigVariable_MicMaxPhrases(self, _e=None): + self.setGuiVariable_MicMaxPhrases(config.INPUT_MIC_MAX_PHRASES) + self.clearErrorMessage() + def setGuiVariable_SpeakerRecordTimeout(self, value:str="", delete=False): if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_speaker_record_timeout) self.view_variable.VAR_SPEAKER_RECORD_TIMEOUT.set(value) + def setLatestConfigVariable_SpeakerRecordTimeout(self, _e=None): + self.setGuiVariable_SpeakerRecordTimeout(config.INPUT_SPEAKER_RECORD_TIMEOUT) + self.clearErrorMessage() + + def setGuiVariable_SpeakerPhraseTimeout(self, value:str="", delete=False): if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_speaker_phrase_timeout) self.view_variable.VAR_SPEAKER_PHRASE_TIMEOUT.set(value) + def setLatestConfigVariable_SpeakerPhraseTimeout(self, _e=None): + self.setGuiVariable_SpeakerPhraseTimeout(config.INPUT_SPEAKER_PHRASE_TIMEOUT) + self.clearErrorMessage() + + def setGuiVariable_SpeakerMaxPhrases(self, value:str="", delete=False): if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_speaker_max_phrases) self.view_variable.VAR_SPEAKER_MAX_PHRASES.set(value) + def setLatestConfigVariable_SpeakerMaxPhrases(self, _e=None): + self.setGuiVariable_SpeakerMaxPhrases(config.INPUT_SPEAKER_MAX_PHRASES) + self.clearErrorMessage() + @staticmethod def _clearEntryBox(entry_widget): entry_widget.delete(0, CTK_END) + def showErrorMessage_MicEnergyThreshold(self): + self._showErrorMessage(vrct_gui.config_window.sb__progressbar_x_slider__entry_mic_energy_threshold, "Mic Energy Threshold Error Message") + def showErrorMessage_MicRecordTimeout(self): + self._showErrorMessage(vrct_gui.config_window.sb__entry_mic_record_timeout, "Mic Record Timeout Error Message") + + def showErrorMessage_MicPhraseTimeout(self): + self._showErrorMessage(vrct_gui.config_window.sb__entry_mic_phrase_timeout, "Mic Phrase Timeout Error Message") + + def showErrorMessage_MicMaxPhrases(self): + self._showErrorMessage(vrct_gui.config_window.sb__entry_mic_max_phrases, "Mic Max Phrases Error Message") + + + def showErrorMessage_SpeakerEnergyThreshold(self): + self._showErrorMessage(vrct_gui.config_window.sb__progressbar_x_slider__entry_speaker_energy_threshold, "Speaker Energy Threshold Error Message") + + def showErrorMessage_SpeakerRecordTimeout(self): + self._showErrorMessage(vrct_gui.config_window.sb__entry_speaker_record_timeout, "Speaker Record Timeout Error Message") + + def showErrorMessage_SpeakerPhraseTimeout(self): + self._showErrorMessage(vrct_gui.config_window.sb__entry_speaker_phrase_timeout, "Speaker Phrase Timeout Error Message") + + def showErrorMessage_SpeakerMaxPhrases(self): + self._showErrorMessage(vrct_gui.config_window.sb__entry_speaker_max_phrases, "Speaker Max Phrases Error Message") + + def _showErrorMessage(self, target_widget, message): + self.view_variable.VAR_ERROR_MESSAGE.set(message) + vrct_gui.showErrorMessage(target_widget=target_widget) + + def clearErrorMessage(self): + vrct_gui._clearErrorMessage() # These conversations are generated by ChatGPT def _insertSampleConversationToTextbox(self): diff --git a/vrct_gui/_CreateErrorWindow.py b/vrct_gui/_CreateErrorWindow.py new file mode 100644 index 00000000..24a33c27 --- /dev/null +++ b/vrct_gui/_CreateErrorWindow.py @@ -0,0 +1,134 @@ +from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont +from time import sleep + +class _CreateErrorWindow(CTkToplevel): + def __init__(self, settings, view_variable, wrapper_widget): + super().__init__() + self.withdraw() + self.hide = True + + self.title("") + self.overrideredirect(True) + + self.wm_attributes("-alpha", 0) + self.wm_attributes("-toolwindow", True) + + self.settings = settings + self.attach_widget = None + self._view_variable = view_variable + self.wrapper_widget = wrapper_widget + + + + self.attach_widget_width = None + self.attach_widget_height = None + self.attach_widget_x_pos = None + self.attach_widget_y_pos = None + self.x_pos = None + self.y_pos = None + + + + self.rowconfigure(0,weight=1) + self.columnconfigure(0,weight=1) + + # The color code [#bb4448] is a mixture of [#a9555c] and [#cc3333] (for a redder shade). + self.modal_container = CTkFrame(self, corner_radius=0, fg_color="#bb4448", width=0, height=0) + self.modal_container.grid(row=0, column=0, sticky="nsew") + + + self.modal_container_label_wrapper = CTkLabel( + self.modal_container, + # text=message, + textvariable=self._view_variable.VAR_ERROR_MESSAGE, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=12, weight="normal"), + anchor="w", + text_color="white", + ) + self.modal_container_label_wrapper.grid(row=0, column=0, padx=10, pady=6, sticky="nsew") + + + + def show(self, target_widget): + if self.hide is False: return + + self.attach_widget = target_widget + + self.deiconify() + self._adjustToTargetWidgetGeometry() + self.BIND_CONFIGURE_FUNC_ID = self.attach_widget.winfo_toplevel().bind("", self._adjustToTargetWidgetGeometry, "+") + self.BIND_UNMAP_FUNC_ID = self.attach_widget.bind("", self._withdraw, "+") + + self.hide = False + + + for i in range(0,101,20): + if not self.winfo_exists(): + break + self.attributes("-alpha", i/100) + self.update() + sleep(1/100) + + sleep(0.1) + + for i in range(0,91,10): + if not self.winfo_exists(): + break + self.attributes("-alpha", i/100) + self.update() + sleep(1/80) + + + def _withdraw(self, e=None): + self.withdraw() + self.attach_widget.winfo_toplevel().unbind("", self.BIND_CONFIGURE_FUNC_ID) + self.attach_widget.unbind("", self.BIND_UNMAP_FUNC_ID) + self.hide = True + + + + def _adjustToTargetWidgetGeometry(self, e=None): + if not self.attach_widget.winfo_exists(): + return + self.attach_widget.update_idletasks() + + + + self.update() + if self.attach_widget_x_pos == self.attach_widget.winfo_rootx() and self.attach_widget_y_pos == self.attach_widget.winfo_rooty(): + self.lift() + return + + self.wrapper_widget_y_pos = self.wrapper_widget.winfo_rooty() + self.wrapper_widget_bottom_y_pos = self.wrapper_widget_y_pos + self.wrapper_widget.winfo_height() + + self.attach_widget_width = self.attach_widget.winfo_width() + self.attach_widget_height = self.attach_widget.winfo_height() + self.attach_widget_x_pos = self.attach_widget.winfo_rootx() + self.attach_widget_y_pos = self.attach_widget.winfo_rooty() + + + self.y_pos = int(self.attach_widget_y_pos + self.attach_widget_height + 4) + + if self.wrapper_widget_y_pos > self.y_pos or self.y_pos > self.wrapper_widget_bottom_y_pos: + self.hideTemporarily() + else: + if self.winfo_exists(): + self.deiconify() + + + if self.winfo_width() >= self.attach_widget_width: + self.x_pos = int(self.attach_widget_x_pos - (self.winfo_width() - self.attach_widget_width)) + else: + self.x_pos = self.attach_widget_x_pos + + self.geometry("+{}+{}".format(self.x_pos, self.y_pos)) + + self.lift() + + def hideTemporarily(self): + self.withdraw() + + diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 26d94b46..4e469de2 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -31,4 +31,7 @@ class ConfigWindow(CTkToplevel): createSettingBoxTopBar(config_window=self, settings=self.settings, view_variable=self._view_variable) - createSideMenuAndSettingsBoxContainers(config_window=self, settings=self.settings, view_variable=self._view_variable) \ No newline at end of file + createSideMenuAndSettingsBoxContainers(config_window=self, settings=self.settings, view_variable=self._view_variable) + + + self.bind_all("", lambda event: event.widget.focus_set(), "+") \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 766e0ccd..051e09eb 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -185,12 +185,13 @@ class _SettingBoxGenerator(): def createSettingBoxProgressbarXSlider( self, for_var_label_text, for_var_desc_text, command, - entry_attr_name, + entry_attr_name, entry_bind__FocusOut, slider_attr_name, slider_range, progressbar_attr_name, passive_button_attr_name, passive_button_command, active_button_attr_name, active_button_command, button_image_file, + entry_variable, slider_variable, @@ -220,10 +221,14 @@ class _SettingBoxGenerator(): ) entry_widget.bind("", adjusted_command__for_entry_bind__Any_KeyRelease) + if entry_bind__FocusOut is not None: + entry_widget.bind("", entry_bind__FocusOut, "+") + entry_widget.grid(row=1, column=SETTING_BOX_COLUMN, padx=0, pady=0, sticky="e") setattr(self.config_window, entry_attr_name, entry_widget) + # at least 2px is needed otherwise the slider button is gonna broken. SLIDER_BORDER_WIDTH = max(2,self.settings.uism.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_LENGTH) SLIDER_BUTTON_LENGTH = int(SLIDER_BORDER_WIDTH/2) @@ -286,7 +291,7 @@ class _SettingBoxGenerator(): - def createSettingBoxEntry(self, for_var_label_text, for_var_desc_text, entry_attr_name, entry_width, entry_bind__Any_KeyRelease, entry_textvariable): + def createSettingBoxEntry(self, for_var_label_text, for_var_desc_text, entry_attr_name, entry_width, entry_bind__Any_KeyRelease, entry_textvariable, entry_bind__FocusOut=None): (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) def adjusted_command__for_entry_bind__Any_KeyRelease(e): @@ -305,6 +310,9 @@ class _SettingBoxGenerator(): entry_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") + if entry_bind__FocusOut is not None: + entry_widget.bind("", entry_bind__FocusOut, "+") + return setting_box_frame diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 66160591..a02bc583 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -72,6 +72,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari entry_attr_name="sb__progressbar_x_slider__entry_mic_energy_threshold", entry_variable=view_variable.VAR_MIC_ENERGY_THRESHOLD__ENTRY, + entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_MIC_ENERGY_THRESHOLD, slider_attr_name="progressbar_x_slider__slider_mic_energy_threshold", @@ -109,6 +110,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_record_timeout_callback(value), entry_textvariable=view_variable.VAR_MIC_RECORD_TIMEOUT, + entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_MIC_RECORD_TIMEOUT, ) config_window.sb__mic_record_timeout.grid(row=row) row+=1 @@ -120,6 +122,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_phrase_timeout_callback(value), entry_textvariable=view_variable.VAR_MIC_PHRASE_TIMEOUT, + entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_MIC_PHRASE_TIMEOUT, ) config_window.sb__mic_phrase_timeout.grid(row=row) row+=1 @@ -131,6 +134,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_max_phrases_callback(value), entry_textvariable=view_variable.VAR_MIC_MAX_PHRASES, + entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_MIC_MAX_PHRASES, ) config_window.sb__mic_max_phrases.grid(row=row) row+=1 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 0a04eca7..5ebae1b5 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -55,6 +55,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ entry_variable=view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__ENTRY, entry_attr_name="sb__progressbar_x_slider__entry_speaker_energy_threshold", + entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_SPEAKER_ENERGY_THRESHOLD, slider_attr_name="progressbar_x_slider__slider_speaker_energy_threshold", @@ -92,6 +93,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_record_timeout_callback(value), entry_textvariable=view_variable.VAR_SPEAKER_RECORD_TIMEOUT, + entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_SPEAKER_RECORD_TIMEOUT, ) config_window.sb__speaker_record_timeout.grid(row=row) row+=1 @@ -103,6 +105,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_phrase_timeout_callback(value), entry_textvariable=view_variable.VAR_SPEAKER_PHRASE_TIMEOUT, + entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_SPEAKER_PHRASE_TIMEOUT, ) config_window.sb__speaker_phrase_timeout.grid(row=row) row+=1 @@ -114,6 +117,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ entry_width=settings.uism.SB__ENTRY_WIDTH_100, entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_max_phrases_callback(value), entry_textvariable=view_variable.VAR_SPEAKER_MAX_PHRASES, + entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_SPEAKER_MAX_PHRASES, ) config_window.sb__speaker_max_phrases.grid(row=row) row+=1 diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 54705bb9..bd167560 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -5,6 +5,7 @@ from customtkinter import CTk, CTkImage from ._CreateSelectableLanguagesWindow import _CreateSelectableLanguagesWindow from ._CreateModalWindow import _CreateModalWindow +from ._CreateErrorWindow import _CreateErrorWindow from ._changeMainWindowWidgetsStatus import _changeMainWindowWidgetsStatus from ._changeConfigWindowWidgetsStatus import _changeConfigWindowWidgetsStatus from ._printToTextbox import _printToTextbox @@ -19,6 +20,7 @@ class VRCT_GUI(CTk): def __init__(self): super().__init__() self.adjusted_event=None + self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID=None def createGUI(self, settings, view_variable): @@ -50,6 +52,12 @@ class VRCT_GUI(CTk): view_variable=self._view_variable ) + self.error_message_window = _CreateErrorWindow( + settings=self.settings.modal_window, + view_variable=self._view_variable, + wrapper_widget=self.config_window.main_bg_container, + ) + def startMainLoop(self): @@ -61,12 +69,12 @@ class VRCT_GUI(CTk): self.destroy() - def openConfigWindow(self, e): + def openConfigWindow(self, _e): callFunctionIfCallable(self._view_variable.CALLBACK_OPEN_CONFIG_WINDOW) self.adjustToMainWindowGeometry() self.modal_window.deiconify() - self.bind("", self.adjustToMainWindowGeometry) + self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID = self.bind("", self.adjustToMainWindowGeometry) self.config_window.deiconify() self.config_window.focus_set() @@ -74,11 +82,12 @@ class VRCT_GUI(CTk): def closeConfigWindow(self): callFunctionIfCallable(self._view_variable.CALLBACK_CLOSE_CONFIG_WINDOW) + self.config_window.withdraw() self.config_window.grab_release() self.modal_window.withdraw() - self.unbind("") + self.unbind("", self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID) self.adjusted_event=None @@ -195,4 +204,15 @@ class VRCT_GUI(CTk): if e is not None: self.adjusted_event=str(e) + + def showErrorMessage(self, target_widget): + self.error_message_window.show(target_widget=target_widget) + + def _clearErrorMessage(self): + try: + self.error_message_window._withdraw() + except: + pass + + vrct_gui = VRCT_GUI() \ No newline at end of file From 5270c680a59d9e13ad2a1d85dcc8dd7bd6e3a5cf Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 5 Oct 2023 13:28:14 +0900 Subject: [PATCH 214/355] =?UTF-8?q?[Refactor]=20main.py=E3=81=8B=E3=82=89v?= =?UTF-8?q?iew.setGuiVariable=E7=B3=BB=E3=81=B8=E3=81=AE=E5=BC=95=E6=95=B0?= =?UTF-8?q?=E3=81=A7=E3=80=81=E5=9E=8B=E5=A4=89=E6=8F=9B=E3=81=97=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=81=9F=E3=81=AE=E3=82=92=E3=81=AA=E3=81=8F=E3=81=97?= =?UTF-8?q?=E3=80=81view=E5=81=B4=E3=81=A7=E5=A4=89=E6=8F=9B=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=80=82=20(=E5=9E=8B?= =?UTF-8?q?=E5=A4=89=E6=8F=9B=E3=81=AFview=E5=81=B4=E3=81=AE=E9=83=BD?= =?UTF-8?q?=E5=90=88=E3=81=A0=E3=81=A8=E6=80=9D=E3=81=A3=E3=81=9F=E3=81=9F?= =?UTF-8?q?=E3=82=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 13 ++++++------- view.py | 32 ++++++++++++++++---------------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/main.py b/main.py index 94d943cd..5cefaa00 100644 --- a/main.py +++ b/main.py @@ -425,7 +425,7 @@ def callbackSetMicRecordTimeout(value): if 0 <= value and value <= config.INPUT_MIC_PHRASE_TIMEOUT: view.clearErrorMessage() config.INPUT_MIC_RECORD_TIMEOUT = value - view.setGuiVariable_MicRecordTimeout(str(config.INPUT_MIC_RECORD_TIMEOUT)) + view.setGuiVariable_MicRecordTimeout(config.INPUT_MIC_RECORD_TIMEOUT) else: raise ValueError() except: @@ -438,7 +438,7 @@ def callbackSetMicPhraseTimeout(value): if 0 <= value and value >= config.INPUT_MIC_RECORD_TIMEOUT: view.clearErrorMessage() config.INPUT_MIC_PHRASE_TIMEOUT = value - view.setGuiVariable_MicPhraseTimeout(str(config.INPUT_MIC_PHRASE_TIMEOUT)) + view.setGuiVariable_MicPhraseTimeout(config.INPUT_MIC_PHRASE_TIMEOUT) else: raise ValueError() except: @@ -451,7 +451,7 @@ def callbackSetMicMaxPhrases(value): if 0 <= value: view.clearErrorMessage() config.INPUT_MIC_MAX_PHRASES = value - view.setGuiVariable_MicMaxPhrases(str(config.INPUT_MIC_MAX_PHRASES)) + view.setGuiVariable_MicMaxPhrases(config.INPUT_MIC_MAX_PHRASES) else: raise ValueError() except: @@ -505,7 +505,6 @@ def callbackCheckSpeakerThreshold(is_turned_on): model.startCheckSpeakerEnergy(setProgressBarSpeakerEnergy) view.replaceSpeakerThresholdCheckButton_Active() view.setWidgetsStatus_ThresholdCheckButton_Normal() - else: view.setWidgetsStatus_ThresholdCheckButton_Disabled() model.stopCheckSpeakerEnergy() @@ -519,7 +518,7 @@ def callbackSetSpeakerRecordTimeout(value): if 0 <= value and value <= config.INPUT_SPEAKER_PHRASE_TIMEOUT: view.clearErrorMessage() config.INPUT_SPEAKER_RECORD_TIMEOUT = value - view.setGuiVariable_SpeakerRecordTimeout(str(config.INPUT_SPEAKER_RECORD_TIMEOUT)) + view.setGuiVariable_SpeakerRecordTimeout(config.INPUT_SPEAKER_RECORD_TIMEOUT) else: raise ValueError() except: @@ -532,7 +531,7 @@ def callbackSetSpeakerPhraseTimeout(value): if 0 <= value and value >= config.INPUT_SPEAKER_RECORD_TIMEOUT: view.clearErrorMessage() config.INPUT_SPEAKER_PHRASE_TIMEOUT = value - view.setGuiVariable_SpeakerPhraseTimeout(str(config.INPUT_SPEAKER_PHRASE_TIMEOUT)) + view.setGuiVariable_SpeakerPhraseTimeout(config.INPUT_SPEAKER_PHRASE_TIMEOUT) else: raise ValueError() except: @@ -545,7 +544,7 @@ def callbackSetSpeakerMaxPhrases(value): if 0 <= value: view.clearErrorMessage() config.INPUT_SPEAKER_MAX_PHRASES = value - view.setGuiVariable_SpeakerMaxPhrases(str(config.INPUT_SPEAKER_MAX_PHRASES)) + view.setGuiVariable_SpeakerMaxPhrases(config.INPUT_SPEAKER_MAX_PHRASES) else: raise ValueError() except: diff --git a/view.py b/view.py index 88661660..dd32167b 100644 --- a/view.py +++ b/view.py @@ -728,8 +728,8 @@ class View(): - def setGuiVariable_MicEnergyThreshold(self, value:int): - self.view_variable.VAR_MIC_ENERGY_THRESHOLD__SLIDER.set(value) + def setGuiVariable_MicEnergyThreshold(self, value): + self.view_variable.VAR_MIC_ENERGY_THRESHOLD__SLIDER.set(int(value)) self.view_variable.VAR_MIC_ENERGY_THRESHOLD__ENTRY.set(str(value)) def setLatestConfigVariable_MicEnergyThreshold(self, _e=None): @@ -737,8 +737,8 @@ class View(): self.clearErrorMessage() - def setGuiVariable_SpeakerEnergyThreshold(self, value:int): - self.view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__SLIDER.set(value) + def setGuiVariable_SpeakerEnergyThreshold(self, value): + self.view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__SLIDER.set(int(value)) self.view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__ENTRY.set(str(value)) def setLatestConfigVariable_SpeakerEnergyThreshold(self, _e=None): @@ -747,27 +747,27 @@ class View(): - def setGuiVariable_MicRecordTimeout(self, value:str="", delete=False): + def setGuiVariable_MicRecordTimeout(self, value, delete=False): if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_mic_record_timeout) - self.view_variable.VAR_MIC_RECORD_TIMEOUT.set(value) + self.view_variable.VAR_MIC_RECORD_TIMEOUT.set(str(value)) def setLatestConfigVariable_MicRecordTimeout(self, _e=None): self.setGuiVariable_MicRecordTimeout(config.INPUT_MIC_RECORD_TIMEOUT) self.clearErrorMessage() - def setGuiVariable_MicPhraseTimeout(self, value:str="", delete=False): + def setGuiVariable_MicPhraseTimeout(self, value, delete=False): if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_mic_phrase_timeout) - self.view_variable.VAR_MIC_PHRASE_TIMEOUT.set(value) + self.view_variable.VAR_MIC_PHRASE_TIMEOUT.set(str(value)) def setLatestConfigVariable_MicPhraseTimeout(self, _e=None): self.setGuiVariable_MicPhraseTimeout(config.INPUT_MIC_PHRASE_TIMEOUT) self.clearErrorMessage() - def setGuiVariable_MicMaxPhrases(self, value:str="", delete=False): + def setGuiVariable_MicMaxPhrases(self, value, delete=False): if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_mic_max_phrases) - self.view_variable.VAR_MIC_MAX_PHRASES.set(value) + self.view_variable.VAR_MIC_MAX_PHRASES.set(str(value)) def setLatestConfigVariable_MicMaxPhrases(self, _e=None): self.setGuiVariable_MicMaxPhrases(config.INPUT_MIC_MAX_PHRASES) @@ -775,27 +775,27 @@ class View(): - def setGuiVariable_SpeakerRecordTimeout(self, value:str="", delete=False): + def setGuiVariable_SpeakerRecordTimeout(self, value, delete=False): if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_speaker_record_timeout) - self.view_variable.VAR_SPEAKER_RECORD_TIMEOUT.set(value) + self.view_variable.VAR_SPEAKER_RECORD_TIMEOUT.set(str(value)) def setLatestConfigVariable_SpeakerRecordTimeout(self, _e=None): self.setGuiVariable_SpeakerRecordTimeout(config.INPUT_SPEAKER_RECORD_TIMEOUT) self.clearErrorMessage() - def setGuiVariable_SpeakerPhraseTimeout(self, value:str="", delete=False): + def setGuiVariable_SpeakerPhraseTimeout(self, value, delete=False): if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_speaker_phrase_timeout) - self.view_variable.VAR_SPEAKER_PHRASE_TIMEOUT.set(value) + self.view_variable.VAR_SPEAKER_PHRASE_TIMEOUT.set(str(value)) def setLatestConfigVariable_SpeakerPhraseTimeout(self, _e=None): self.setGuiVariable_SpeakerPhraseTimeout(config.INPUT_SPEAKER_PHRASE_TIMEOUT) self.clearErrorMessage() - def setGuiVariable_SpeakerMaxPhrases(self, value:str="", delete=False): + def setGuiVariable_SpeakerMaxPhrases(self, value, delete=False): if delete is True: self._clearEntryBox(vrct_gui.config_window.sb__entry_speaker_max_phrases) - self.view_variable.VAR_SPEAKER_MAX_PHRASES.set(value) + self.view_variable.VAR_SPEAKER_MAX_PHRASES.set(str(value)) def setLatestConfigVariable_SpeakerMaxPhrases(self, _e=None): self.setGuiVariable_SpeakerMaxPhrases(config.INPUT_SPEAKER_MAX_PHRASES) From be02b948aa3a3a8592e295919f3648dba755f905 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 6 Oct 2023 01:04:59 +0900 Subject: [PATCH 215/355] [Chore] Main Window: add corner_radius=4 to language settings button and the tab switches that is belong to. --- .../widgets/_create_sidebar/createSidebarLanguagesSettings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 9501f9f6..ede9a14a 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -71,7 +71,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): - sls__selected_language_box = CTkFrame(sls__box_wrapper, corner_radius=0, fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, cursor="hand2") + sls__selected_language_box = CTkFrame(sls__box_wrapper, corner_radius=4, fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, cursor="hand2") sls__selected_language_box.grid(row=1, column=0) @@ -176,7 +176,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): preset_tab_attr_name, CTkFrame( main_window.sls__presets_buttons_box, - corner_radius=0, + corner_radius=4, fg_color=settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR, width=0, height=30, From ff4460620b09b695fcd4c19b83d97ef9792eccc2 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 6 Oct 2023 02:29:21 +0900 Subject: [PATCH 216/355] =?UTF-8?q?[bugfix]=20Main=20Window:=20Language=20?= =?UTF-8?q?Settings=20Preset=20Tabs.=20=E8=A6=8B=E3=81=9F=E7=9B=AE?= =?UTF-8?q?=E3=81=AF=E4=B8=8A=E5=81=B4=E3=81=A0=E3=81=91=E3=81=AE=E8=A7=92?= =?UTF-8?q?=E4=B8=B8=E3=81=AB=E3=80=82(4px=E3=81=8B=E3=82=896px=E3=81=AB?= =?UTF-8?q?=E3=82=82=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B)=20=E5=85=B7?= =?UTF-8?q?=E4=BD=93=E7=9A=84=E3=81=AB=E3=81=AFplace=E4=BD=BF=E3=81=A3?= =?UTF-8?q?=E3=81=A6=E8=A6=81=E7=B4=A0=E3=82=92=E4=B8=8B=E3=81=AB=E3=81=9A?= =?UTF-8?q?=E3=82=89=E3=81=97=E3=80=81=E4=B8=8B=E8=A7=92=E4=B8=B8=E3=82=92?= =?UTF-8?q?=E9=9A=A0=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B=E3=80=82(text=5Fb?= =?UTF-8?q?ox=E3=81=AEtabs=E3=81=A7=E3=81=AF=E6=97=A2=E3=81=AB=E5=90=8C?= =?UTF-8?q?=E3=81=98=E3=81=93=E3=81=A8=E3=82=92=E3=81=97=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=82=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../createSidebarLanguagesSettings.py | 13 ++++++++----- vrct_gui/main_window/widgets/create_textbox.py | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index ede9a14a..29497ac3 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -141,8 +141,11 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): # Presets buttons main_window.sidebar_bg_container.grid_rowconfigure(2, weight=1) - main_window.sls__presets_buttons_box = CTkFrame(main_window.sls__container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) - main_window.sls__presets_buttons_box.grid(row=1, column=0, sticky="ew") + main_window.sls__presets_buttons_container = CTkFrame(main_window.sls__container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=30) + main_window.sls__presets_buttons_container.grid(row=1, column=0, sticky="nsew") + + main_window.sls__presets_buttons_box = CTkFrame(main_window.sls__presets_buttons_container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + main_window.sls__presets_buttons_box.place(relwidth=1, relx=0, rely=1.15, anchor="sw") main_window.sls__presets_buttons_box.grid_columnconfigure((0,1,2), weight=1) @@ -176,10 +179,10 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): preset_tab_attr_name, CTkFrame( main_window.sls__presets_buttons_box, - corner_radius=4, + corner_radius=6, fg_color=settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR, width=0, - height=30, + height=36, cursor="hand2", ) ) @@ -195,7 +198,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): anchor="center", text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE ) - label_widget.place(relx=0.5, rely=0.5, anchor="center") + label_widget.place(relx=0.5, rely=0.44, anchor="center") diff --git a/vrct_gui/main_window/widgets/create_textbox.py b/vrct_gui/main_window/widgets/create_textbox.py index 76d50a8b..891e78cb 100644 --- a/vrct_gui/main_window/widgets/create_textbox.py +++ b/vrct_gui/main_window/widgets/create_textbox.py @@ -61,8 +61,8 @@ def createTextbox(settings, main_window, view_variable): main_window.main_textbox_container = CTkFrame(main_window.main_bg_container, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) main_window.main_textbox_container.grid(row=1, column=0, sticky="nsew") - main_window.main_textbox_container.columnconfigure(0,weight=1) - main_window.main_textbox_container.rowconfigure(0,weight=1) + main_window.main_textbox_container.grid_columnconfigure(0,weight=1) + main_window.main_textbox_container.grid_rowconfigure(0,weight=1) main_window.textbox_switch_tabs_container = CTkFrame(main_window.main_topbar_center_container, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) main_window.textbox_switch_tabs_container.place(relx=0.07, rely=1.15, anchor="sw") From b1058efebef77a422881c2ff3e6b5ab82fb2887e Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 6 Oct 2023 07:35:16 +0900 Subject: [PATCH 217/355] =?UTF-8?q?[Refactor]=20Main=20Window:=20Language?= =?UTF-8?q?=20Settings.=20Option=20Menu.=20=E9=96=A2=E6=95=B0=E5=88=87?= =?UTF-8?q?=E3=82=8A=E5=87=BA=E3=81=97=E3=80=81=E6=B1=8E=E7=94=A8=E5=8C=96?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../createSidebarLanguagesSettings.py | 78 ++++++++----------- vrct_gui/ui_utils/ui_utils.py | 50 +++++++++++- 2 files changed, 81 insertions(+), 47 deletions(-) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 29497ac3..c739d7db 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -1,6 +1,6 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkImage -from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor +from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor, createOptionMenuBox from utils import callFunctionIfCallable @@ -51,69 +51,55 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): def createLanguageSettingBox(parent_widget, var_title_text, title_text_attr_name, arrow_img_attr_name, open_selectable_language_window_command, variable): sls__box = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) - sls__box.columnconfigure((0,2), weight=1) + sls__box.columnconfigure(1, weight=1) sls__box_wrapper = CTkFrame(sls__box, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) - sls__box_wrapper.grid(row=2, column=1, padx=10, pady=settings.uism.SLS__BOX_IPADY) + sls__box_wrapper.grid(row=2, column=1, padx=10, pady=settings.uism.SLS__BOX_IPADY, sticky="ew") + + sls__box_wrapper.grid_columnconfigure(0, weight=1) + sls__box_label_wrapper = CTkFrame(sls__box_wrapper, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) + sls__box_label_wrapper.grid(row=0, column=0) + + sls__box_label_wrapper.grid_columnconfigure((0,2), weight=1) sls__label = CTkLabel( - sls__box_wrapper, + sls__box_label_wrapper, textvariable=var_title_text, height=0, font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_SECTION_TITLE_FONT_SIZE, weight="normal"), text_color=settings.ctm.SLS__BOX_SECTION_TITLE_TEXT_COLOR ) - sls__label.grid(row=0, column=0, pady=(0,settings.uism.SLS__BOX_SECTION_TITLE_BOTTOM_PADY)) + sls__label.grid(row=0, column=1, pady=(0,settings.uism.SLS__BOX_SECTION_TITLE_BOTTOM_PADY)) setattr(main_window, title_text_attr_name, sls__label) + sls__box_optionmenu_wrapper = CTkFrame(sls__box_wrapper, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) + sls__box_optionmenu_wrapper.grid(row=1, column=0, sticky="ew") - sls__selected_language_box = CTkFrame(sls__box_wrapper, corner_radius=4, fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, cursor="hand2") - sls__selected_language_box.grid(row=1, column=0) + sls__box_optionmenu_wrapper.grid_columnconfigure(0, weight=1) + sls__selected_language_box = createOptionMenuBox( + parent_widget=sls__box_optionmenu_wrapper, + optionmenu_bg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, + optionmenu_hovered_bg_color=settings.ctm.SLS__DROPDOWN_MENU_HOVERED_BG_COLOR, + optionmenu_clicked_bg_color=settings.ctm.SLS__DROPDOWN_MENU_CLICKED_BG_COLOR, + optionmenu_ipadx=(0,0), + optionmenu_ipady=2, + variable=variable, + font_family=settings.FONT_FAMILY, + font_size=settings.uism.SLS__BOX_DROPDOWN_MENU_FONT_SIZE, + text_color=settings.ctm.LABELS_TEXT_COLOR, + image_file=settings.image_file.ARROW_LEFT.rotate(180), + image_size=(20,20), + command=open_selectable_language_window_command, - - sls__selected_language_box.columnconfigure(0, minsize=200) - sls__selected_language_box.rowconfigure(0, minsize=30) - sls__selected_language_label_frame = CTkFrame(sls__selected_language_box, corner_radius=0, fg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR) - sls__selected_language_label_frame.grid(row=0, column=0) - - sls__selected_language_label = CTkLabel( - sls__selected_language_label_frame, - textvariable=variable, - height=0, - # anchor="center", - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.SLS__BOX_DROPDOWN_MENU_FONT_SIZE, weight="normal"), - text_color=settings.ctm.LABELS_TEXT_COLOR + optionmenu_position="center", + setattr_widget=main_window, + image_widget_attr_name=arrow_img_attr_name, ) - sls__selected_language_label.grid(row=0, column=0, pady=2) - setattr(main_window, title_text_attr_name, sls__selected_language_label) - - - sls__selected_language_arrow_img = CTkLabel( - sls__selected_language_box, - text=None, - corner_radius=0, - height=0, - image=CTkImage((settings.image_file.ARROW_LEFT).rotate(180),size=(20,20)) - ) - setattr(main_window, arrow_img_attr_name, sls__selected_language_arrow_img) - - - - sls__selected_language_arrow_img.grid(row=0, column=1, padx=0, pady=0) - - - - - bindEnterAndLeaveColor([sls__selected_language_label_frame, sls__selected_language_box, sls__selected_language_label, sls__selected_language_arrow_img], settings.ctm.SLS__DROPDOWN_MENU_HOVERED_BG_COLOR, settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR) - bindButtonPressColor([sls__selected_language_label_frame, sls__selected_language_box, sls__selected_language_label, sls__selected_language_arrow_img], settings.ctm.SLS__DROPDOWN_MENU_CLICKED_BG_COLOR, settings.ctm.SLS__DROPDOWN_MENU_HOVERED_BG_COLOR) - - - - bindButtonReleaseFunction([sls__selected_language_label_frame, sls__selected_language_box, sls__selected_language_label, sls__selected_language_arrow_img], open_selectable_language_window_command) + sls__selected_language_box.grid(row=0, column=0, sticky="ew") return sls__box diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index 1955fbfa..a68d1743 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -1,7 +1,7 @@ from os import path as os_path from PIL.Image import open as Image_open, LANCZOS -from customtkinter import CTkFrame, CTkLabel, CTkImage +from customtkinter import CTkFrame, CTkLabel, CTkImage, CTkFont def getImagePath(file_name): # root\img\file_name @@ -140,3 +140,51 @@ def createButtonWithImage(parent_widget, button_fg_color, button_enter_color, bu ) return button_wrapper + + +def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_bg_color, optionmenu_clicked_bg_color, optionmenu_ipadx, optionmenu_ipady, variable, font_family, font_size, text_color, image_file, image_size, command, optionmenu_position=None, setattr_widget=None, image_widget_attr_name=None): + + option_menu_box = CTkFrame(parent_widget, corner_radius=4, fg_color=optionmenu_bg_color, cursor="hand2") + + option_menu_box.grid_columnconfigure(0, weight=1) + option_menu_box.grid_rowconfigure(0, weight=1) + optionmenu_label_wrapper = CTkFrame(option_menu_box, corner_radius=0, fg_color=optionmenu_bg_color) + optionmenu_label_wrapper.grid(row=0, column=0, sticky="ew") + + LABEL_COLUMN=0 + if optionmenu_position == "center": + optionmenu_label_wrapper.grid_columnconfigure((0,2), weight=1) + LABEL_COLUMN=1 + + optionmenu_label_widget = CTkLabel( + optionmenu_label_wrapper, + textvariable=variable, + height=0, + font=CTkFont(family=font_family, size=font_size, weight="normal"), + text_color=text_color + ) + optionmenu_label_widget.grid(row=0, column=LABEL_COLUMN, padx=optionmenu_ipadx, pady=optionmenu_ipady) + + + optionmenu_img_widget = CTkLabel( + option_menu_box, + text=None, + corner_radius=0, + height=0, + image=CTkImage(image_file, size=image_size) + ) + + if image_widget_attr_name is not None: + setattr(setattr_widget, image_widget_attr_name, optionmenu_img_widget) + + optionmenu_img_widget.grid(row=0, column=1, padx=0, pady=0) + + + bindEnterAndLeaveColor([optionmenu_label_wrapper, option_menu_box, optionmenu_label_widget, optionmenu_img_widget], optionmenu_hovered_bg_color, optionmenu_bg_color) + bindButtonPressColor([optionmenu_label_wrapper, option_menu_box, optionmenu_label_widget, optionmenu_img_widget], optionmenu_clicked_bg_color, optionmenu_hovered_bg_color) + + + + bindButtonReleaseFunction([optionmenu_label_wrapper, option_menu_box, optionmenu_label_widget, optionmenu_img_widget], command) + + return option_menu_box \ No newline at end of file From a393f80e23df4a09d1a5ebc7f8585e60d369e57c Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 6 Oct 2023 08:09:24 +0900 Subject: [PATCH 218/355] =?UTF-8?q?[Chore]=20Change=20the=20variable=20nam?= =?UTF-8?q?es=20of=20color=20to=20appropriate=20ones.=20Option=20Menu?= =?UTF-8?q?=E3=81=AA=E3=81=AE=E3=81=ABDropdown=20Menu=E3=81=A8=E3=81=97?= =?UTF-8?q?=E3=81=A6=E8=A1=A8=E8=A8=98=E3=81=97=E3=81=A6=E3=81=84=E3=81=9F?= =?UTF-8?q?=E3=81=AE=E3=81=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_create_sidebar/createSidebarLanguagesSettings.py | 6 +++--- vrct_gui/ui_managers/ColorThemeManager.py | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index c739d7db..2ebf4d04 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -82,9 +82,9 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): sls__box_optionmenu_wrapper.grid_columnconfigure(0, weight=1) sls__selected_language_box = createOptionMenuBox( parent_widget=sls__box_optionmenu_wrapper, - optionmenu_bg_color=settings.ctm.SLS__DROPDOWN_MENU_BG_COLOR, - optionmenu_hovered_bg_color=settings.ctm.SLS__DROPDOWN_MENU_HOVERED_BG_COLOR, - optionmenu_clicked_bg_color=settings.ctm.SLS__DROPDOWN_MENU_CLICKED_BG_COLOR, + optionmenu_bg_color=settings.ctm.SLS__OPTIONMENU_BG_COLOR, + optionmenu_hovered_bg_color=settings.ctm.SLS__OPTIONMENU_HOVERED_BG_COLOR, + optionmenu_clicked_bg_color=settings.ctm.SLS__OPTIONMENU_CLICKED_BG_COLOR, optionmenu_ipadx=(0,0), optionmenu_ipady=2, variable=variable, diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index c712b2d4..a94f5f8f 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -158,9 +158,9 @@ class ColorThemeManager(): self.main.SLS__BOX_SECTION_TITLE_TEXT_COLOR = self.DARK_400_COLOR self.main.SLS__BOX_ARROWS_TEXT_COLOR = self.DARK_500_COLOR - self.main.SLS__DROPDOWN_MENU_BG_COLOR = self.DARK_888_COLOR - self.main.SLS__DROPDOWN_MENU_HOVERED_BG_COLOR = self.DARK_875_COLOR - self.main.SLS__DROPDOWN_MENU_CLICKED_BG_COLOR = self.DARK_900_COLOR + self.main.SLS__OPTIONMENU_BG_COLOR = self.DARK_888_COLOR + self.main.SLS__OPTIONMENU_HOVERED_BG_COLOR = self.DARK_875_COLOR + self.main.SLS__OPTIONMENU_CLICKED_BG_COLOR = self.DARK_900_COLOR self.main.CONFIG_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR @@ -343,7 +343,7 @@ class ColorThemeManager(): self.main.SLS__BOX_SECTION_TITLE_TEXT_COLOR = self.LIGHT_800_COLOR self.main.SLS__BOX_ARROWS_TEXT_COLOR = self.LIGHT_700_COLOR - self.main.SLS__DROPDOWN_MENU_BG_COLOR = self.LIGHT_500_COLOR + self.main.SLS__OPTIONMENU_BG_COLOR = self.LIGHT_500_COLOR self.main.CONFIG_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR From 8dd1ddd6acd64b89581fa7249f5668eee635e0b6 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 6 Oct 2023 16:30:56 +0900 Subject: [PATCH 219/355] =?UTF-8?q?[Update(tmp)]=20=E2=80=BBDropdown=20Men?= =?UTF-8?q?u=E9=96=8B=E3=81=8D=E3=81=BE=E3=81=9B=E3=82=93=E3=80=82=20Confi?= =?UTF-8?q?g=20Window:=20Option=20Menu=E7=B3=BBwidget=E3=82=92=E5=85=A8?= =?UTF-8?q?=E3=81=A6frame=E3=82=92=E4=BD=BF=E3=81=A3=E3=81=9F=E8=87=AA?= =?UTF-8?q?=E4=BD=9Cwidget=E3=81=AB=E3=81=99=E3=82=8A=E6=9B=BF=E3=81=88?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 6 ++-- .../_SettingBoxGenerator.py | 35 ++++++++++++------- vrct_gui/ui_managers/ColorThemeManager.py | 1 + vrct_gui/ui_managers/UiScalingManager.py | 6 ++-- vrct_gui/ui_utils/ui_utils.py | 16 +++++---- 5 files changed, 40 insertions(+), 24 deletions(-) diff --git a/view.py b/view.py index dd32167b..d19acb2b 100644 --- a/view.py +++ b/view.py @@ -692,14 +692,14 @@ class View(): def updateList_MicHost(self, new_mic_host_list:list): self.view_variable.LIST_MIC_HOST = new_mic_host_list - vrct_gui.config_window.sb__optionmenu_mic_host.configure(values=new_mic_host_list) + # vrct_gui.config_window.sb__optionmenu_mic_host.configure(values=new_mic_host_list) def updateSelected_MicHost(self, selected_mic_host_name:str): self.view_variable.VAR_MIC_HOST.set(selected_mic_host_name) def updateList_MicDevice(self, new_mic_device_list): self.view_variable.LIST_MIC_DEVICE = new_mic_device_list - vrct_gui.config_window.sb__optionmenu_mic_device.configure(values=new_mic_device_list) + # vrct_gui.config_window.sb__optionmenu_mic_device.configure(values=new_mic_device_list) def updateSelected_MicDevice(self, default_selected_mic_device_name:str): self.view_variable.VAR_MIC_DEVICE.set(default_selected_mic_device_name) @@ -716,7 +716,7 @@ class View(): def updateList_SpeakerDevice(self, new_speaker_device_list): self.view_variable.LIST_SPEAKER_DEVICE = new_speaker_device_list - vrct_gui.config_window.sb__optionmenu_speaker_device.configure(values=new_speaker_device_list) + # vrct_gui.config_window.sb__optionmenu_speaker_device.configure(values=new_speaker_device_list) @staticmethod def updateSetProgressBar_SpeakerEnergy(new_speaker_energy): diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 051e09eb..fc53e23f 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -3,7 +3,7 @@ from typing import Union from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar -from vrct_gui.ui_utils import createButtonWithImage, getLatestWidth +from vrct_gui.ui_utils import createButtonWithImage, getLatestWidth, createOptionMenuBox SETTING_BOX_COLUMN = 1 @@ -76,19 +76,30 @@ class _SettingBoxGenerator(): def createSettingBoxDropdownMenu(self, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, variable=None, dropdown_menu_values=None): (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) - option_menu_widget = CTkOptionMenu( - setting_box_item_frame, - height=self.settings.uism.SB__OPTIONMENU_HEIGHT, - width=self.settings.uism.SB__OPTIONMENU_WIDTH, - values=dropdown_menu_values, - button_color=self.settings.ctm.SB__OPTIONMENU_BG_COLOR, - button_hover_color=self.settings.ctm.SB__OPTIONMENU_HOVERED_BG_COLOR, - fg_color=self.settings.ctm.SB__OPTIONMENU_BG_COLOR, - font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__OPTION_MENU_FONT_SIZE, weight="normal"), + option_menu_widget = createOptionMenuBox( + parent_widget=setting_box_item_frame, + optionmenu_bg_color=self.settings.ctm.SB__OPTIONMENU_BG_COLOR, + optionmenu_hovered_bg_color=self.settings.ctm.SB__OPTIONMENU_HOVERED_BG_COLOR, + optionmenu_clicked_bg_color=self.settings.ctm.SB__OPTIONMENU_CLICKED_BG_COLOR, + optionmenu_ipadx=(8,8), + optionmenu_ipady=2, + optionmenu_ipady_between_img=8, + optionmenu_min_height=self.settings.uism.SB__OPTIONMENU_MIN_HEIGHT, + optionmenu_min_width=self.settings.uism.SB__OPTIONMENU_MIN_WIDTH, variable=variable, - command=command, - anchor="w", + font_family=self.settings.FONT_FAMILY, + font_size=self.settings.uism.SB__OPTION_MENU_FONT_SIZE, + text_color=self.settings.ctm.LABELS_TEXT_COLOR, + image_file=self.settings.image_file.ARROW_LEFT.rotate(90), + image_size=(14,14), + command=lambda _e: print(_e), + # command=open_selectable_language_window_command, + + # optionmenu_position="center", + # setattr_widget=main_window, + # image_widget_attr_name=arrow_img_attr_name, ) + option_menu_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") setattr(self.config_window, optionmenu_attr_name, option_menu_widget) diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index a94f5f8f..124b2a01 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -232,6 +232,7 @@ class ColorThemeManager(): self.config_window.SB__OPTIONMENU_BG_COLOR = self.DARK_925_COLOR self.config_window.SB__OPTIONMENU_HOVERED_BG_COLOR = self.DARK_850_COLOR + self.config_window.SB__OPTIONMENU_CLICKED_BG_COLOR = self.DARK_950_COLOR self.config_window.SB__SLIDER_BUTTON_COLOR = self.DARK_700_COLOR self.config_window.SB__SLIDER_BUTTON_HOVERED_COLOR = self.DARK_600_COLOR diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 902951b5..bf16839f 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -146,9 +146,9 @@ class UiScalingManager(): self.config_window.SB__OPTION_MENU_FONT_SIZE = self.config_window.SB__SELECTOR_FONT_SIZE - self.config_window.SB__OPTIONMENU_HEIGHT = self._calculateUiSize(30) - self.config_window.SB__OPTIONMENU_WIDTH = self._calculateUiSize(200) - self.config_window.SB__DROPDOWN_MENU_WIDTH = self.config_window.SB__OPTIONMENU_WIDTH + self.config_window.SB__OPTIONMENU_MIN_HEIGHT = self._calculateUiSize(30) + self.config_window.SB__OPTIONMENU_MIN_WIDTH = self._calculateUiSize(200) + self.config_window.SB__DROPDOWN_MENU_WIDTH = self.config_window.SB__OPTIONMENU_MIN_WIDTH self.config_window.SB__DROPDOWN_MENU_MAX_BUTTON_HEIGHT = int(self.config_window.SB__OPTION_MENU_FONT_SIZE + self._calculateUiSize(6)) self.config_window.SB__DROPDOWN_MENU_FRAME_CORNER_RADIUS = self._calculateUiSize(10) self.config_window.SB__DROPDOWN_MENU_FRAME_MAX_HEIGHT = self._calculateUiSize(200) diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index a68d1743..254c6c41 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -142,14 +142,18 @@ def createButtonWithImage(parent_widget, button_fg_color, button_enter_color, bu return button_wrapper -def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_bg_color, optionmenu_clicked_bg_color, optionmenu_ipadx, optionmenu_ipady, variable, font_family, font_size, text_color, image_file, image_size, command, optionmenu_position=None, setattr_widget=None, image_widget_attr_name=None): +def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_bg_color, optionmenu_clicked_bg_color, optionmenu_ipadx, optionmenu_ipady, variable, font_family, font_size, text_color, image_file, image_size, command, optionmenu_position=None, optionmenu_ipady_between_img=0, optionmenu_min_height=None, optionmenu_min_width=None, setattr_widget=None, image_widget_attr_name=None): - option_menu_box = CTkFrame(parent_widget, corner_radius=4, fg_color=optionmenu_bg_color, cursor="hand2") + option_menu_box = CTkFrame(parent_widget, corner_radius=6, fg_color=optionmenu_bg_color, cursor="hand2") + + option_menu_box.grid_rowconfigure(0, weight=1) + if optionmenu_min_height is not None: option_menu_box.grid_rowconfigure(0, minsize=optionmenu_min_height) option_menu_box.grid_columnconfigure(0, weight=1) - option_menu_box.grid_rowconfigure(0, weight=1) + if optionmenu_min_width is not None: option_menu_box.grid_columnconfigure(0, minsize=optionmenu_min_width) + optionmenu_label_wrapper = CTkFrame(option_menu_box, corner_radius=0, fg_color=optionmenu_bg_color) - optionmenu_label_wrapper.grid(row=0, column=0, sticky="ew") + optionmenu_label_wrapper.grid(row=0, column=0, padx=(optionmenu_ipadx[0],0), pady=optionmenu_ipady, sticky="ew") LABEL_COLUMN=0 if optionmenu_position == "center": @@ -163,7 +167,7 @@ def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_b font=CTkFont(family=font_family, size=font_size, weight="normal"), text_color=text_color ) - optionmenu_label_widget.grid(row=0, column=LABEL_COLUMN, padx=optionmenu_ipadx, pady=optionmenu_ipady) + optionmenu_label_widget.grid(row=0, column=LABEL_COLUMN, padx=(0, optionmenu_ipady_between_img)) optionmenu_img_widget = CTkLabel( @@ -177,7 +181,7 @@ def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_b if image_widget_attr_name is not None: setattr(setattr_widget, image_widget_attr_name, optionmenu_img_widget) - optionmenu_img_widget.grid(row=0, column=1, padx=0, pady=0) + optionmenu_img_widget.grid(row=0, column=1, padx=(0, optionmenu_ipadx[1]), pady=optionmenu_ipady) bindEnterAndLeaveColor([optionmenu_label_wrapper, option_menu_box, optionmenu_label_widget, optionmenu_img_widget], optionmenu_hovered_bg_color, optionmenu_bg_color) From c4cd3772cdc7b5cdd357932b125bc53244cdd66e Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 7 Oct 2023 22:35:46 +0900 Subject: [PATCH 220/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20=E3=83=91?= =?UTF-8?q?=E3=83=83=E3=82=B1=E3=83=BC=E3=82=B8=E3=81=AE=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=83=A7=E3=83=B3=E3=82=92=E5=9B=BA=E5=AE=9A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- requirements.txt | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/requirements.txt b/requirements.txt index bf9b297b..a399733d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,8 +1,8 @@ -pillow -PyAudioWPatch -python-osc -customtkinter -deepl -flashtext -pyyaml -python-i18n \ No newline at end of file +pillow == 10.0.0 +PyAudioWPatch == 0.2.12.6 +python-osc == 1.8.3 +customtkinter == 5.2.0 +deepl == 1.15.0 +flashtext == 2.7 +pyyaml == 6.0.1 +python-i18n == 0.3.9 \ No newline at end of file From 7e33f04baa478d4481c1beeaa5e50d07e7fcfc7f Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 8 Oct 2023 11:13:18 +0900 Subject: [PATCH 221/355] =?UTF-8?q?[Update]=20Config=20Window:=20DropdownM?= =?UTF-8?q?enu=E8=BF=BD=E5=8A=A0=E3=80=82=E5=89=8D=E5=9B=9E=E3=82=B3?= =?UTF-8?q?=E3=83=9F=E3=83=83=E3=83=88=E3=81=A7=E3=81=AFOption=20Menu?= =?UTF-8?q?=E3=82=92=E3=82=AF=E3=83=AA=E3=83=83=E3=82=AF=E3=81=97=E3=81=A6?= =?UTF-8?q?=E3=82=82=E4=BD=95=E3=82=82=E8=B5=B7=E3=81=93=E3=82=89=E3=81=AA?= =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=9F=E3=81=8C=E3=80=81DropdownMenu?= =?UTF-8?q?=E3=81=A8=E3=81=97=E3=81=A6=E9=81=B8=E6=8A=9E=E8=82=A2=E3=82=92?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=80=82=E9=81=B8=E6=8A=9E=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=81=9F=E9=9A=9B=E3=81=AE=E6=A9=9F=E8=83=BD=E3=82=82=E4=BB=8A?= =?UTF-8?q?=E3=81=BE=E3=81=A7=E9=80=9A=E3=82=8A=E3=80=82=20grab=5Fset?= =?UTF-8?q?=E5=BB=83=E6=AD=A2=E3=80=82focus=5Fset=E3=82=84lift=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E3=81=A3=E3=81=A6=E3=81=93=E3=81=A1=E3=82=89=E3=81=A7?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=88=E3=83=AD=E3=83=BC=E3=83=AB=E3=80=82?= =?UTF-8?q?=20=E7=90=86=E7=94=B1=E3=81=AF=E3=80=81=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E7=94=BB=E9=9D=A2=E4=B8=8A=E3=81=A7=E6=96=B0=E3=81=9F=E3=81=AB?= =?UTF-8?q?Dropdown=20Menu=20Window=E3=82=92=E7=94=9F=E6=88=90=E3=81=97?= =?UTF-8?q?=E3=80=81=E9=81=B8=E6=8A=9E=E8=82=A2=E3=82=92=E3=82=AF=E3=83=AA?= =?UTF-8?q?=E3=83=83=E3=82=AF=E3=81=99=E3=82=8B=E9=9A=9B=E9=82=AA=E9=AD=94?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=82=8B=E3=81=9F=E3=82=81=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 19 +- vrct_gui/_CreateDropdownMenuWindow.py | 268 ++++++++++++++++++ .../_SettingBoxGenerator.py | 30 +- .../createSidebarLanguagesSettings.py | 2 +- vrct_gui/ui_managers/ColorThemeManager.py | 7 + vrct_gui/ui_utils/ui_utils.py | 4 +- vrct_gui/vrct_gui.py | 17 +- 7 files changed, 330 insertions(+), 17 deletions(-) create mode 100644 vrct_gui/_CreateDropdownMenuWindow.py diff --git a/view.py b/view.py index d19acb2b..9d256518 100644 --- a/view.py +++ b/view.py @@ -692,14 +692,20 @@ class View(): def updateList_MicHost(self, new_mic_host_list:list): self.view_variable.LIST_MIC_HOST = new_mic_host_list - # vrct_gui.config_window.sb__optionmenu_mic_host.configure(values=new_mic_host_list) + vrct_gui.dropdown_menu_window.updateDropdownMenuValues( + dropdown_menu_widget_id="sb__optionmenu_mic_host", + dropdown_menu_values=new_mic_host_list, + ) def updateSelected_MicHost(self, selected_mic_host_name:str): self.view_variable.VAR_MIC_HOST.set(selected_mic_host_name) - def updateList_MicDevice(self, new_mic_device_list): + def updateList_MicDevice(self, new_mic_device_list:list): self.view_variable.LIST_MIC_DEVICE = new_mic_device_list - # vrct_gui.config_window.sb__optionmenu_mic_device.configure(values=new_mic_device_list) + vrct_gui.dropdown_menu_window.updateDropdownMenuValues( + dropdown_menu_widget_id="sb__optionmenu_mic_device", + dropdown_menu_values=new_mic_device_list, + ) def updateSelected_MicDevice(self, default_selected_mic_device_name:str): self.view_variable.VAR_MIC_DEVICE.set(default_selected_mic_device_name) @@ -714,9 +720,12 @@ class View(): vrct_gui.config_window.sb__progressbar_x_slider__progressbar_mic_energy_threshold.set(0) - def updateList_SpeakerDevice(self, new_speaker_device_list): + def updateList_SpeakerDevice(self, new_speaker_device_list:list): self.view_variable.LIST_SPEAKER_DEVICE = new_speaker_device_list - # vrct_gui.config_window.sb__optionmenu_speaker_device.configure(values=new_speaker_device_list) + vrct_gui.dropdown_menu_window.updateDropdownMenuValues( + dropdown_menu_widget_id="sb__optionmenu_speaker_device", + dropdown_menu_values=new_speaker_device_list, + ) @staticmethod def updateSetProgressBar_SpeakerEnergy(new_speaker_energy): diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py new file mode 100644 index 00000000..25b10479 --- /dev/null +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -0,0 +1,268 @@ +from types import SimpleNamespace + +from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont, CTkScrollableFrame +from time import sleep + +from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor, getLatestWidth, getLatestHeight +from functools import partial + +class _CreateDropdownMenuWindow(CTkToplevel): + def __init__(self, settings, view_variable): + super().__init__() + self.withdraw() + self.hide = True + + self.title("") + self.overrideredirect(True) + + self.wm_attributes("-alpha", 0) + self.wm_attributes("-toolwindow", True) + + self.resizable(width=False, height=False) + + + self.settings = settings + self.attach_widget = None + self._view_variable = view_variable + self.wrapper_widget = None + + self.dropdown_menu_widgets = {} + self.active_dropdown_menu_widget = None + + + + self.attach_widget_width = None + self.attach_widget_height = None + self.attach_widget_x_pos = None + self.attach_widget_y_pos = None + self.x_pos = None + self.y_pos = None + + + + # self.rowconfigure(0,weight=1) + # self.columnconfigure(0,weight=1) + + # The color code [#bb4448] is a mixture of [#a9555c] and [#cc3333] (for a redder shade). + + def updateDropdownMenuValues(self, dropdown_menu_widget_id, dropdown_menu_values): + self.dropdown_menu_widgets[dropdown_menu_widget_id].widget.destroy() + self.createDropdownMenuBox( + dropdown_menu_widget_id=dropdown_menu_widget_id, + dropdown_menu_values=dropdown_menu_values, + command=self.dropdown_menu_widgets[dropdown_menu_widget_id].command, + wrapper_widget=self.dropdown_menu_widgets[dropdown_menu_widget_id].wrapper_widget, + ) + + + def createDropdownMenuBox(self, dropdown_menu_widget_id, dropdown_menu_values, command, wrapper_widget): + self.wrapper_widget = wrapper_widget + + self.dropdown_menu_container = CTkFrame(self, corner_radius=0, fg_color="#bb4448", width=0, height=0) + self.dropdown_menu_container.grid(row=0, column=0, sticky="nsew") + self.dropdown_menu_container.grid_remove() + + self.dropdown_menu_widgets[dropdown_menu_widget_id] = SimpleNamespace() + + self.dropdown_menu_widgets[dropdown_menu_widget_id] = SimpleNamespace( + widget=self.dropdown_menu_container, + command=command, + wrapper_widget=wrapper_widget, + ) + + + self.scroll_frame_container = CTkScrollableFrame( + self.dropdown_menu_container, + corner_radius=0, + fg_color=self.settings.ctm.SB__DROPDOWN_MENU_WINDOW_BG_COLOR, + width=0, + height=0, + border_color=self.settings.ctm.SB__DROPDOWN_MENU_WINDOW_BORDER_COLOR, + border_width=1, + ) + self.scroll_frame_container.grid(row=0, column=0, sticky="nsew") + self.scroll_frame_container._scrollbar.grid_configure(padx=3) + self.scroll_frame_container.grid_columnconfigure(0, weight=1) + + self.dropdown_menu_values_box = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.settings.ctm.SB__DROPDOWN_MENU_WINDOW_BG_COLOR, width=0, height=0) + self.dropdown_menu_values_box.grid(row=0, column=0, sticky="nsew") + self.dropdown_menu_values_box.grid_columnconfigure(0, weight=1) + + self._createDropdownMenuValues(dropdown_menu_widget_id, dropdown_menu_values, command) + + def _createDropdownMenuValues(self, dropdown_menu_widget_id, dropdown_menu_values, command): + + # self.dropdown_menu_values_wrapper = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color="red", width=0, height=0) + self.dropdown_menu_values_wrapper = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.settings.ctm.SB__DROPDOWN_MENU_WINDOW_BG_COLOR) + self.dropdown_menu_values_wrapper.grid(row=0, column=0, sticky="nsew") + self.dropdown_menu_values_wrapper.grid_columnconfigure(0, weight=1) + + # for get to the height__________________ + __dropdown_menu_value_wrapper = CTkFrame(self.dropdown_menu_values_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__DROPDOWN_MENU_BG_COLOR, width=0, height=0) + __dropdown_menu_value_wrapper.grid(row=0, column=0, ipadx=6, ipady=6, sticky="nsew") + setattr(self, f"{dropdown_menu_widget_id}__{0}", __dropdown_menu_value_wrapper) + + + __dropdown_menu_value_wrapper.grid_rowconfigure((0,2), weight=1) + __dropdown_menu_value_wrapper.grid_columnconfigure(0, weight=1) + __label_widget = CTkLabel( + __dropdown_menu_value_wrapper, + text="Aa", + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=14, weight="normal"), + anchor="w", + text_color=self.settings.ctm.BASIC_TEXT_COLOR, + ) + # setattr(self, f"l", __label_widget) + + __label_widget.grid(row=1, column=0, padx=(8,0), sticky="w") + label_height = getLatestHeight(__dropdown_menu_value_wrapper) + # ______________________________________ + + dropdown_menu_values_length = len(dropdown_menu_values) + if dropdown_menu_values_length <= 3: + self.scroll_frame_container.configure(width=200, height=int(dropdown_menu_values_length * label_height)) + # self.geometry("{}x{}".format(300, int(dropdown_menu_values_length * label_height))) + # self.geometry("{}x{}".format(300, int(dropdown_menu_values_length * label_height))) + # self.scroll_frame_container._parent_canvas.configure(height=20) + else: + self.scroll_frame_container.configure(width=200, height=200) + # self.geometry("{}x{}".format(200, 200)) + # self.scroll_frame_container._parent_canvas.configure(height=20) + + # This is for CustomTkinter's spec change or bug fix. + self.scroll_frame_container._scrollbar.configure(height=0) + + + + row=0 + for dropdown_menu_value in dropdown_menu_values: + + dropdown_menu_value_wrapper = CTkFrame(self.dropdown_menu_values_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__DROPDOWN_MENU_BG_COLOR, width=0, height=0) + dropdown_menu_value_wrapper.grid(row=row, column=0, ipadx=6, ipady=6, sticky="nsew") + setattr(self, f"{dropdown_menu_widget_id}__{row}", dropdown_menu_value_wrapper) + + + + dropdown_menu_value_wrapper.grid_rowconfigure((0,2), weight=1) + dropdown_menu_value_wrapper.grid_columnconfigure(0, weight=1) + label_widget = CTkLabel( + dropdown_menu_value_wrapper, + text=dropdown_menu_value, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=14, weight="normal"), + anchor="w", + text_color=self.settings.ctm.BASIC_TEXT_COLOR, + ) + # setattr(self, f"l", label_widget) + + label_widget.grid(row=1, column=0, padx=(8,0), sticky="w") + + + + bindEnterAndLeaveColor([dropdown_menu_value_wrapper, label_widget], self.settings.ctm.SB__DROPDOWN_MENU_HOVERED_BG_COLOR, self.settings.ctm.SB__DROPDOWN_MENU_BG_COLOR) + bindButtonPressColor([dropdown_menu_value_wrapper, label_widget], self.settings.ctm.SB__DROPDOWN_MENU_CLICKED_BG_COLOR, self.settings.ctm.SB__DROPDOWN_MENU_BG_COLOR) + + + + def optimizedCommand(value, _e): + command(value) + self._withdraw() + + callback = partial(optimizedCommand, dropdown_menu_value) + bindButtonReleaseFunction([dropdown_menu_value_wrapper, label_widget], callback) + + row+=1 + + + + def show(self, dropdown_menu_widget_id, target_widget): + if self.hide is False: return + self.wm_attributes("-alpha", 0) + + + self.attach_widget = target_widget + + if self.active_dropdown_menu_widget is not None: + self.active_dropdown_menu_widget.grid_remove() + + target_Widget = self.dropdown_menu_widgets[dropdown_menu_widget_id].widget + target_Widget.grid() + self.active_dropdown_menu_widget = target_Widget + + self.deiconify() + self._adjustToTargetWidgetGeometry() + self.BIND_CONFIGURE_FUNC_ID = self.attach_widget.winfo_toplevel().bind("", self._adjustToTargetWidgetGeometry, "+") + self.BIND_UNMAP_FUNC_ID = self.attach_widget.bind("", self._withdraw, "+") + + self.BIND_BUTTON_1_FUNC_ID = self.attach_widget.winfo_toplevel().bind("", self._withdraw, "+") + + + self.hide = False + + + + for i in range(0,91,10): + if not self.winfo_exists(): + break + self.attributes("-alpha", i/100) + self.update() + sleep(1/100) + self.wm_attributes("-alpha", 1) + self.update() + + + + def _withdraw(self, e=None): + self.withdraw() + self.attach_widget.winfo_toplevel().unbind("", self.BIND_CONFIGURE_FUNC_ID) + self.attach_widget.unbind("", self.BIND_UNMAP_FUNC_ID) + self.attach_widget.winfo_toplevel().unbind("", self.BIND_BUTTON_1_FUNC_ID) + self.hide = True + + + def _adjustToTargetWidgetGeometry(self, e=None): + if not self.attach_widget.winfo_exists(): + return + self.attach_widget.update_idletasks() + + + + self.update() + if self.attach_widget_x_pos == self.attach_widget.winfo_rootx() and self.attach_widget_y_pos == self.attach_widget.winfo_rooty(): + self.lift() + return + + self.wrapper_widget_y_pos = self.wrapper_widget.winfo_rooty() + self.wrapper_widget_bottom_y_pos = self.wrapper_widget_y_pos + self.wrapper_widget.winfo_height() + + self.attach_widget_width = self.attach_widget.winfo_width() + self.attach_widget_height = self.attach_widget.winfo_height() + self.attach_widget_x_pos = self.attach_widget.winfo_rootx() + self.attach_widget_y_pos = self.attach_widget.winfo_rooty() + + + self.y_pos = int(self.attach_widget_y_pos + self.attach_widget_height + 4) + + if self.wrapper_widget_y_pos > self.y_pos or self.y_pos > self.wrapper_widget_bottom_y_pos: + self.hideTemporarily() + else: + if self.winfo_exists(): + self.deiconify() + + + if self.winfo_width() >= self.attach_widget_width: + self.x_pos = int(self.attach_widget_x_pos - (self.winfo_width() - self.attach_widget_width)) + else: + self.x_pos = self.attach_widget_x_pos + + self.geometry("+{}+{}".format(self.x_pos, self.y_pos)) + + self.lift() + + def hideTemporarily(self): + self.withdraw() + + diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index fc53e23f..014761d3 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -4,6 +4,7 @@ from typing import Union from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar from vrct_gui.ui_utils import createButtonWithImage, getLatestWidth, createOptionMenuBox +from vrct_gui import vrct_gui SETTING_BOX_COLUMN = 1 @@ -14,6 +15,14 @@ class _SettingBoxGenerator(): self.parent_widget = parent_widget self.settings = settings + self.dropdown_menu_window = vrct_gui.vrct_gui.dropdown_menu_window + + # self.dropdown_menu_window = _CreateDropdownMenuWindow( + # settings=self.settings, + # view_variable=self.view_variable, + # wrapper_widget=self.config_window.main_bg_container, + # ) + def _createSettingBoxFrame(self, for_var_label_text, for_var_desc_text): setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) @@ -76,6 +85,17 @@ class _SettingBoxGenerator(): def createSettingBoxDropdownMenu(self, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, variable=None, dropdown_menu_values=None): (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) + def adjustedCommand(value): + variable.set(value) + command(value) + + self.dropdown_menu_window.createDropdownMenuBox( + dropdown_menu_widget_id=optionmenu_attr_name, + dropdown_menu_values=dropdown_menu_values, + command=adjustedCommand, + wrapper_widget=self.config_window.main_bg_container, + ) + option_menu_widget = createOptionMenuBox( parent_widget=setting_box_item_frame, optionmenu_bg_color=self.settings.ctm.SB__OPTIONMENU_BG_COLOR, @@ -92,12 +112,10 @@ class _SettingBoxGenerator(): text_color=self.settings.ctm.LABELS_TEXT_COLOR, image_file=self.settings.image_file.ARROW_LEFT.rotate(90), image_size=(14,14), - command=lambda _e: print(_e), - # command=open_selectable_language_window_command, - - # optionmenu_position="center", - # setattr_widget=main_window, - # image_widget_attr_name=arrow_img_attr_name, + optionmenu_clicked_command=lambda _e: self.dropdown_menu_window.show( + dropdown_menu_widget_id=optionmenu_attr_name, + target_widget=option_menu_widget, + ), ) option_menu_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 2ebf4d04..0f22ffae 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -93,7 +93,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): text_color=settings.ctm.LABELS_TEXT_COLOR, image_file=settings.image_file.ARROW_LEFT.rotate(180), image_size=(20,20), - command=open_selectable_language_window_command, + optionmenu_clicked_command=open_selectable_language_window_command, optionmenu_position="center", setattr_widget=main_window, diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 124b2a01..8aed18f6 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -41,6 +41,7 @@ class ColorThemeManager(): self.DARK_450_COLOR = "#b8b9bd" self.DARK_500_COLOR = "#a9aaae" self.DARK_600_COLOR = "#7f8084" + # self.DARK_650_COLOR = "#75767a" self.DARK_700_COLOR = "#6a6c6f" self.DARK_725_COLOR = "#636467" self.DARK_750_COLOR = "#5b5c5f" @@ -233,6 +234,12 @@ class ColorThemeManager(): self.config_window.SB__OPTIONMENU_BG_COLOR = self.DARK_925_COLOR self.config_window.SB__OPTIONMENU_HOVERED_BG_COLOR = self.DARK_850_COLOR self.config_window.SB__OPTIONMENU_CLICKED_BG_COLOR = self.DARK_950_COLOR + self.config_window.SB__DROPDOWN_MENU_WINDOW_BG_COLOR = self.config_window.MAIN_BG_COLOR + self.config_window.SB__DROPDOWN_MENU_WINDOW_BORDER_COLOR = self.DARK_600_COLOR + # self.config_window.SB__DROPDOWN_MENU_WINDOW_BG_COLOR = self.DARK_700_COLOR + self.config_window.SB__DROPDOWN_MENU_BG_COLOR = self.DARK_875_COLOR + self.config_window.SB__DROPDOWN_MENU_HOVERED_BG_COLOR = self.DARK_800_COLOR + self.config_window.SB__DROPDOWN_MENU_CLICKED_BG_COLOR = self.DARK_900_COLOR self.config_window.SB__SLIDER_BUTTON_COLOR = self.DARK_700_COLOR self.config_window.SB__SLIDER_BUTTON_HOVERED_COLOR = self.DARK_600_COLOR diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index 254c6c41..da1d1648 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -142,7 +142,7 @@ def createButtonWithImage(parent_widget, button_fg_color, button_enter_color, bu return button_wrapper -def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_bg_color, optionmenu_clicked_bg_color, optionmenu_ipadx, optionmenu_ipady, variable, font_family, font_size, text_color, image_file, image_size, command, optionmenu_position=None, optionmenu_ipady_between_img=0, optionmenu_min_height=None, optionmenu_min_width=None, setattr_widget=None, image_widget_attr_name=None): +def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_bg_color, optionmenu_clicked_bg_color, optionmenu_ipadx, optionmenu_ipady, variable, font_family, font_size, text_color, image_file, image_size, optionmenu_clicked_command, optionmenu_position=None, optionmenu_ipady_between_img=0, optionmenu_min_height=None, optionmenu_min_width=None, setattr_widget=None, image_widget_attr_name=None): option_menu_box = CTkFrame(parent_widget, corner_radius=6, fg_color=optionmenu_bg_color, cursor="hand2") @@ -189,6 +189,6 @@ def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_b - bindButtonReleaseFunction([optionmenu_label_wrapper, option_menu_box, optionmenu_label_widget, optionmenu_img_widget], command) + bindButtonReleaseFunction([optionmenu_label_wrapper, option_menu_box, optionmenu_label_widget, optionmenu_img_widget], optionmenu_clicked_command) return option_menu_box \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index bd167560..9f722671 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -6,6 +6,7 @@ from ._CreateSelectableLanguagesWindow import _CreateSelectableLanguagesWindow from ._CreateModalWindow import _CreateModalWindow from ._CreateErrorWindow import _CreateErrorWindow +from ._CreateDropdownMenuWindow import _CreateDropdownMenuWindow from ._changeMainWindowWidgetsStatus import _changeMainWindowWidgetsStatus from ._changeConfigWindowWidgetsStatus import _changeConfigWindowWidgetsStatus from ._printToTextbox import _printToTextbox @@ -21,6 +22,7 @@ class VRCT_GUI(CTk): super().__init__() self.adjusted_event=None self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID=None + self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID=None def createGUI(self, settings, view_variable): @@ -33,6 +35,11 @@ class VRCT_GUI(CTk): view_variable=self._view_variable ) + self.dropdown_menu_window = _CreateDropdownMenuWindow( + settings=self.settings.config_window, + view_variable=self._view_variable, + ) + self.config_window = ConfigWindow( vrct_gui=self, settings=self.settings.config_window, @@ -74,20 +81,20 @@ class VRCT_GUI(CTk): self.adjustToMainWindowGeometry() self.modal_window.deiconify() - self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID = self.bind("", self.adjustToMainWindowGeometry) + self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID = self.bind("", self.adjustToMainWindowGeometry, "+") + self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID = self.modal_window.bind("", lambda _e: self.config_window.lift(), "+") self.config_window.deiconify() self.config_window.focus_set() - self.config_window.grab_set() def closeConfigWindow(self): callFunctionIfCallable(self._view_variable.CALLBACK_CLOSE_CONFIG_WINDOW) self.config_window.withdraw() - self.config_window.grab_release() self.modal_window.withdraw() self.unbind("", self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID) + self.modal_window.unbind("", self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID) self.adjusted_event=None @@ -200,6 +207,10 @@ class VRCT_GUI(CTk): self.after(150, lambda: self.config_window.lift()) elif self.adjusted_event is None: self.after(150, lambda: self.config_window.lift()) + else: + pass + + self.config_window.focus_set() if e is not None: self.adjusted_event=str(e) From b47738b424f561fd4ef435b5e65260ff01ca4b99 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 8 Oct 2023 12:36:17 +0900 Subject: [PATCH 222/355] [bugfix] Config Window: Dropdown Menu Window. Set the maximum height to be 8 times the label's height, and adjust it if the value's length is less than 8. --- vrct_gui/_CreateDropdownMenuWindow.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index 25b10479..32dbf738 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -43,8 +43,6 @@ class _CreateDropdownMenuWindow(CTkToplevel): # self.rowconfigure(0,weight=1) # self.columnconfigure(0,weight=1) - # The color code [#bb4448] is a mixture of [#a9555c] and [#cc3333] (for a redder shade). - def updateDropdownMenuValues(self, dropdown_menu_widget_id, dropdown_menu_values): self.dropdown_menu_widgets[dropdown_menu_widget_id].widget.destroy() self.createDropdownMenuBox( @@ -121,15 +119,16 @@ class _CreateDropdownMenuWindow(CTkToplevel): # ______________________________________ dropdown_menu_values_length = len(dropdown_menu_values) - if dropdown_menu_values_length <= 3: - self.scroll_frame_container.configure(width=200, height=int(dropdown_menu_values_length * label_height)) - # self.geometry("{}x{}".format(300, int(dropdown_menu_values_length * label_height))) - # self.geometry("{}x{}".format(300, int(dropdown_menu_values_length * label_height))) - # self.scroll_frame_container._parent_canvas.configure(height=20) + new_height = 200 + new_width = 200 + max_display_length = 8 + if dropdown_menu_values_length < max_display_length: + new_height = int(dropdown_menu_values_length * label_height) + # new_width = 200 else: - self.scroll_frame_container.configure(width=200, height=200) - # self.geometry("{}x{}".format(200, 200)) - # self.scroll_frame_container._parent_canvas.configure(height=20) + new_height = int(max_display_length * label_height) + + self.scroll_frame_container.configure(width=new_width, height=new_height) # This is for CustomTkinter's spec change or bug fix. self.scroll_frame_container._scrollbar.configure(height=0) From 39472e64c4f5c28d7d29bc3e7e8c4d54f7b9c130 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 8 Oct 2023 14:15:20 +0900 Subject: [PATCH 223/355] =?UTF-8?q?[Refactor]=20UiScalingManager:=20?= =?UTF-8?q?=E5=A4=89=E6=95=B0=E5=90=8D=E5=A4=89=E6=9B=B4=E3=80=81=E6=B1=8E?= =?UTF-8?q?=E7=94=A8=E5=8C=96=E3=80=82Entry=20widget=E4=BB=A5=E5=A4=96?= =?UTF-8?q?=E3=81=A7=E3=82=82=E4=BD=BF=E3=81=84=E3=81=9F=E3=81=8F=E3=81=AA?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E3=81=AE=E3=81=A7=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../createSettingBox_AdvancedSettings.py | 4 ++-- .../setting_box_others/createSettingBox_Others.py | 2 +- .../setting_box_transcription/createSettingBox_Mic.py | 8 ++++---- .../createSettingBox_Speaker.py | 6 +++--- .../createSettingBox_Translation.py | 2 +- vrct_gui/ui_managers/UiScalingManager.py | 11 ++++++----- 6 files changed, 17 insertions(+), 16 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py index 6dcdca4e..fb7a679f 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py @@ -18,7 +18,7 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin for_var_label_text=view_variable.VAR_LABEL_OSC_IP_ADDRESS, for_var_desc_text=view_variable.VAR_DESC_OSC_IP_ADDRESS, entry_attr_name="sb__entry_ip_address", - entry_width=settings.uism.SB__ENTRY_WIDTH_150, + entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_150, entry_bind__Any_KeyRelease=lambda value: entry_ip_address_callback(value), entry_textvariable=view_variable.VAR_OSC_IP_ADDRESS, ) @@ -30,7 +30,7 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin for_var_label_text=view_variable.VAR_LABEL_OSC_PORT, for_var_desc_text=view_variable.VAR_DESC_OSC_PORT, entry_attr_name="sb__entry_port", - entry_width=settings.uism.SB__ENTRY_WIDTH_150, + entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_150, entry_bind__Any_KeyRelease=lambda value: entry_port_callback(value), entry_textvariable=view_variable.VAR_OSC_PORT, ) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index d6be70b4..2953809e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -66,7 +66,7 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v for_var_label_text=view_variable.VAR_LABEL_MESSAGE_FORMAT, for_var_desc_text=view_variable.VAR_DESC_MESSAGE_FORMAT, entry_attr_name="sb__entry_message_format", - entry_width=settings.uism.SB__ENTRY_WIDTH_250, + entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_250, entry_bind__Any_KeyRelease=lambda value: entry_message_format_callback(value), entry_textvariable=view_variable.VAR_MESSAGE_FORMAT, ) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index a02bc583..3842e392 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -107,7 +107,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_label_text=view_variable.VAR_LABEL_MIC_RECORD_TIMEOUT, for_var_desc_text=view_variable.VAR_DESC_MIC_RECORD_TIMEOUT, entry_attr_name="sb__entry_mic_record_timeout", - entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_100, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_record_timeout_callback(value), entry_textvariable=view_variable.VAR_MIC_RECORD_TIMEOUT, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_MIC_RECORD_TIMEOUT, @@ -119,7 +119,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_label_text=view_variable.VAR_LABEL_MIC_PHRASE_TIMEOUT, for_var_desc_text=view_variable.VAR_DESC_MIC_PHRASE_TIMEOUT, entry_attr_name="sb__entry_mic_phrase_timeout", - entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_100, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_phrase_timeout_callback(value), entry_textvariable=view_variable.VAR_MIC_PHRASE_TIMEOUT, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_MIC_PHRASE_TIMEOUT, @@ -131,7 +131,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_label_text=view_variable.VAR_LABEL_MIC_MAX_PHRASES, for_var_desc_text=view_variable.VAR_DESC_MIC_MAX_PHRASES, entry_attr_name="sb__entry_mic_max_phrases", - entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_100, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_max_phrases_callback(value), entry_textvariable=view_variable.VAR_MIC_MAX_PHRASES, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_MIC_MAX_PHRASES, @@ -145,7 +145,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_label_text=view_variable.VAR_LABEL_MIC_WORD_FILTER, for_var_desc_text=view_variable.VAR_DESC_MIC_WORD_FILTER, entry_attr_name="sb__entry_mic_word_filter", - entry_width=settings.uism.SB__ENTRY_WIDTH_300, + entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_300, entry_bind__Any_KeyRelease=lambda value: entry_input_mic_word_filters_callback(value), entry_textvariable=view_variable.VAR_MIC_WORD_FILTER, ) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 5ebae1b5..90a382a9 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -90,7 +90,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ for_var_label_text=view_variable.VAR_LABEL_SPEAKER_RECORD_TIMEOUT, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_RECORD_TIMEOUT, entry_attr_name="sb__entry_speaker_record_timeout", - entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_100, entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_record_timeout_callback(value), entry_textvariable=view_variable.VAR_SPEAKER_RECORD_TIMEOUT, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_SPEAKER_RECORD_TIMEOUT, @@ -102,7 +102,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ for_var_label_text=view_variable.VAR_LABEL_SPEAKER_PHRASE_TIMEOUT, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_PHRASE_TIMEOUT, entry_attr_name="sb__entry_speaker_phrase_timeout", - entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_100, entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_phrase_timeout_callback(value), entry_textvariable=view_variable.VAR_SPEAKER_PHRASE_TIMEOUT, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_SPEAKER_PHRASE_TIMEOUT, @@ -114,7 +114,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ for_var_label_text=view_variable.VAR_LABEL_SPEAKER_MAX_PHRASES, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_MAX_PHRASES, entry_attr_name="sb__entry_speaker_max_phrases", - entry_width=settings.uism.SB__ENTRY_WIDTH_100, + entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_100, entry_bind__Any_KeyRelease=lambda value: entry_input_speaker_max_phrases_callback(value), entry_textvariable=view_variable.VAR_SPEAKER_MAX_PHRASES, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_SPEAKER_MAX_PHRASES, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index 343f309d..014cee3e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -16,7 +16,7 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings, v for_var_label_text=view_variable.VAR_LABEL_DEEPL_AUTH_KEY, for_var_desc_text=view_variable.VAR_DESC_DEEPL_AUTH_KEY, entry_attr_name="sb__entry_deepl_authkey", - entry_width=settings.uism.SB__ENTRY_WIDTH_300, + entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_300, entry_bind__Any_KeyRelease=lambda value: deepl_authkey_callback(value), entry_textvariable=view_variable.VAR_DEEPL_AUTH_KEY, ) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index bf16839f..78f892bb 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -15,6 +15,11 @@ class UiScalingManager(): def _calculatedUiSizes(self): # Common + # RESPONSIVE_UI_SIZE_INT_10 ... RESPONSIVE_UI_SIZE_INT_300 + for i in range(10, 301, 10): + setattr(self.main, f"RESPONSIVE_UI_SIZE_INT_{i}", self._calculateUiSize(i)) + setattr(self.config_window, f"RESPONSIVE_UI_SIZE_INT_{i}", self._calculateUiSize(i)) + # Main self.main.TEXTBOX_PADX = self._calculateUiSize(16) @@ -166,12 +171,8 @@ class UiScalingManager(): self.config_window.SB__ENTRY_FONT_SIZE = self.config_window.SB__SELECTOR_FONT_SIZE self.config_window.SB__ENTRY_HEIGHT = self._calculateUiSize(30) - # SB__ENTRY_WIDTH_10 ... SB__ENTRY_WIDTH_200 - for i in range(10, 301, 10): - setattr(self.config_window, f'SB__ENTRY_WIDTH_{i}', self._calculateUiSize(i)) - - self.config_window.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH = self.config_window.SB__ENTRY_WIDTH_50 + self.config_window.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH = self.config_window.RESPONSIVE_UI_SIZE_INT_50 self.config_window.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT = self.config_window.SB__ENTRY_HEIGHT self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_HEIGHT = self._calculateUiSize(40) self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_LENGTH = self._calculateUiSize(2) From 95758919bd76b21650f0737f23ae78909365acf1 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 8 Oct 2023 20:03:55 +0900 Subject: [PATCH 224/355] =?UTF-8?q?[Update]=20Config=20Window:=20Dropdown?= =?UTF-8?q?=20Menu=20Window.=20Width=E6=8C=87=E5=AE=9A=E3=80=82=E3=83=95?= =?UTF-8?q?=E3=82=A9=E3=83=B3=E3=83=88=E3=82=84=E3=83=87=E3=83=90=E3=82=A4?= =?UTF-8?q?=E3=82=B9=E3=81=AA=E3=81=A9=E3=83=86=E3=82=AD=E3=82=B9=E3=83=88?= =?UTF-8?q?=E3=81=8C=E9=95=B7=E3=81=8F=E3=81=AA=E3=82=8B=E3=82=82=E3=81=AE?= =?UTF-8?q?=E3=81=AF=E5=B9=85=E3=82=92=E5=BA=83=E3=82=81=E3=81=AB=E3=80=82?= =?UTF-8?q?(=E5=8F=AF=E5=A4=89=E3=81=AF=E9=9B=A3=E3=81=97=E3=81=84?= =?UTF-8?q?=E3=81=AE=E3=81=A7=E4=BB=8A=E3=81=AF=E6=8C=87=E5=AE=9A=E5=9E=8B?= =?UTF-8?q?=E3=81=AB)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateDropdownMenuWindow.py | 34 ++++++++++++++----- .../_SettingBoxGenerator.py | 3 +- .../createSettingBox_Appearance.py | 1 + .../createSettingBox_Mic.py | 1 + .../createSettingBox_Speaker.py | 1 + 5 files changed, 31 insertions(+), 9 deletions(-) diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index 32dbf738..ccbfda32 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -38,6 +38,14 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.x_pos = None self.y_pos = None + self.init_height = 200 + self.new_height = self.init_height + self.init_width = 200 + self.new_width = self.init_width + + self.init_max_display_length = 8 + self.max_display_length = self.init_max_display_length + # self.rowconfigure(0,weight=1) @@ -50,10 +58,19 @@ class _CreateDropdownMenuWindow(CTkToplevel): dropdown_menu_values=dropdown_menu_values, command=self.dropdown_menu_widgets[dropdown_menu_widget_id].command, wrapper_widget=self.dropdown_menu_widgets[dropdown_menu_widget_id].wrapper_widget, + + dropdown_menu_width=self.dropdown_menu_widgets[dropdown_menu_widget_id].dropdown_menu_settings.dropdown_menu_width, + dropdown_menu_height=self.dropdown_menu_widgets[dropdown_menu_widget_id].dropdown_menu_settings.dropdown_menu_height, + max_display_length=self.dropdown_menu_widgets[dropdown_menu_widget_id].dropdown_menu_settings.max_display_length, ) - def createDropdownMenuBox(self, dropdown_menu_widget_id, dropdown_menu_values, command, wrapper_widget): + def createDropdownMenuBox(self, dropdown_menu_widget_id, dropdown_menu_values, command, wrapper_widget, dropdown_menu_width=None, dropdown_menu_height=None, max_display_length=None): + self.new_width = dropdown_menu_width if dropdown_menu_width is not None else self.init_width + self.new_height = dropdown_menu_height if dropdown_menu_height is not None else self.init_height + self.max_display_length = max_display_length if max_display_length is not None else self.init_max_display_length + + self.wrapper_widget = wrapper_widget self.dropdown_menu_container = CTkFrame(self, corner_radius=0, fg_color="#bb4448", width=0, height=0) @@ -66,6 +83,11 @@ class _CreateDropdownMenuWindow(CTkToplevel): widget=self.dropdown_menu_container, command=command, wrapper_widget=wrapper_widget, + dropdown_menu_settings=SimpleNamespace( + dropdown_menu_width=dropdown_menu_width, + dropdown_menu_height=dropdown_menu_height, + max_display_length=max_display_length, + ) ) @@ -119,16 +141,12 @@ class _CreateDropdownMenuWindow(CTkToplevel): # ______________________________________ dropdown_menu_values_length = len(dropdown_menu_values) - new_height = 200 - new_width = 200 - max_display_length = 8 - if dropdown_menu_values_length < max_display_length: + if dropdown_menu_values_length < self.max_display_length: new_height = int(dropdown_menu_values_length * label_height) - # new_width = 200 else: - new_height = int(max_display_length * label_height) + new_height = int(self.max_display_length * label_height) - self.scroll_frame_container.configure(width=new_width, height=new_height) + self.scroll_frame_container.configure(width=self.new_width, height=new_height) # This is for CustomTkinter's spec change or bug fix. self.scroll_frame_container._scrollbar.configure(height=0) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 014761d3..4d7798ea 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -82,7 +82,7 @@ class _SettingBoxGenerator(): self.config_window.additional_widgets.append(setting_box_desc) - def createSettingBoxDropdownMenu(self, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, variable=None, dropdown_menu_values=None): + def createSettingBoxDropdownMenu(self, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, dropdown_menu_width=None, variable=None, dropdown_menu_values=None): (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) def adjustedCommand(value): @@ -94,6 +94,7 @@ class _SettingBoxGenerator(): dropdown_menu_values=dropdown_menu_values, command=adjustedCommand, wrapper_widget=self.config_window.main_bg_container, + dropdown_menu_width=dropdown_menu_width, ) option_menu_widget = createOptionMenuBox( diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index 7109496e..89c17d81 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -68,6 +68,7 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi for_var_desc_text=view_variable.VAR_DESC_FONT_FAMILY, optionmenu_attr_name="sb__optionmenu_font_family", dropdown_menu_values=view_variable.LIST_FONT_FAMILY, + dropdown_menu_width=settings.uism.RESPONSIVE_UI_SIZE_INT_300, command=lambda value: optionmenu_font_family_callback(value), variable=view_variable.VAR_FONT_FAMILY, ) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 3842e392..ba06c763 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -58,6 +58,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_desc_text=view_variable.VAR_DESC_MIC_DEVICE, optionmenu_attr_name="sb__optionmenu_mic_device", dropdown_menu_values=view_variable.LIST_MIC_DEVICE, + dropdown_menu_width=settings.uism.RESPONSIVE_UI_SIZE_INT_300, command=lambda value: optionmenu_input_mic_device_callback(value), variable=view_variable.VAR_MIC_DEVICE, ) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 90a382a9..06571644 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -41,6 +41,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ for_var_desc_text=view_variable.VAR_DESC_SPEAKER_DEVICE, optionmenu_attr_name="sb__optionmenu_speaker_device", dropdown_menu_values=view_variable.LIST_SPEAKER_DEVICE, + dropdown_menu_width=settings.uism.RESPONSIVE_UI_SIZE_INT_300, command=lambda value: optionmenu_input_speaker_device_callback(value), variable=view_variable.VAR_SPEAKER_DEVICE, ) From e80ba886d76989dc8b5df5212005ed98cbd18bdb Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 8 Oct 2023 20:08:56 +0900 Subject: [PATCH 225/355] =?UTF-8?q?[Chore]=20Config=20Window:=20Setting=20?= =?UTF-8?q?Box.=20=E3=83=9B=E3=83=90=E3=83=BC=E6=99=82=E3=81=AEOption=20Me?= =?UTF-8?q?nu=E3=81=AE=E8=89=B2=E3=81=8C=E6=98=8E=E3=82=8B=E3=81=99?= =?UTF-8?q?=E3=81=8E=E3=82=8B=E6=B0=97=E3=81=8C=E3=81=97=E3=81=9F=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E5=B0=91=E3=81=97=E3=81=A0=E3=81=91=E6=9A=97=E3=81=8F?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/ui_managers/ColorThemeManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 8aed18f6..18c73d0b 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -389,7 +389,7 @@ class ColorThemeManager(): self.config_window.SB__BG_COLOR = self.DARK_888_COLOR self.config_window.SB__OPTIONMENU_BG_COLOR = self.DARK_925_COLOR - self.config_window.SB__OPTIONMENU_HOVERED_BG_COLOR = self.DARK_850_COLOR + self.config_window.SB__OPTIONMENU_HOVERED_BG_COLOR = self.DARK_875_COLOR self.config_window.SB__SLIDER_BUTTON_COLOR = self.DARK_700_COLOR self.config_window.SB__SLIDER_BUTTON_HOVERED_COLOR = self.DARK_600_COLOR From 1e220281ba5839e426cc3e2aae608264e6bec8b0 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 8 Oct 2023 20:24:32 +0900 Subject: [PATCH 226/355] [Update] Dropdown Window and Selectable Language WIndow: add cursor=hand2. and the color when it is hovered and clicked. [Chore] remove the CTkFrame widget that was meaningless. --- vrct_gui/_CreateDropdownMenuWindow.py | 2 +- vrct_gui/_CreateSelectableLanguagesWindow.py | 17 +++++++++-------- vrct_gui/ui_managers/ColorThemeManager.py | 4 +++- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index ccbfda32..73342d5f 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -156,7 +156,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): row=0 for dropdown_menu_value in dropdown_menu_values: - dropdown_menu_value_wrapper = CTkFrame(self.dropdown_menu_values_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__DROPDOWN_MENU_BG_COLOR, width=0, height=0) + dropdown_menu_value_wrapper = CTkFrame(self.dropdown_menu_values_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__DROPDOWN_MENU_BG_COLOR, width=0, height=0, cursor="hand2") dropdown_menu_value_wrapper.grid(row=row, column=0, ipadx=6, ipady=6, sticky="nsew") setattr(self, f"{dropdown_menu_widget_id}__{row}", dropdown_menu_value_wrapper) diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index 5892f7d1..44b48555 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -80,15 +80,11 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.top_container.rowconfigure((0,2), weight=1) self.top_container.columnconfigure(1, weight=1) - self.go_back_button_container = CTkFrame(self.top_container, corner_radius=0, fg_color=self.settings.ctm.GO_BACK_BUTTON_BG_COLOR, width=0, height=0) + self.go_back_button_container = CTkFrame(self.top_container, corner_radius=0, fg_color=self.settings.ctm.GO_BACK_BUTTON_BG_COLOR, width=0, height=0, cursor="hand2") self.go_back_button_container.grid(row=1, column=0) - self.go_back_button_label_wrapper = CTkFrame(self.go_back_button_container, corner_radius=0, fg_color=self.settings.ctm.GO_BACK_BUTTON_BG_COLOR, width=0, height=0) - self.go_back_button_label_wrapper.grid(row=0, column=0) - - self.go_back_button_label = CTkLabel( - self.go_back_button_label_wrapper, + self.go_back_button_container, textvariable=self._view_variable.VAR_GO_BACK_LABEL_SELECTABLE_LANGUAGE, height=0, corner_radius=0, @@ -98,7 +94,12 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): ) self.go_back_button_label.grid(row=0, column=0, padx=10, pady=8) - bindButtonReleaseFunction([self.go_back_button_label_wrapper, self.go_back_button_label], lambda _e: self.vrct_gui.closeSelectableLanguagesWindow()) + + bindEnterAndLeaveColor([self.go_back_button_container, self.go_back_button_label], self.settings.ctm.GO_BACK_BUTTON_BG_HOVERED_COLOR, self.settings.ctm.GO_BACK_BUTTON_BG_COLOR) + bindButtonPressColor([self.go_back_button_container, self.go_back_button_label], self.settings.ctm.GO_BACK_BUTTON_BG_CLICKED_COLOR, self.settings.ctm.GO_BACK_BUTTON_BG_COLOR) + + + bindButtonReleaseFunction([self.go_back_button_container, self.go_back_button_label], lambda _e: self.vrct_gui.closeSelectableLanguagesWindow()) @@ -137,7 +138,7 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): column=0 for selectable_language_name in self._view_variable.LIST_SELECTABLE_LANGUAGES: - self.wrapper = CTkFrame(self.container, corner_radius=0, fg_color=self.settings.ctm.LANGUAGE_BUTTON_BG_COLOR, width=0, height=0) + self.wrapper = CTkFrame(self.container, corner_radius=0, fg_color=self.settings.ctm.LANGUAGE_BUTTON_BG_COLOR, width=0, height=0, cursor="hand2") self.wrapper.grid(row=row, column=column, ipadx=6, ipady=6, sticky="nsew") setattr(self, f"{row}_{column}", self.wrapper) diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 18c73d0b..50ed0b17 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -199,12 +199,14 @@ class ColorThemeManager(): self.selectable_language_window.MAIN_BG_COLOR = self.DARK_875_COLOR - self.selectable_language_window.LANGUAGE_BUTTON_BG_COLOR = self.selectable_language_window.MAIN_BG_COLOR self.selectable_language_window.GO_BACK_BUTTON_BG_COLOR = self.DARK_800_COLOR + self.selectable_language_window.GO_BACK_BUTTON_BG_HOVERED_COLOR = self.DARK_750_COLOR + self.selectable_language_window.GO_BACK_BUTTON_BG_CLICKED_COLOR = self.DARK_875_COLOR self.selectable_language_window.TOP_BG_COLOR = self.main.SIDEBAR_BG_COLOR self.selectable_language_window.TITLE_TEXT_COLOR = self.DARK_400_COLOR + self.selectable_language_window.LANGUAGE_BUTTON_BG_COLOR = self.selectable_language_window.MAIN_BG_COLOR self.selectable_language_window.LANGUAGE_BUTTON_BG_HOVERED_COLOR = self.DARK_825_COLOR self.selectable_language_window.LANGUAGE_BUTTON_BG_CLICKED_COLOR = self.DARK_888_COLOR From 9fce7f6a94ee282fbd56a9be6d74c964c5a64f5c Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 8 Oct 2023 20:49:17 +0900 Subject: [PATCH 227/355] =?UTF-8?q?[bugfix]=20Config=20Window:=20Dropdown?= =?UTF-8?q?=20Menu=20Window.=20=E3=82=B9=E3=82=AF=E3=83=AD=E3=83=BC?= =?UTF-8?q?=E3=83=AB=E3=83=90=E3=83=BC=E5=B9=85=E3=81=8C=E5=B7=A6=E5=8F=B3?= =?UTF-8?q?=E3=81=A7=E9=81=95=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82=20=E5=90=84Window=E3=81=AB?= =?UTF-8?q?1px=E3=83=90=E3=82=B0=E6=A4=9C=E5=87=BA=E7=94=A8=E8=89=B2?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E3=81=A8=E8=AA=BF=E6=95=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateDropdownMenuWindow.py | 6 +++--- vrct_gui/_CreateErrorWindow.py | 2 ++ vrct_gui/_CreateModalWindow.py | 2 +- vrct_gui/_CreateSelectableLanguagesWindow.py | 3 +-- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index 73342d5f..e07a89a5 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -18,6 +18,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.wm_attributes("-alpha", 0) self.wm_attributes("-toolwindow", True) + self.configure(fg_color="#ff7f50") self.resizable(width=False, height=False) @@ -73,7 +74,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.wrapper_widget = wrapper_widget - self.dropdown_menu_container = CTkFrame(self, corner_radius=0, fg_color="#bb4448", width=0, height=0) + self.dropdown_menu_container = CTkFrame(self, corner_radius=0, fg_color="#ff7f50", width=0, height=0) self.dropdown_menu_container.grid(row=0, column=0, sticky="nsew") self.dropdown_menu_container.grid_remove() @@ -101,7 +102,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): border_width=1, ) self.scroll_frame_container.grid(row=0, column=0, sticky="nsew") - self.scroll_frame_container._scrollbar.grid_configure(padx=3) + self.scroll_frame_container._scrollbar.grid_configure(padx=(1, 2)) self.scroll_frame_container.grid_columnconfigure(0, weight=1) self.dropdown_menu_values_box = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.settings.ctm.SB__DROPDOWN_MENU_WINDOW_BG_COLOR, width=0, height=0) @@ -112,7 +113,6 @@ class _CreateDropdownMenuWindow(CTkToplevel): def _createDropdownMenuValues(self, dropdown_menu_widget_id, dropdown_menu_values, command): - # self.dropdown_menu_values_wrapper = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color="red", width=0, height=0) self.dropdown_menu_values_wrapper = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.settings.ctm.SB__DROPDOWN_MENU_WINDOW_BG_COLOR) self.dropdown_menu_values_wrapper.grid(row=0, column=0, sticky="nsew") self.dropdown_menu_values_wrapper.grid_columnconfigure(0, weight=1) diff --git a/vrct_gui/_CreateErrorWindow.py b/vrct_gui/_CreateErrorWindow.py index 24a33c27..8c576a1a 100644 --- a/vrct_gui/_CreateErrorWindow.py +++ b/vrct_gui/_CreateErrorWindow.py @@ -13,6 +13,8 @@ class _CreateErrorWindow(CTkToplevel): self.wm_attributes("-alpha", 0) self.wm_attributes("-toolwindow", True) + self.configure(fg_color="#fff") + self.settings = settings self.attach_widget = None self._view_variable = view_variable diff --git a/vrct_gui/_CreateModalWindow.py b/vrct_gui/_CreateModalWindow.py index b54bad76..8aa6cb64 100644 --- a/vrct_gui/_CreateModalWindow.py +++ b/vrct_gui/_CreateModalWindow.py @@ -15,7 +15,7 @@ class _CreateModalWindow(CTkToplevel): self.attach_window = attach_window - self.configure(fg_color="black") + self.configure(fg_color="#ff7f50") self.protocol("WM_DELETE_WINDOW", lambda e: self.withdraw()) self.settings = settings diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index 44b48555..dbac7fc9 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -18,8 +18,7 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.attach = vrct_gui.main_bg_container self.vrct_gui = vrct_gui - - self.configure(fg_color="black") + self.configure(fg_color="#ff7f50") self.protocol("WM_DELETE_WINDOW", vrct_gui.closeSelectableLanguagesWindow) self.settings = settings From c87c3fa4da52c64723f06c68fc9da1a5dda7e730 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 8 Oct 2023 20:58:10 +0900 Subject: [PATCH 228/355] =?UTF-8?q?[Chore]=20Config=20Window:=20=E5=90=84?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E9=A0=85=E7=9B=AE=E3=81=AE=E4=B8=80=E7=95=AA?= =?UTF-8?q?=E6=9C=80=E5=BE=8C=E3=81=AE=E8=A6=81=E7=B4=A0=20border=20bottom?= =?UTF-8?q?=E6=B6=88=E3=81=97=E3=80=82=20=E9=A0=85=E7=9B=AE=E3=81=AE?= =?UTF-8?q?=E7=A7=BB=E5=8B=95=E3=82=84=E8=BF=BD=E5=8A=A0=E3=81=AA=E3=81=A9?= =?UTF-8?q?=E3=81=A7=E6=AF=8E=E5=9B=9E=E6=8C=87=E5=AE=9A=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=AE=E3=81=8C=E6=89=8B=E9=96=93=E3=81=AA=E3=81=AE=E3=81=A7?= =?UTF-8?q?=E5=BE=8C=E5=9B=9E=E3=81=97=E3=81=AB=E3=81=97=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=81=8C=E3=80=81=E3=83=AA=E3=83=AA=E3=83=BC=E3=82=B9?= =?UTF-8?q?=E3=81=AB=E5=90=91=E3=81=91=E3=81=A6=E4=BB=8A=E5=9B=9E=E3=81=99?= =?UTF-8?q?=E3=81=B9=E3=81=A6=E6=8C=87=E5=AE=9A=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../createSettingBox_AdvancedSettings.py | 2 +- .../setting_box_appearance/createSettingBox_Appearance.py | 2 +- .../setting_box_others/createSettingBox_Others.py | 2 +- .../setting_box_transcription/createSettingBox_Mic.py | 2 +- .../setting_box_transcription/createSettingBox_Speaker.py | 2 +- .../setting_box_translation/createSettingBox_Translation.py | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py index fb7a679f..4c6ef337 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_advanced_settings/createSettingBox_AdvancedSettings.py @@ -34,5 +34,5 @@ def createSettingBox_AdvancedSettings(setting_box_wrapper, config_window, settin entry_bind__Any_KeyRelease=lambda value: entry_port_callback(value), entry_textvariable=view_variable.VAR_OSC_PORT, ) - config_window.sb__port.grid(row=row) + config_window.sb__port.grid(row=row, pady=0) row+=1 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index 89c17d81..e745bc3f 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -84,5 +84,5 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi command=lambda value: optionmenu_ui_language_callback(value), variable=view_variable.VAR_UI_LANGUAGE, ) - config_window.sb__ui_language.grid(row=row) + config_window.sb__ui_language.grid(row=row, pady=0) row+=1 \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index 2953809e..4fb6aeed 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -91,6 +91,6 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v command=lambda: checkbox_startup_osc_enabled_check_callback(config_window.sb__checkbox_startup_osc_enabled_check), variable=view_variable.VAR_STARTUP_OSC_ENABLED_CHECK, ) - config_window.sb__startup_osc_enabled_check.grid(row=row) + config_window.sb__startup_osc_enabled_check.grid(row=row, pady=0) row+=1 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index ba06c763..026c6539 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -150,5 +150,5 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari entry_bind__Any_KeyRelease=lambda value: entry_input_mic_word_filters_callback(value), entry_textvariable=view_variable.VAR_MIC_WORD_FILTER, ) - config_window.sb__mic_word_filter.grid(row=row) + config_window.sb__mic_word_filter.grid(row=row, pady=0) row+=1 \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 06571644..1e97eaee 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -120,6 +120,6 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ entry_textvariable=view_variable.VAR_SPEAKER_MAX_PHRASES, entry_bind__FocusOut=view_variable.CALLBACK_FOCUS_OUT_SPEAKER_MAX_PHRASES, ) - config_window.sb__speaker_max_phrases.grid(row=row) + config_window.sb__speaker_max_phrases.grid(row=row, pady=0) row+=1 # __________ \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index 014cee3e..d2975d0d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -20,5 +20,5 @@ def createSettingBox_Translation(setting_box_wrapper, config_window, settings, v entry_bind__Any_KeyRelease=lambda value: deepl_authkey_callback(value), entry_textvariable=view_variable.VAR_DEEPL_AUTH_KEY, ) - config_window.sb__deepl_authkey.grid(row=row) + config_window.sb__deepl_authkey.grid(row=row, pady=0) row+=1 \ No newline at end of file From 7be50d0010cc557425496a59c8bf81119895283d Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 8 Oct 2023 21:05:25 +0900 Subject: [PATCH 229/355] [Chore] remove the code that is no longer in use --- vrct_gui/_CreateDropdownMenuWindow.py | 4 ---- .../setting_box_containers/_SettingBoxGenerator.py | 7 ------- vrct_gui/vrct_gui.py | 3 --- 3 files changed, 14 deletions(-) diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index e07a89a5..d11a991c 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -48,10 +48,6 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.max_display_length = self.init_max_display_length - - # self.rowconfigure(0,weight=1) - # self.columnconfigure(0,weight=1) - def updateDropdownMenuValues(self, dropdown_menu_widget_id, dropdown_menu_values): self.dropdown_menu_widgets[dropdown_menu_widget_id].widget.destroy() self.createDropdownMenuBox( diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 4d7798ea..e07f116c 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -17,13 +17,6 @@ class _SettingBoxGenerator(): self.dropdown_menu_window = vrct_gui.vrct_gui.dropdown_menu_window - # self.dropdown_menu_window = _CreateDropdownMenuWindow( - # settings=self.settings, - # view_variable=self.view_variable, - # wrapper_widget=self.config_window.main_bg_container, - # ) - - def _createSettingBoxFrame(self, for_var_label_text, for_var_desc_text): setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 9f722671..b3a1d59c 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -1,7 +1,5 @@ from customtkinter import CTk, CTkImage -# from window_help_and_info import ToplevelWindowInformation - from ._CreateSelectableLanguagesWindow import _CreateSelectableLanguagesWindow from ._CreateModalWindow import _CreateModalWindow @@ -45,7 +43,6 @@ class VRCT_GUI(CTk): settings=self.settings.config_window, view_variable=self._view_variable ) - # self.information_window = ToplevelWindowInformation(self) self.selectable_languages_window = _CreateSelectableLanguagesWindow( vrct_gui=self, From b8994ffd87eabb75a0e350611471b2518edc2bc8 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 8 Oct 2023 23:45:44 +0900 Subject: [PATCH 230/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20upda?= =?UTF-8?q?teSoftware=E3=81=AB=E5=86=8D=E8=B5=B7=E5=8B=95=E3=81=AE?= =?UTF-8?q?=E5=BC=95=E6=95=B0=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 4 ++-- update.bat | 6 +++++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/model.py b/model.py index 9916228e..4b371f0b 100644 --- a/model.py +++ b/model.py @@ -233,7 +233,7 @@ class Model: return update_flag @staticmethod - def updateSoftware(): + def updateSoftware(restart:bool=True): filename = 'download.zip' program_name = 'VRCT.exe' temporary_name = '_VRCT.exe' @@ -253,7 +253,7 @@ class Model: zf.extract(program_name, os_path.join(current_directory, tmp_directory_name)) os_rename(os_path.join(current_directory, tmp_directory_name, program_name), os_path.join(current_directory, temporary_name)) rmtree(os_path.join(current_directory, tmp_directory_name)) - command = [os_path.join(program_directory, batch_name), program_name, temporary_name] + command = [os_path.join(program_directory, batch_name), program_name, temporary_name, str(restart)] Popen(command) @staticmethod diff --git a/update.bat b/update.bat index cbdef3c4..22dc9255 100644 --- a/update.bat +++ b/update.bat @@ -3,4 +3,8 @@ timeout 2 del /f %1 timeout 2 rename %2 %1 -START "" %1 \ No newline at end of file +echo %3 +timeout 2 +if %3 == True ( + START "" %1 +) \ No newline at end of file From aa3ca4036eb0fb4f54b7136546154b5ac306514b Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 9 Oct 2023 00:17:27 +0900 Subject: [PATCH 231/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20Devi?= =?UTF-8?q?ce=E3=81=8C=E4=B8=80=E3=81=A4=E3=82=82=E3=81=AA=E3=81=84?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=AB"NoHost"/"NoDevice"=E3=81=A7?= =?UTF-8?q?=E3=82=BB=E3=83=83=E3=83=88=E3=81=99=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 8 ++++---- models/transcription/transcription_utils.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/model.py b/model.py index 4b371f0b..121bf8d4 100644 --- a/model.py +++ b/model.py @@ -280,7 +280,7 @@ class Model: return False def startMicTranscript(self, fnc): - if config.CHOICE_MIC_HOST == "None" or config.CHOICE_MIC_DEVICE == "None": + if config.CHOICE_MIC_HOST == "NoDevice" or config.CHOICE_MIC_DEVICE == "NoDevice": return mic_audio_queue = Queue() @@ -324,7 +324,7 @@ class Model: self.mic_audio_recorder = None def startCheckMicEnergy(self, fnc): - if config.CHOICE_MIC_HOST == "None" or config.CHOICE_MIC_DEVICE == "None": + if config.CHOICE_MIC_HOST == "NoDevice" or config.CHOICE_MIC_DEVICE == "NoDevice": return def sendMicEnergy(): @@ -353,7 +353,7 @@ class Model: self.mic_energy_recorder = None def startSpeakerTranscript(self, fnc): - if config.CHOICE_SPEAKER_DEVICE == "None": + if config.CHOICE_SPEAKER_DEVICE == "NoDevice": return spk_audio_queue = Queue() spk_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] @@ -397,7 +397,7 @@ class Model: self.spk_audio_recorder = None def startCheckSpeakerEnergy(self, fnc): - if config.CHOICE_SPEAKER_DEVICE == "None": + if config.CHOICE_SPEAKER_DEVICE == "NoDevice": return def sendSpeakerEnergy(): diff --git a/models/transcription/transcription_utils.py b/models/transcription/transcription_utils.py index 522cb7a2..fead4fc4 100644 --- a/models/transcription/transcription_utils.py +++ b/models/transcription/transcription_utils.py @@ -13,7 +13,7 @@ def getInputDevices(): else: devices[host["name"]] = [device] if len(devices) == 0: - devices = {"None": [{"name": "None"}]} + devices = {"NoHost": [{"name": "NoDevice"}]} return devices def getOutputDevices(): @@ -24,7 +24,7 @@ def getOutputDevices(): if device["hostApi"] == wasapi_info["index"] and device["isLoopbackDevice"] is True: devices.append(device) if len(devices) == 0: - devices = [{'name':"None"}] + devices = [{'name':"NoDevice"}] return devices def getDefaultInputDevice(): @@ -38,7 +38,7 @@ def getDefaultInputDevice(): device = p.get_device_info_by_host_api_device_index(host_index, device_index) if device["index"] == defaultInputDevice: return {"host": host, "device": device} - return {"host": {"name": "None"}, "device": {"name": "None"}} + return {"host": {"name": "NoHost"}, "device": {"name": "NoDevice"}} def getDefaultOutputDevice(): with PyAudio() as p: @@ -55,4 +55,4 @@ def getDefaultOutputDevice(): if default_speakers["name"] in loopback["name"]: default_device = loopback return default_device - return {"name":"None"} \ No newline at end of file + return {"name":"NoDevice"} \ No newline at end of file From 4f8bad129ba86fb18cff8961325ff2a912500ff3 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 9 Oct 2023 05:50:04 +0900 Subject: [PATCH 232/355] =?UTF-8?q?[Update]=20Update=20Software=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E3=81=AE=E5=8F=97=E3=81=91=E5=8F=A3=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=80=82update=20available=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=92?= =?UTF-8?q?=E6=8A=BC=E3=81=95=E3=82=8C=E3=82=8B=E3=81=A8=E3=82=B3=E3=83=BC?= =?UTF-8?q?=E3=83=AB=E3=83=90=E3=83=83=E3=82=AF=E9=96=A2=E6=95=B0=E3=81=8C?= =?UTF-8?q?=E5=91=BC=E3=81=B0=E3=82=8C=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=80=82=E5=AE=9F=E9=9A=9B=E3=81=AB=E3=82=A2=E3=83=83=E3=83=97?= =?UTF-8?q?=E3=83=87=E3=83=BC=E3=83=88=E3=82=82=E5=8F=AF=E8=83=BD=E7=8A=B6?= =?UTF-8?q?=E6=85=8B=E3=80=82=20Restart=20Software=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E3=81=AE=E5=8F=97=E3=81=91=E5=8F=A3=E8=BF=BD=E5=8A=A0=E3=80=82?= =?UTF-8?q?=20[bugfix]=20update=20available=E3=83=9C=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=81=A8help=20and=20info=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AE?= =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=AB=E3=83=90=E3=83=83=E3=82=AF=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E6=8C=87=E5=AE=9A=E3=81=8C=E9=80=86=E3=81=A0=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82(?= =?UTF-8?q?=E4=BB=8A=E3=81=BE=E3=81=A7=E5=A5=87=E8=B7=A1=E7=9A=84=E3=81=AB?= =?UTF-8?q?view=E5=81=B4=E3=82=82UI=E3=83=9C=E3=82=BF=E3=83=B3=E5=81=B4?= =?UTF-8?q?=E3=82=82=E5=8F=8D=E5=AF=BE=E3=81=AE=E6=8C=87=E5=AE=9A=E3=82=92?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=81=84=E3=81=A6=E7=B5=90=E6=9E=9C=E7=9A=84?= =?UTF-8?q?=E3=81=AB=E6=A9=9F=E8=83=BD=E3=81=97=E3=81=A6=E3=81=84=E3=81=BE?= =?UTF-8?q?=E3=81=97=E3=81=9F=E2=80=A6=E2=80=A6=E3=80=82)=20=E3=81=9D?= =?UTF-8?q?=E3=81=AE=E4=BB=96=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=81=AA?= =?UTF-8?q?=E3=81=A9=E3=82=B3=E3=83=BC=E3=83=89=E6=95=B4=E5=BD=A2=E3=81=82?= =?UTF-8?q?=E3=82=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 13 +++++++++++++ view.py | 18 +++++++++++------- .../main_window/createMainWindowWidgets.py | 4 ++-- 3 files changed, 26 insertions(+), 9 deletions(-) diff --git a/main.py b/main.py index 5cefaa00..c35708d2 100644 --- a/main.py +++ b/main.py @@ -6,6 +6,14 @@ from view import view from utils import get_key_by_value from languages import selectable_languages +# Common +def callbackUpdateSoftware(): + model.updateSoftware() + +def callbackRestartSoftware(): + print("callbackRestartSoftware") + # model.updateSoftware(restart=True) + # func transcription send message def sendMicMessage(message): if len(message) > 0: @@ -622,6 +630,11 @@ if config.ENABLE_LOGGER is True: # set UI and callback view.register( + common_registers={ + "callback_update_software": callbackUpdateSoftware, + "callback_restart_software": callbackRestartSoftware, + }, + window_action_registers={ "callback_open_config_window": callbackOpenConfigWindow, "callback_close_config_window": callbackCloseConfigWindow, diff --git a/view.py b/view.py index 9d256518..0e65f432 100644 --- a/view.py +++ b/view.py @@ -65,10 +65,10 @@ class View(): CALLBACK_CLOSE_CONFIG_WINDOW=None, # Open Help and Information Page - CALLBACK_CLICKED_HELP_AND_INFO=self.openWebPage_Booth, + CALLBACK_CLICKED_HELP_AND_INFO=self.openWebPage_VrctDocuments, # Open Update Page - CALLBACK_CLICKED_UPDATE_AVAILABLE=self.openWebPage_VrctDocuments, + CALLBACK_CLICKED_UPDATE_AVAILABLE=None, # Main Window @@ -324,20 +324,22 @@ class View(): def register( self, + common_registers=None, window_action_registers=None, main_window_registers=None, config_window_registers=None ): - # Open Config Window + if common_registers is not None: + self.view_variable.CALLBACK_CLICKED_UPDATE_AVAILABLE=common_registers.get("callback_update_software", None) + + if window_action_registers is not None: self.view_variable.CALLBACK_OPEN_CONFIG_WINDOW=window_action_registers.get("callback_open_config_window", None) self.view_variable.CALLBACK_CLOSE_CONFIG_WINDOW=window_action_registers.get("callback_close_config_window", None) - - if main_window_registers is not None: self.view_variable.CALLBACK_ENABLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = main_window_registers.get("callback_enable_main_window_sidebar_compact_mode", None) self.view_variable.CALLBACK_DISABLE_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = main_window_registers.get("callback_disable_main_window_sidebar_compact_mode", None) @@ -363,18 +365,20 @@ class View(): entry_message_box.bind("", self._foregroundOffForcefully) entry_message_box.bind("", self._foregroundOnForcefully) + self.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) self.view_variable.CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_YourLanguage self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_TargetLanguage + # Config Window self.view_variable.CALLBACK_SELECTED_SETTING_BOX_TAB=self._updateActiveSettingBoxTabNo - # Compact Mode Switch - if config_window_registers is not None: + if config_window_registers is not None: + # Compact Mode Switch self.view_variable.CALLBACK_ENABLE_CONFIG_WINDOW_COMPACT_MODE = config_window_registers.get("callback_disable_config_window_compact_mode", None) self.view_variable.CALLBACK_DISABLE_CONFIG_WINDOW_COMPACT_MODE = config_window_registers.get("callback_enable_config_window_compact_mode", None) diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index f926c9de..0a323b89 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -86,7 +86,7 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): enter_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_HOVERED_BG_COLOR, leave_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_BG_COLOR, clicked_color=settings.ctm.UPDATE_AVAILABLE_BUTTON_CLICKED_BG_COLOR, - buttonReleasedFunction=lambda e: callFunctionIfCallable(view_variable.CALLBACK_CLICKED_HELP_AND_INFO), + buttonReleasedFunction=lambda e: callFunctionIfCallable(view_variable.CALLBACK_CLICKED_UPDATE_AVAILABLE), ) @@ -102,7 +102,7 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): button_image_file=settings.image_file.HELP_ICON, button_image_size=settings.uism.HELP_AND_INFO_BUTTON_SIZE, button_ipadxy=settings.uism.HELP_AND_INFO_BUTTON_IPADXY, - button_command=lambda e: callFunctionIfCallable(view_variable.CALLBACK_CLICKED_UPDATE_AVAILABLE), + button_command=lambda e: callFunctionIfCallable(view_variable.CALLBACK_CLICKED_HELP_AND_INFO), corner_radius=settings.uism.HELP_AND_INFO_BUTTON_CORNER_RADIUS, ) vrct_gui.help_and_info_button_container.grid(row=0, column=4, padx=settings.uism.HELP_AND_INFO_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="e") From 305b061f1c33d6d67095d7cde8bccc176f6b8214 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 9 Oct 2023 06:30:46 +0900 Subject: [PATCH 233/355] =?UTF-8?q?[Update]=20Main=20Window:=20add=20Versi?= =?UTF-8?q?on=20info=20and=20Restart=20button(VRCT=20LOGO).=20=E2=80=BB?= =?UTF-8?q?=E9=96=8B=E7=99=BA=E7=94=A8=E3=81=AE=E4=B8=80=E6=99=82=E7=9A=84?= =?UTF-8?q?=E3=81=AA=E3=82=82=E3=81=AE=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 6 +++ .../main_window/createMainWindowWidgets.py | 42 +++++++++++++++++-- 2 files changed, 45 insertions(+), 3 deletions(-) diff --git a/view.py b/view.py index 0e65f432..abdd5421 100644 --- a/view.py +++ b/view.py @@ -60,6 +60,10 @@ class View(): ) self.view_variable = SimpleNamespace( + # Common + CALLBACK_RESTART_SOFTWARE=None, + + # Open Config Window CALLBACK_OPEN_CONFIG_WINDOW=None, CALLBACK_CLOSE_CONFIG_WINDOW=None, @@ -131,6 +135,7 @@ class View(): ACTIVE_SETTING_BOX_TAB_ATTR_NAME="side_menu_tab_appearance", CALLBACK_SELECTED_SETTING_BOX_TAB=None, VAR_ERROR_MESSAGE=StringVar(value=""), + VAR_VERSION=StringVar(value=config.VERSION), # Side Menu Labels @@ -333,6 +338,7 @@ class View(): if common_registers is not None: self.view_variable.CALLBACK_CLICKED_UPDATE_AVAILABLE=common_registers.get("callback_update_software", None) + self.view_variable.CALLBACK_RESTART_SOFTWARE=common_registers.get("callback_restart_software", None) if window_action_registers is not None: diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 0a323b89..06e2783e 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -41,6 +41,41 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): + # Main Top Bar Container - Right Side + # start from 3 + main_topbar_column=3 + + # VRCT Now Version Label(Tmp) + vrct_gui.version_label = CTkLabel( + vrct_gui.main_topbar_container, + textvariable=view_variable.VAR_VERSION, + height=0, + corner_radius=0, + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.UPDATE_AVAILABLE_BUTTON_FONT_SIZE, weight="normal"), + anchor="e", + text_color="white", + ) + vrct_gui.version_label.grid(row=0, column=main_topbar_column, padx=(0,8)) + main_topbar_column+=1 + + + + # Restart Button(Tmp) + vrct_gui.restart_button_container = createButtonWithImage( + parent_widget=vrct_gui.main_topbar_container, + button_fg_color=settings.ctm.HELP_AND_INFO_BUTTON_BG_COLOR, + button_enter_color=settings.ctm.HELP_AND_INFO_BUTTON_HOVERED_BG_COLOR, + button_clicked_color=settings.ctm.HELP_AND_INFO_BUTTON_CLICKED_BG_COLOR, + button_image_file=settings.image_file.VRCT_LOGO_MARK, + button_image_size=settings.uism.HELP_AND_INFO_BUTTON_SIZE, + button_ipadxy=settings.uism.HELP_AND_INFO_BUTTON_IPADXY, + button_command=lambda e: callFunctionIfCallable(view_variable.CALLBACK_RESTART_SOFTWARE), + corner_radius=settings.uism.HELP_AND_INFO_BUTTON_CORNER_RADIUS, + ) + vrct_gui.restart_button_container.grid(row=0, column=main_topbar_column, padx=settings.uism.HELP_AND_INFO_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="e") + main_topbar_column+=1 + + # Update Available Button vrct_gui.update_available_container = CTkFrame( vrct_gui.main_topbar_container, @@ -48,8 +83,9 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): fg_color=settings.ctm.MAIN_BG_COLOR, cursor="hand2", ) - vrct_gui.update_available_container.grid(row=0, column=3, padx=settings.uism.UPDATE_AVAILABLE_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="nse") + vrct_gui.update_available_container.grid(row=0, column=main_topbar_column, padx=settings.uism.UPDATE_AVAILABLE_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="nse") vrct_gui.update_available_container.grid_remove() + main_topbar_column+=1 vrct_gui.update_available_container.rowconfigure((0,2), weight=1) @@ -105,8 +141,8 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): button_command=lambda e: callFunctionIfCallable(view_variable.CALLBACK_CLICKED_HELP_AND_INFO), corner_radius=settings.uism.HELP_AND_INFO_BUTTON_CORNER_RADIUS, ) - vrct_gui.help_and_info_button_container.grid(row=0, column=4, padx=settings.uism.HELP_AND_INFO_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="e") - + vrct_gui.help_and_info_button_container.grid(row=0, column=main_topbar_column, padx=settings.uism.HELP_AND_INFO_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="e") + main_topbar_column+=1 createSidebar(settings, vrct_gui, view_variable) createMinimizeSidebarButton(settings, vrct_gui, view_variable) From 040ca9c8e91fab9413e80a694ec7cf7cbd151430 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 9 Oct 2023 10:25:58 +0900 Subject: [PATCH 234/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20NoDe?= =?UTF-8?q?vice=20->=20NoHost?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/model.py b/model.py index 121bf8d4..9a3871d0 100644 --- a/model.py +++ b/model.py @@ -280,7 +280,7 @@ class Model: return False def startMicTranscript(self, fnc): - if config.CHOICE_MIC_HOST == "NoDevice" or config.CHOICE_MIC_DEVICE == "NoDevice": + if config.CHOICE_MIC_HOST == "NoHost" or config.CHOICE_MIC_DEVICE == "NoDevice": return mic_audio_queue = Queue() @@ -324,7 +324,7 @@ class Model: self.mic_audio_recorder = None def startCheckMicEnergy(self, fnc): - if config.CHOICE_MIC_HOST == "NoDevice" or config.CHOICE_MIC_DEVICE == "NoDevice": + if config.CHOICE_MIC_HOST == "NoHost" or config.CHOICE_MIC_DEVICE == "NoDevice": return def sendMicEnergy(): From 0bc021cbe8607358e0decb121d312b05d896433c Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 9 Oct 2023 13:49:22 +0900 Subject: [PATCH 235/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20rest?= =?UTF-8?q?art=E5=87=A6=E7=90=86=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.bat | 2 +- main.py | 1 + model.py | 8 ++++++++ restart.bat | 4 ++++ 4 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 restart.bat diff --git a/build.bat b/build.bat index 8b50ff9c..9c7d54fe 100644 --- a/build.bat +++ b/build.bat @@ -1 +1 @@ -pyinstaller --onedir --onefile --windowed --clean --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --add-data "./locales;locales/" --add-data "./update.bat;./" --name VRCT --exclude-module numpy --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py \ No newline at end of file +pyinstaller --onedir --onefile --windowed --clean --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --add-data "./locales;locales/" --add-data "./update.bat;./" --add-data "./restart.bat;./" --name VRCT --exclude-module numpy --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py \ No newline at end of file diff --git a/main.py b/main.py index c35708d2..db2bdcbc 100644 --- a/main.py +++ b/main.py @@ -13,6 +13,7 @@ def callbackUpdateSoftware(): def callbackRestartSoftware(): print("callbackRestartSoftware") # model.updateSoftware(restart=True) + model.reStartSoftware() # func transcription send message def sendMicMessage(message): diff --git a/model.py b/model.py index 9a3871d0..2b3eaf80 100644 --- a/model.py +++ b/model.py @@ -256,6 +256,14 @@ class Model: command = [os_path.join(program_directory, batch_name), program_name, temporary_name, str(restart)] Popen(command) + @staticmethod + def reStartSoftware(): + program_name = 'VRCT.exe' + batch_name = 'restart.bat' + program_directory = os_path.dirname(__file__) + command = [os_path.join(program_directory, batch_name), program_name] + Popen(command) + @staticmethod def getListInputHost(): return [host for host in getInputDevices().keys()] diff --git a/restart.bat b/restart.bat new file mode 100644 index 00000000..fc305a6e --- /dev/null +++ b/restart.bat @@ -0,0 +1,4 @@ +@echo off + +taskkill /im %1 /F +START "" %1 \ No newline at end of file From c266a098d418778861852086183dec6e0f8d5a63 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 9 Oct 2023 16:14:28 +0900 Subject: [PATCH 236/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20?= =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AAcommand=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- update.bat | 1 - 1 file changed, 1 deletion(-) diff --git a/update.bat b/update.bat index 22dc9255..099d8689 100644 --- a/update.bat +++ b/update.bat @@ -3,7 +3,6 @@ timeout 2 del /f %1 timeout 2 rename %2 %1 -echo %3 timeout 2 if %3 == True ( START "" %1 From d59a1a05a97d456b914495d65dc9ea0c1b5bf976 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 9 Oct 2023 16:21:46 +0900 Subject: [PATCH 237/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20batc?= =?UTF-8?q?h=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=82=92=E4=B8=80?= =?UTF-8?q?=E7=AE=87=E6=89=80=E3=81=AB=E3=81=BE=E3=81=A8=E3=82=81=E3=81=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- restart.bat => batch/restart.bat | 0 update.bat => batch/update.bat | 0 build.bat | 2 +- model.py | 4 ++-- 4 files changed, 3 insertions(+), 3 deletions(-) rename restart.bat => batch/restart.bat (100%) rename update.bat => batch/update.bat (100%) diff --git a/restart.bat b/batch/restart.bat similarity index 100% rename from restart.bat rename to batch/restart.bat diff --git a/update.bat b/batch/update.bat similarity index 100% rename from update.bat rename to batch/update.bat diff --git a/build.bat b/build.bat index 9c7d54fe..6dcbbf71 100644 --- a/build.bat +++ b/build.bat @@ -1 +1 @@ -pyinstaller --onedir --onefile --windowed --clean --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --add-data "./locales;locales/" --add-data "./update.bat;./" --add-data "./restart.bat;./" --name VRCT --exclude-module numpy --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py \ No newline at end of file +pyinstaller --onedir --onefile --windowed --clean --icon="./img/vrct_logo_mark_black.ico" --add-data "./img;img/" --add-data "./locales;locales/" --add-data "./batch;batch/" --name VRCT --exclude-module numpy --exclude-module pandas --exclude-module matplotlib --exclude-module PyQt5 main.py \ No newline at end of file diff --git a/model.py b/model.py index 2b3eaf80..292c387c 100644 --- a/model.py +++ b/model.py @@ -253,7 +253,7 @@ class Model: zf.extract(program_name, os_path.join(current_directory, tmp_directory_name)) os_rename(os_path.join(current_directory, tmp_directory_name, program_name), os_path.join(current_directory, temporary_name)) rmtree(os_path.join(current_directory, tmp_directory_name)) - command = [os_path.join(program_directory, batch_name), program_name, temporary_name, str(restart)] + command = [os_path.join(program_directory, "batch", batch_name), program_name, temporary_name, str(restart)] Popen(command) @staticmethod @@ -261,7 +261,7 @@ class Model: program_name = 'VRCT.exe' batch_name = 'restart.bat' program_directory = os_path.dirname(__file__) - command = [os_path.join(program_directory, batch_name), program_name] + command = [os_path.join(program_directory, "batch", batch_name), program_name] Popen(command) @staticmethod From 6657903c57a4d5e63cace8acb7bab919f3856a03 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 06:15:05 +0900 Subject: [PATCH 238/355] =?UTF-8?q?[bugfix]=20Config=20Window:=20Dropdown?= =?UTF-8?q?=20Menu=20Window.=201px=E3=83=90=E3=82=B0=E4=BF=AE=E6=AD=A3=20?= =?UTF-8?q?=E7=94=9F=E6=88=90=E6=99=82=E3=81=ABroot=E3=81=AEgeometry?= =?UTF-8?q?=E3=82=92=E6=89=8B=E5=8B=95=E3=81=A7=E8=A8=88=E7=AE=97(px?= =?UTF-8?q?=E3=81=AF=E5=81=B6=E6=95=B0=E3=81=AB)=E3=81=97=E3=81=A6?= =?UTF-8?q?=E4=BF=9D=E5=AD=98=E3=80=82=E8=A1=A8=E7=A4=BA=E6=99=82=E3=81=AB?= =?UTF-8?q?=E3=81=9D=E3=81=AE=E5=80=A4=E3=82=92root=E3=81=AEgeometry?= =?UTF-8?q?=E3=81=AB=E3=82=BB=E3=83=83=E3=83=88=E3=81=99=E3=82=8B=E3=81=93?= =?UTF-8?q?=E3=81=A8=E3=81=AB=E3=82=88=E3=81=A3=E3=81=A6=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?(=E4=BB=8A=E3=81=AE=E6=89=80=E3=81=93=E3=81=AE=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E3=81=97=E3=81=8B=E3=82=8F=E3=81=8B=E3=82=89=E3=81=AA?= =?UTF-8?q?=E3=81=84)=E3=80=82=20[Refactor]=20Config=20Window:=20Dropdown?= =?UTF-8?q?=20Menu=20Window.=20=E8=89=B2=E6=8C=87=E5=AE=9A=E3=82=92?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E3=82=B9=E3=82=BF=E3=83=B3=E3=82=B9=E7=94=9F?= =?UTF-8?q?=E6=88=90=E6=99=82=E3=81=AB=E6=8C=87=E5=AE=9A=E3=81=95=E3=81=9B?= =?UTF-8?q?=E6=B1=8E=E7=94=A8=E5=8C=96=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateDropdownMenuWindow.py | 110 ++++++++++++++++++-------- vrct_gui/vrct_gui.py | 7 ++ 2 files changed, 82 insertions(+), 35 deletions(-) diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index d11a991c..1f2bbc1d 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -7,7 +7,16 @@ from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindBut from functools import partial class _CreateDropdownMenuWindow(CTkToplevel): - def __init__(self, settings, view_variable): + def __init__(self, + settings, + view_variable, + window_bg_color, + window_border_color, + values_bg_color, + values_hovered_bg_color, + values_clicked_bg_color, + values_text_color, + ): super().__init__() self.withdraw() self.hide = True @@ -22,6 +31,14 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.resizable(width=False, height=False) + self.window_bg_color=window_bg_color + self.window_border_color=window_border_color + self.values_bg_color=values_bg_color + self.values_hovered_bg_color=values_hovered_bg_color + self.values_clicked_bg_color=values_clicked_bg_color + self.values_text_color=values_text_color + + self.settings = settings self.attach_widget = None self._view_variable = view_variable @@ -70,10 +87,36 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.wrapper_widget = wrapper_widget - self.dropdown_menu_container = CTkFrame(self, corner_radius=0, fg_color="#ff7f50", width=0, height=0) + self.dropdown_menu_container = CTkFrame(self, corner_radius=0, fg_color=self.window_border_color, width=0, height=0) + # self.dropdown_menu_container = CTkFrame(self, corner_radius=0, fg_color="#ff7f50", width=0, height=0) self.dropdown_menu_container.grid(row=0, column=0, sticky="nsew") - self.dropdown_menu_container.grid_remove() + + BORDER_WIDTH=1 + self.scroll_frame_container = CTkScrollableFrame( + self.dropdown_menu_container, + corner_radius=0, + fg_color=self.window_bg_color, + width=0, + height=0, + border_width=0, + ) + self.scroll_frame_container.grid(row=0, column=0, padx=BORDER_WIDTH, pady=BORDER_WIDTH, sticky="nsew") + self.scroll_frame_container._scrollbar.grid_configure(padx=(2, 2)) + self.scroll_frame_container.grid_columnconfigure(0, weight=1) + + + + + self.dropdown_menu_values_box = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.window_bg_color, width=0, height=0) + self.dropdown_menu_values_box.grid(row=0, column=0, sticky="nsew") + self.dropdown_menu_values_box.grid_columnconfigure(0, weight=1) + + + self._createDropdownMenuValues(dropdown_menu_widget_id, dropdown_menu_values, command) + + geometry_width = int(self.new_width + self.scroll_frame_container._scrollbar.winfo_width() + (BORDER_WIDTH*2) + 4) + geometry_height = int(self.new_height + (BORDER_WIDTH*2)) self.dropdown_menu_widgets[dropdown_menu_widget_id] = SimpleNamespace() self.dropdown_menu_widgets[dropdown_menu_widget_id] = SimpleNamespace( @@ -84,37 +127,24 @@ class _CreateDropdownMenuWindow(CTkToplevel): dropdown_menu_width=dropdown_menu_width, dropdown_menu_height=dropdown_menu_height, max_display_length=max_display_length, - ) + ), + _settings=SimpleNamespace( + geometry_width=geometry_width, + geometry_height=geometry_height, + ), ) + self.dropdown_menu_container.grid_remove() - self.scroll_frame_container = CTkScrollableFrame( - self.dropdown_menu_container, - corner_radius=0, - fg_color=self.settings.ctm.SB__DROPDOWN_MENU_WINDOW_BG_COLOR, - width=0, - height=0, - border_color=self.settings.ctm.SB__DROPDOWN_MENU_WINDOW_BORDER_COLOR, - border_width=1, - ) - self.scroll_frame_container.grid(row=0, column=0, sticky="nsew") - self.scroll_frame_container._scrollbar.grid_configure(padx=(1, 2)) - self.scroll_frame_container.grid_columnconfigure(0, weight=1) - - self.dropdown_menu_values_box = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.settings.ctm.SB__DROPDOWN_MENU_WINDOW_BG_COLOR, width=0, height=0) - self.dropdown_menu_values_box.grid(row=0, column=0, sticky="nsew") - self.dropdown_menu_values_box.grid_columnconfigure(0, weight=1) - - self._createDropdownMenuValues(dropdown_menu_widget_id, dropdown_menu_values, command) def _createDropdownMenuValues(self, dropdown_menu_widget_id, dropdown_menu_values, command): - self.dropdown_menu_values_wrapper = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.settings.ctm.SB__DROPDOWN_MENU_WINDOW_BG_COLOR) + self.dropdown_menu_values_wrapper = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.window_bg_color) self.dropdown_menu_values_wrapper.grid(row=0, column=0, sticky="nsew") self.dropdown_menu_values_wrapper.grid_columnconfigure(0, weight=1) # for get to the height__________________ - __dropdown_menu_value_wrapper = CTkFrame(self.dropdown_menu_values_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__DROPDOWN_MENU_BG_COLOR, width=0, height=0) + __dropdown_menu_value_wrapper = CTkFrame(self.dropdown_menu_values_wrapper, corner_radius=0, fg_color=self.values_bg_color, width=0, height=0) __dropdown_menu_value_wrapper.grid(row=0, column=0, ipadx=6, ipady=6, sticky="nsew") setattr(self, f"{dropdown_menu_widget_id}__{0}", __dropdown_menu_value_wrapper) @@ -128,7 +158,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): corner_radius=0, font=CTkFont(family=self.settings.FONT_FAMILY, size=14, weight="normal"), anchor="w", - text_color=self.settings.ctm.BASIC_TEXT_COLOR, + text_color=self.values_text_color, ) # setattr(self, f"l", __label_widget) @@ -138,11 +168,18 @@ class _CreateDropdownMenuWindow(CTkToplevel): dropdown_menu_values_length = len(dropdown_menu_values) if dropdown_menu_values_length < self.max_display_length: - new_height = int(dropdown_menu_values_length * label_height) + self.new_height = int(dropdown_menu_values_length * label_height) else: - new_height = int(self.max_display_length * label_height) + self.new_height = int(self.max_display_length * label_height) - self.scroll_frame_container.configure(width=self.new_width, height=new_height) + + def makeEven(input_value): + return input_value + 1 if input_value % 2 == 1 else input_value + + + self.new_height = makeEven(self.new_height) + self.new_width = makeEven(self.new_width) + self.scroll_frame_container.configure(width=self.new_width, height=self.new_height) # This is for CustomTkinter's spec change or bug fix. self.scroll_frame_container._scrollbar.configure(height=0) @@ -152,7 +189,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): row=0 for dropdown_menu_value in dropdown_menu_values: - dropdown_menu_value_wrapper = CTkFrame(self.dropdown_menu_values_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__DROPDOWN_MENU_BG_COLOR, width=0, height=0, cursor="hand2") + dropdown_menu_value_wrapper = CTkFrame(self.dropdown_menu_values_wrapper, corner_radius=0, fg_color=self.values_bg_color, width=0, height=0, cursor="hand2") dropdown_menu_value_wrapper.grid(row=row, column=0, ipadx=6, ipady=6, sticky="nsew") setattr(self, f"{dropdown_menu_widget_id}__{row}", dropdown_menu_value_wrapper) @@ -167,7 +204,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): corner_radius=0, font=CTkFont(family=self.settings.FONT_FAMILY, size=14, weight="normal"), anchor="w", - text_color=self.settings.ctm.BASIC_TEXT_COLOR, + text_color=self.values_text_color, ) # setattr(self, f"l", label_widget) @@ -175,8 +212,8 @@ class _CreateDropdownMenuWindow(CTkToplevel): - bindEnterAndLeaveColor([dropdown_menu_value_wrapper, label_widget], self.settings.ctm.SB__DROPDOWN_MENU_HOVERED_BG_COLOR, self.settings.ctm.SB__DROPDOWN_MENU_BG_COLOR) - bindButtonPressColor([dropdown_menu_value_wrapper, label_widget], self.settings.ctm.SB__DROPDOWN_MENU_CLICKED_BG_COLOR, self.settings.ctm.SB__DROPDOWN_MENU_BG_COLOR) + bindEnterAndLeaveColor([dropdown_menu_value_wrapper, label_widget], self.values_hovered_bg_color, self.values_bg_color) + bindButtonPressColor([dropdown_menu_value_wrapper, label_widget], self.values_clicked_bg_color, self.values_bg_color) @@ -201,9 +238,12 @@ class _CreateDropdownMenuWindow(CTkToplevel): if self.active_dropdown_menu_widget is not None: self.active_dropdown_menu_widget.grid_remove() - target_Widget = self.dropdown_menu_widgets[dropdown_menu_widget_id].widget - target_Widget.grid() - self.active_dropdown_menu_widget = target_Widget + target_data = self.dropdown_menu_widgets[dropdown_menu_widget_id] + target_data.widget.grid() + self.active_dropdown_menu_widget = target_data.widget + + self.geometry("{}x{}".format(target_data._settings.geometry_width, target_data._settings.geometry_height)) + self.deiconify() self._adjustToTargetWidgetGeometry() diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index b3a1d59c..236da886 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -36,6 +36,13 @@ class VRCT_GUI(CTk): self.dropdown_menu_window = _CreateDropdownMenuWindow( settings=self.settings.config_window, view_variable=self._view_variable, + + window_bg_color=self.settings.config_window.ctm.SB__DROPDOWN_MENU_WINDOW_BG_COLOR, + window_border_color=self.settings.config_window.ctm.SB__DROPDOWN_MENU_WINDOW_BORDER_COLOR, + values_bg_color=self.settings.config_window.ctm.SB__DROPDOWN_MENU_BG_COLOR, + values_hovered_bg_color=self.settings.config_window.ctm.SB__DROPDOWN_MENU_HOVERED_BG_COLOR, + values_clicked_bg_color=self.settings.config_window.ctm.SB__DROPDOWN_MENU_CLICKED_BG_COLOR, + values_text_color=self.settings.config_window.ctm.BASIC_TEXT_COLOR, ) self.config_window = ConfigWindow( From 7fd8b089d58c80ee99f2451ef59890416416b01d Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 08:46:14 +0900 Subject: [PATCH 239/355] =?UTF-8?q?[Update]=20Config=20Window:=20Dropdown?= =?UTF-8?q?=20Menu=20Window.=20UI=20Size=E5=A4=89=E6=9B=B4=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C=E3=80=82=20[bugfix]=20Config=20Window:=20Dropdown=20M?= =?UTF-8?q?enu=20Window.=20=E3=81=9D=E3=82=8C=E3=81=AB=E4=BC=B4=E3=81=84ro?= =?UTF-8?q?ot=E3=81=AEgeometry,=20position=E3=81=8C=E5=B4=A9=E3=82=8C?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=81=A7=E3=81=9D=E3=81=AE=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateDropdownMenuWindow.py | 56 +++++++++++++------ .../_SettingBoxGenerator.py | 17 +++--- vrct_gui/ui_managers/UiScalingManager.py | 15 +++-- vrct_gui/vrct_gui.py | 6 ++ 4 files changed, 64 insertions(+), 30 deletions(-) diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index 1f2bbc1d..287dfb51 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -10,6 +10,14 @@ class _CreateDropdownMenuWindow(CTkToplevel): def __init__(self, settings, view_variable, + + window_additional_y_pos, + window_border_width, + scrollbar_ipadx, + value_padx, + value_pady, + value_font_size, + window_bg_color, window_border_color, values_bg_color, @@ -31,6 +39,13 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.resizable(width=False, height=False) + self.window_additional_y_pos=window_additional_y_pos + self.window_border_width=window_border_width + self.scrollbar_ipadx=scrollbar_ipadx + self.value_padx=value_padx + self.value_pady=value_pady + self.value_font_size=value_font_size + self.window_bg_color=window_bg_color self.window_border_color=window_border_color self.values_bg_color=values_bg_color @@ -72,6 +87,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): dropdown_menu_values=dropdown_menu_values, command=self.dropdown_menu_widgets[dropdown_menu_widget_id].command, wrapper_widget=self.dropdown_menu_widgets[dropdown_menu_widget_id].wrapper_widget, + attach_widget=self.dropdown_menu_widgets[dropdown_menu_widget_id].attach_widget, dropdown_menu_width=self.dropdown_menu_widgets[dropdown_menu_widget_id].dropdown_menu_settings.dropdown_menu_width, dropdown_menu_height=self.dropdown_menu_widgets[dropdown_menu_widget_id].dropdown_menu_settings.dropdown_menu_height, @@ -79,20 +95,22 @@ class _CreateDropdownMenuWindow(CTkToplevel): ) - def createDropdownMenuBox(self, dropdown_menu_widget_id, dropdown_menu_values, command, wrapper_widget, dropdown_menu_width=None, dropdown_menu_height=None, max_display_length=None): - self.new_width = dropdown_menu_width if dropdown_menu_width is not None else self.init_width + def createDropdownMenuBox(self, dropdown_menu_widget_id, dropdown_menu_values, command, wrapper_widget, attach_widget, dropdown_menu_width=None, dropdown_menu_height=None, max_display_length=None): + + self.attach_widget = attach_widget + self.wrapper_widget = wrapper_widget + + self.update() + self.new_width = dropdown_menu_width if dropdown_menu_width is not None else self.attach_widget.winfo_width() self.new_height = dropdown_menu_height if dropdown_menu_height is not None else self.init_height self.max_display_length = max_display_length if max_display_length is not None else self.init_max_display_length - self.wrapper_widget = wrapper_widget - self.dropdown_menu_container = CTkFrame(self, corner_radius=0, fg_color=self.window_border_color, width=0, height=0) - # self.dropdown_menu_container = CTkFrame(self, corner_radius=0, fg_color="#ff7f50", width=0, height=0) self.dropdown_menu_container.grid(row=0, column=0, sticky="nsew") - BORDER_WIDTH=1 + BORDER_WIDTH=self.window_border_width self.scroll_frame_container = CTkScrollableFrame( self.dropdown_menu_container, corner_radius=0, @@ -102,7 +120,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): border_width=0, ) self.scroll_frame_container.grid(row=0, column=0, padx=BORDER_WIDTH, pady=BORDER_WIDTH, sticky="nsew") - self.scroll_frame_container._scrollbar.grid_configure(padx=(2, 2)) + self.scroll_frame_container._scrollbar.grid_configure(padx=self.scrollbar_ipadx) self.scroll_frame_container.grid_columnconfigure(0, weight=1) @@ -115,14 +133,16 @@ class _CreateDropdownMenuWindow(CTkToplevel): self._createDropdownMenuValues(dropdown_menu_widget_id, dropdown_menu_values, command) - geometry_width = int(self.new_width + self.scroll_frame_container._scrollbar.winfo_width() + (BORDER_WIDTH*2) + 4) + geometry_width = int(self.new_width + self.scroll_frame_container._scrollbar.winfo_width() + (BORDER_WIDTH*2) + (self.scrollbar_ipadx[0] + self.scrollbar_ipadx[1])) geometry_height = int(self.new_height + (BORDER_WIDTH*2)) + self.dropdown_menu_widgets[dropdown_menu_widget_id] = SimpleNamespace() self.dropdown_menu_widgets[dropdown_menu_widget_id] = SimpleNamespace( widget=self.dropdown_menu_container, command=command, wrapper_widget=wrapper_widget, + attach_widget=attach_widget, dropdown_menu_settings=SimpleNamespace( dropdown_menu_width=dropdown_menu_width, dropdown_menu_height=dropdown_menu_height, @@ -145,7 +165,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): # for get to the height__________________ __dropdown_menu_value_wrapper = CTkFrame(self.dropdown_menu_values_wrapper, corner_radius=0, fg_color=self.values_bg_color, width=0, height=0) - __dropdown_menu_value_wrapper.grid(row=0, column=0, ipadx=6, ipady=6, sticky="nsew") + __dropdown_menu_value_wrapper.grid(row=0, column=0, sticky="nsew") setattr(self, f"{dropdown_menu_widget_id}__{0}", __dropdown_menu_value_wrapper) @@ -156,13 +176,13 @@ class _CreateDropdownMenuWindow(CTkToplevel): text="Aa", height=0, corner_radius=0, - font=CTkFont(family=self.settings.FONT_FAMILY, size=14, weight="normal"), + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.value_font_size, weight="normal"), anchor="w", text_color=self.values_text_color, ) # setattr(self, f"l", __label_widget) - __label_widget.grid(row=1, column=0, padx=(8,0), sticky="w") + __label_widget.grid(row=1, column=0, padx=self.value_padx, pady=self.value_pady, sticky="w") label_height = getLatestHeight(__dropdown_menu_value_wrapper) # ______________________________________ @@ -190,7 +210,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): for dropdown_menu_value in dropdown_menu_values: dropdown_menu_value_wrapper = CTkFrame(self.dropdown_menu_values_wrapper, corner_radius=0, fg_color=self.values_bg_color, width=0, height=0, cursor="hand2") - dropdown_menu_value_wrapper.grid(row=row, column=0, ipadx=6, ipady=6, sticky="nsew") + dropdown_menu_value_wrapper.grid(row=row, column=0, sticky="nsew") setattr(self, f"{dropdown_menu_widget_id}__{row}", dropdown_menu_value_wrapper) @@ -202,14 +222,13 @@ class _CreateDropdownMenuWindow(CTkToplevel): text=dropdown_menu_value, height=0, corner_radius=0, - font=CTkFont(family=self.settings.FONT_FAMILY, size=14, weight="normal"), + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.value_font_size, weight="normal"), anchor="w", text_color=self.values_text_color, ) # setattr(self, f"l", label_widget) - label_widget.grid(row=1, column=0, padx=(8,0), sticky="w") - + label_widget.grid(row=1, column=0, padx=self.value_padx, pady=self.value_pady, sticky="w") bindEnterAndLeaveColor([dropdown_menu_value_wrapper, label_widget], self.values_hovered_bg_color, self.values_bg_color) @@ -228,17 +247,18 @@ class _CreateDropdownMenuWindow(CTkToplevel): - def show(self, dropdown_menu_widget_id, target_widget): + def show(self, dropdown_menu_widget_id): if self.hide is False: return self.wm_attributes("-alpha", 0) - self.attach_widget = target_widget if self.active_dropdown_menu_widget is not None: self.active_dropdown_menu_widget.grid_remove() target_data = self.dropdown_menu_widgets[dropdown_menu_widget_id] + self.attach_widget = target_data.attach_widget + target_data.widget.grid() self.active_dropdown_menu_widget = target_data.widget @@ -297,7 +317,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.attach_widget_y_pos = self.attach_widget.winfo_rooty() - self.y_pos = int(self.attach_widget_y_pos + self.attach_widget_height + 4) + self.y_pos = int(self.attach_widget_y_pos + self.attach_widget_height + self.window_additional_y_pos) if self.wrapper_widget_y_pos > self.y_pos or self.y_pos > self.wrapper_widget_bottom_y_pos: self.hideTemporarily() diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index e07f116c..b2d25d9d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -82,13 +82,6 @@ class _SettingBoxGenerator(): variable.set(value) command(value) - self.dropdown_menu_window.createDropdownMenuBox( - dropdown_menu_widget_id=optionmenu_attr_name, - dropdown_menu_values=dropdown_menu_values, - command=adjustedCommand, - wrapper_widget=self.config_window.main_bg_container, - dropdown_menu_width=dropdown_menu_width, - ) option_menu_widget = createOptionMenuBox( parent_widget=setting_box_item_frame, @@ -108,13 +101,21 @@ class _SettingBoxGenerator(): image_size=(14,14), optionmenu_clicked_command=lambda _e: self.dropdown_menu_window.show( dropdown_menu_widget_id=optionmenu_attr_name, - target_widget=option_menu_widget, ), ) option_menu_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") setattr(self.config_window, optionmenu_attr_name, option_menu_widget) + self.dropdown_menu_window.createDropdownMenuBox( + dropdown_menu_widget_id=optionmenu_attr_name, + dropdown_menu_values=dropdown_menu_values, + command=adjustedCommand, + wrapper_widget=self.config_window.main_bg_container, + attach_widget=option_menu_widget, + dropdown_menu_width=dropdown_menu_width, + ) + return setting_box_frame diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 78f892bb..df68a884 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -153,10 +153,14 @@ class UiScalingManager(): self.config_window.SB__OPTION_MENU_FONT_SIZE = self.config_window.SB__SELECTOR_FONT_SIZE self.config_window.SB__OPTIONMENU_MIN_HEIGHT = self._calculateUiSize(30) self.config_window.SB__OPTIONMENU_MIN_WIDTH = self._calculateUiSize(200) + + self.config_window.SB__DROPDOWN_MENU_WINDOW_ADDITIONAL_Y_POS = self._calculateUiSize(4) self.config_window.SB__DROPDOWN_MENU_WIDTH = self.config_window.SB__OPTIONMENU_MIN_WIDTH - self.config_window.SB__DROPDOWN_MENU_MAX_BUTTON_HEIGHT = int(self.config_window.SB__OPTION_MENU_FONT_SIZE + self._calculateUiSize(6)) - self.config_window.SB__DROPDOWN_MENU_FRAME_CORNER_RADIUS = self._calculateUiSize(10) - self.config_window.SB__DROPDOWN_MENU_FRAME_MAX_HEIGHT = self._calculateUiSize(200) + self.config_window.SB__DROPDOWN_MENU_WINDOW_BORDER_WIDTH = self._calculateUiSize(1, is_allowed_odd=True) + self.config_window.SB__DROPDOWN_MENU_SCROLLBAR_IPADX = (self._calculateUiSize(2), self._calculateUiSize(2)) + self.config_window.SB__DROPDOWN_MENU_VALUE_PADX = (self._calculateUiSize(8), 0) + self.config_window.SB__DROPDOWN_MENU_VALUE_PADY = self._calculateUiSize(6) + self.config_window.SB__DROPDOWN_MENU_VALUE_FONT_SIZE = self._calculateUiSize(14) self.config_window.SB__SWITCH_WIDTH = self._calculateUiSize(50) @@ -186,9 +190,12 @@ class UiScalingManager(): - def _calculateUiSize(self, default_size, is_allowed_odd: bool = False): + def _calculateUiSize(self, default_size, is_allowed_odd:bool=False, is_zero_allowed:bool=False): size = int(default_size * self.SCALING_FLOAT) size += 1 if not is_allowed_odd and size % 2 != 0 else 0 + if size <= 0: + size = 0 if is_zero_allowed else 1 + return size @staticmethod diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 236da886..7ca3d7ea 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -36,6 +36,12 @@ class VRCT_GUI(CTk): self.dropdown_menu_window = _CreateDropdownMenuWindow( settings=self.settings.config_window, view_variable=self._view_variable, + window_additional_y_pos=self.settings.config_window.uism.SB__DROPDOWN_MENU_WINDOW_ADDITIONAL_Y_POS, + window_border_width=self.settings.config_window.uism.SB__DROPDOWN_MENU_WINDOW_BORDER_WIDTH, + scrollbar_ipadx=self.settings.config_window.uism.SB__DROPDOWN_MENU_SCROLLBAR_IPADX, + value_padx=self.settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_PADX, + value_pady=self.settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_PADY, + value_font_size=self.settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_FONT_SIZE, window_bg_color=self.settings.config_window.ctm.SB__DROPDOWN_MENU_WINDOW_BG_COLOR, window_border_color=self.settings.config_window.ctm.SB__DROPDOWN_MENU_WINDOW_BORDER_COLOR, From 00561ae3ba790b04391ed5770d26b068f35c7786 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 08:57:24 +0900 Subject: [PATCH 240/355] [Update] Expand available UI Size. It is also for testing more easier. --- config.py | 2 +- view.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config.py b/config.py index bf6f3a6b..5dac3f77 100644 --- a/config.py +++ b/config.py @@ -222,7 +222,7 @@ class Config: @UI_SCALING.setter def UI_SCALING(self, value): - if value in ["80%", "90%", "100%", "110%", "120%"]: + if value in ["40%", "60%", "80%", "90%", "100%", "110%", "120%", "150%", "200%"]: self._UI_SCALING = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) diff --git a/view.py b/view.py index abdd5421..297b2fcb 100644 --- a/view.py +++ b/view.py @@ -163,7 +163,7 @@ class View(): VAR_LABEL_UI_SCALING=StringVar(value=i18n.t("config_window.ui_size.label")), VAR_DESC_UI_SCALING=None, - LIST_UI_SCALING=["80%", "90%", "100%", "110%", "120%"], + LIST_UI_SCALING=["40%", "60%", "80%", "90%", "100%", "110%", "120%", "150%", "200%"], CALLBACK_SET_UI_SCALING=None, VAR_UI_SCALING=StringVar(value=config.UI_SCALING), From 1722cfecff963325644df913547795089e909cc5 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 13:01:46 +0900 Subject: [PATCH 241/355] =?UTF-8?q?[Update]=20Config=20Window:=20Dropdown?= =?UTF-8?q?=20Menu=20Window.=20UI=20Size=E5=A4=89=E6=9B=B4=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C(=E3=82=B9=E3=82=AF=E3=83=AD=E3=83=BC=E3=83=AB?= =?UTF-8?q?=E3=83=BC=E3=83=90=E3=83=BC)=20[bugfix]=20Config=20Window:=20Dr?= =?UTF-8?q?opdown=20Menu=20Window.=20=E3=81=95=E3=82=89=E3=81=AB1px?= =?UTF-8?q?=E3=83=90=E3=82=B0=E3=81=AE=E4=BF=AE=E6=AD=A3=20=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E3=82=B5=E3=82=A4=E3=82=BA=E3=81=AB=E9=96=A2=E3=82=8F?= =?UTF-8?q?=E3=82=89=E3=81=9A=E3=80=81=E3=83=95=E3=82=A9=E3=83=B3=E3=83=88?= =?UTF-8?q?=E3=81=AB=E3=82=88=E3=81=A3=E3=81=A6height=E3=81=8C=E5=A4=89?= =?UTF-8?q?=E3=82=8F=E3=82=8B=E3=81=AE=E3=81=A7=E3=80=81=E5=86=85=E9=83=A8?= =?UTF-8?q?=E3=81=A7=E6=9B=B4=E3=81=AB=E8=A8=88=E7=AE=97=E3=81=97=E3=81=A6?= =?UTF-8?q?=E8=AA=BF=E6=95=B4=E3=80=82=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils.py | 8 ++++++- vrct_gui/_CreateDropdownMenuWindow.py | 30 ++++++++++++++++-------- vrct_gui/ui_managers/UiScalingManager.py | 6 +++-- vrct_gui/vrct_gui.py | 4 +++- 4 files changed, 34 insertions(+), 14 deletions(-) diff --git a/utils.py b/utils.py index d15b169c..1a408523 100644 --- a/utils.py +++ b/utils.py @@ -12,4 +12,10 @@ def get_key_by_value(dictionary, value): return None def callFunctionIfCallable(function, *args): - if callable(function) is True: function(*args) \ No newline at end of file + if callable(function) is True: function(*args) + +def isEven(number): + return number % 2 == 0 + +def makeEven(number): + return number if isEven(number) else number + 1 \ No newline at end of file diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index 287dfb51..f5f79be7 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -6,6 +6,8 @@ from time import sleep from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor, getLatestWidth, getLatestHeight from functools import partial +from utils import isEven, makeEven + class _CreateDropdownMenuWindow(CTkToplevel): def __init__(self, settings, @@ -14,7 +16,9 @@ class _CreateDropdownMenuWindow(CTkToplevel): window_additional_y_pos, window_border_width, scrollbar_ipadx, - value_padx, + scrollbar_width, + value_ipadx, + value_ipady, value_pady, value_font_size, @@ -42,7 +46,9 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.window_additional_y_pos=window_additional_y_pos self.window_border_width=window_border_width self.scrollbar_ipadx=scrollbar_ipadx - self.value_padx=value_padx + self.scrollbar_width=scrollbar_width + self.value_ipadx=value_ipadx + self.value_ipady=value_ipady self.value_pady=value_pady self.value_font_size=value_font_size @@ -165,7 +171,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): # for get to the height__________________ __dropdown_menu_value_wrapper = CTkFrame(self.dropdown_menu_values_wrapper, corner_radius=0, fg_color=self.values_bg_color, width=0, height=0) - __dropdown_menu_value_wrapper.grid(row=0, column=0, sticky="nsew") + __dropdown_menu_value_wrapper.grid(row=0, column=0, pady=self.value_pady, sticky="nsew") setattr(self, f"{dropdown_menu_widget_id}__{0}", __dropdown_menu_value_wrapper) @@ -182,8 +188,14 @@ class _CreateDropdownMenuWindow(CTkToplevel): ) # setattr(self, f"l", __label_widget) - __label_widget.grid(row=1, column=0, padx=self.value_padx, pady=self.value_pady, sticky="w") + __label_widget.grid(row=1, column=0, padx=self.value_ipadx, pady=self.value_ipady, sticky="w") label_height = getLatestHeight(__dropdown_menu_value_wrapper) + + # for fixing 1px bug + if isEven(label_height) is False: + self.value_ipady = (self.value_ipady[0], self.value_ipady[1] - 1) + + __dropdown_menu_value_wrapper.destroy() # ______________________________________ dropdown_menu_values_length = len(dropdown_menu_values) @@ -193,16 +205,14 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.new_height = int(self.max_display_length * label_height) - def makeEven(input_value): - return input_value + 1 if input_value % 2 == 1 else input_value - - + # for fixing 1px bug self.new_height = makeEven(self.new_height) self.new_width = makeEven(self.new_width) self.scroll_frame_container.configure(width=self.new_width, height=self.new_height) # This is for CustomTkinter's spec change or bug fix. self.scroll_frame_container._scrollbar.configure(height=0) + self.scroll_frame_container._scrollbar.configure(width=self.scrollbar_width) @@ -210,7 +220,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): for dropdown_menu_value in dropdown_menu_values: dropdown_menu_value_wrapper = CTkFrame(self.dropdown_menu_values_wrapper, corner_radius=0, fg_color=self.values_bg_color, width=0, height=0, cursor="hand2") - dropdown_menu_value_wrapper.grid(row=row, column=0, sticky="nsew") + dropdown_menu_value_wrapper.grid(row=row, column=0, pady=self.value_pady, sticky="nsew") setattr(self, f"{dropdown_menu_widget_id}__{row}", dropdown_menu_value_wrapper) @@ -228,7 +238,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): ) # setattr(self, f"l", label_widget) - label_widget.grid(row=1, column=0, padx=self.value_padx, pady=self.value_pady, sticky="w") + label_widget.grid(row=1, column=0, padx=self.value_ipadx, pady=self.value_ipady, sticky="w") bindEnterAndLeaveColor([dropdown_menu_value_wrapper, label_widget], self.values_hovered_bg_color, self.values_bg_color) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index df68a884..d95eed7f 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -158,8 +158,10 @@ class UiScalingManager(): self.config_window.SB__DROPDOWN_MENU_WIDTH = self.config_window.SB__OPTIONMENU_MIN_WIDTH self.config_window.SB__DROPDOWN_MENU_WINDOW_BORDER_WIDTH = self._calculateUiSize(1, is_allowed_odd=True) self.config_window.SB__DROPDOWN_MENU_SCROLLBAR_IPADX = (self._calculateUiSize(2), self._calculateUiSize(2)) - self.config_window.SB__DROPDOWN_MENU_VALUE_PADX = (self._calculateUiSize(8), 0) - self.config_window.SB__DROPDOWN_MENU_VALUE_PADY = self._calculateUiSize(6) + self.config_window.SB__DROPDOWN_MENU_SCROLLBAR_WIDTH = self._calculateUiSize(16) + self.config_window.SB__DROPDOWN_MENU_VALUE_IPADX = (self._calculateUiSize(8), 0) + self.config_window.SB__DROPDOWN_MENU_VALUE_IPADY = (self._calculateUiSize(6), self._calculateUiSize(6)) + self.config_window.SB__DROPDOWN_MENU_VALUE_PADY = (0, self._calculateUiSize(1, is_allowed_odd=True)) self.config_window.SB__DROPDOWN_MENU_VALUE_FONT_SIZE = self._calculateUiSize(14) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 7ca3d7ea..3798faa1 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -39,7 +39,9 @@ class VRCT_GUI(CTk): window_additional_y_pos=self.settings.config_window.uism.SB__DROPDOWN_MENU_WINDOW_ADDITIONAL_Y_POS, window_border_width=self.settings.config_window.uism.SB__DROPDOWN_MENU_WINDOW_BORDER_WIDTH, scrollbar_ipadx=self.settings.config_window.uism.SB__DROPDOWN_MENU_SCROLLBAR_IPADX, - value_padx=self.settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_PADX, + scrollbar_width=self.settings.config_window.uism.SB__DROPDOWN_MENU_SCROLLBAR_WIDTH, + value_ipadx=self.settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_IPADX, + value_ipady=self.settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_IPADY, value_pady=self.settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_PADY, value_font_size=self.settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_FONT_SIZE, From 8957e65e617777a6bc2512d0c9af6fea8571ddbf Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:34:10 +0900 Subject: [PATCH 242/355] =?UTF-8?q?[Update]=20Config=20Window:=20Error=20M?= =?UTF-8?q?essage=20Window.=20UI=20Size=E5=A4=89=E6=9B=B4=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C=E3=80=82=E8=89=B2=E6=8C=87=E5=AE=9A=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=95=B0=E5=8C=96=E3=80=82=20[bugfix]=20Config=20Window:=20Err?= =?UTF-8?q?or=20Message=20Window.=20Fix=201px=20bugs.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils.py | 4 +- vrct_gui/_CreateErrorWindow.py | 52 ++++++++++++++++++++--- vrct_gui/ui_managers/ColorThemeManager.py | 6 +++ vrct_gui/ui_managers/UiScalingManager.py | 4 ++ vrct_gui/vrct_gui.py | 9 ++++ 5 files changed, 68 insertions(+), 7 deletions(-) diff --git a/utils.py b/utils.py index 1a408523..5ed7bcd2 100644 --- a/utils.py +++ b/utils.py @@ -17,5 +17,7 @@ def callFunctionIfCallable(function, *args): def isEven(number): return number % 2 == 0 -def makeEven(number): +def makeEven(number, minus:bool=False): + if minus is True: + return number if isEven(number) else number - 1 return number if isEven(number) else number + 1 \ No newline at end of file diff --git a/vrct_gui/_CreateErrorWindow.py b/vrct_gui/_CreateErrorWindow.py index 8c576a1a..5a9029b8 100644 --- a/vrct_gui/_CreateErrorWindow.py +++ b/vrct_gui/_CreateErrorWindow.py @@ -1,8 +1,25 @@ from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont from time import sleep +from .ui_utils import getLatestWidth, getLatestHeight +from utils import isEven + + class _CreateErrorWindow(CTkToplevel): - def __init__(self, settings, view_variable, wrapper_widget): + def __init__( + self, + settings, + view_variable, + wrapper_widget, + + message_ipadx, + message_ipady, + message_font_size, + + message_bg_color, + message_text_color, + ): + super().__init__() self.withdraw() self.hide = True @@ -21,6 +38,13 @@ class _CreateErrorWindow(CTkToplevel): self.wrapper_widget = wrapper_widget + self.message_ipadx = message_ipadx + self.message_ipady = message_ipady + self.message_font_size = message_font_size + + self.message_bg_color = message_bg_color + self.message_text_color = message_text_color + self.attach_widget_width = None self.attach_widget_height = None @@ -34,8 +58,7 @@ class _CreateErrorWindow(CTkToplevel): self.rowconfigure(0,weight=1) self.columnconfigure(0,weight=1) - # The color code [#bb4448] is a mixture of [#a9555c] and [#cc3333] (for a redder shade). - self.modal_container = CTkFrame(self, corner_radius=0, fg_color="#bb4448", width=0, height=0) + self.modal_container = CTkFrame(self, corner_radius=0, fg_color=self.message_bg_color, width=0, height=0) self.modal_container.grid(row=0, column=0, sticky="nsew") @@ -45,11 +68,13 @@ class _CreateErrorWindow(CTkToplevel): textvariable=self._view_variable.VAR_ERROR_MESSAGE, height=0, corner_radius=0, - font=CTkFont(family=self.settings.FONT_FAMILY, size=12, weight="normal"), + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.message_font_size, weight="normal"), anchor="w", - text_color="white", + justify="left", + text_color=self.message_text_color, ) - self.modal_container_label_wrapper.grid(row=0, column=0, padx=10, pady=6, sticky="nsew") + self.modal_container_label_wrapper.grid(row=0, column=0, padx=self.message_ipadx, pady=self.message_ipady, sticky="nsew") + @@ -65,6 +90,21 @@ class _CreateErrorWindow(CTkToplevel): self.hide = False + label_width = getLatestWidth(self.modal_container_label_wrapper) + label_height = getLatestHeight(self.modal_container_label_wrapper) + + # for fixing 1px bug + if isEven(label_width) is False: + self.modal_container_label_wrapper.grid(padx=(self.message_ipadx[0], self.message_ipadx[1]-1)) + else: + self.modal_container_label_wrapper.grid(padx=self.message_ipadx) + + # for fixing 1px bug + if isEven(label_height) is False: + self.modal_container_label_wrapper.grid(pady=(self.message_ipady[0], self.message_ipady[1]-1)) + else: + self.modal_container_label_wrapper.grid(pady=self.message_ipady) + for i in range(0,101,20): if not self.winfo_exists(): diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 50ed0b17..eff12ae5 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -281,6 +281,12 @@ class ColorThemeManager(): self.config_window.SIDE_MENU_SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR + # Error Message Window for Config Window + # The color code [#bb4448] is a mixture of [#a9555c] and [#cc3333] (for a redder shade). + self.config_window.SB__ERROR_MESSAGE_BG_COLOR = "#bb4448" + self.config_window.SB__ERROR_MESSAGE_TEXT_COLOR = "#fff" + + diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index d95eed7f..6389d995 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -143,6 +143,10 @@ class UiScalingManager(): self.config_window.SB__DESC_TOP_PADY = self._calculateUiSize(2) + self.config_window.SB__ERROR_MESSAGE_IPADX = (self._calculateUiSize(10), self._calculateUiSize(10)) + self.config_window.SB__ERROR_MESSAGE_IPADY = (self._calculateUiSize(6), self._calculateUiSize(6)) + self.config_window.SB__ERROR_MESSAGE_FONT_SIZE = self._calculateUiSize(12) + self.config_window.SB__SELECTOR_FONT_SIZE = self._calculateUiSize(14) self.config_window.SB__RADIO_BUTTON_FONT_SIZE = self.config_window.SB__SELECTOR_FONT_SIZE diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 3798faa1..9f07392c 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -36,6 +36,7 @@ class VRCT_GUI(CTk): self.dropdown_menu_window = _CreateDropdownMenuWindow( settings=self.settings.config_window, view_variable=self._view_variable, + window_additional_y_pos=self.settings.config_window.uism.SB__DROPDOWN_MENU_WINDOW_ADDITIONAL_Y_POS, window_border_width=self.settings.config_window.uism.SB__DROPDOWN_MENU_WINDOW_BORDER_WIDTH, scrollbar_ipadx=self.settings.config_window.uism.SB__DROPDOWN_MENU_SCROLLBAR_IPADX, @@ -75,6 +76,14 @@ class VRCT_GUI(CTk): settings=self.settings.modal_window, view_variable=self._view_variable, wrapper_widget=self.config_window.main_bg_container, + + message_ipadx=self.settings.config_window.uism.SB__ERROR_MESSAGE_IPADX, + message_ipady=self.settings.config_window.uism.SB__ERROR_MESSAGE_IPADY, + message_font_size=self.settings.config_window.uism.SB__ERROR_MESSAGE_FONT_SIZE, + + message_bg_color=self.settings.config_window.ctm.SB__ERROR_MESSAGE_BG_COLOR, + message_text_color=self.settings.config_window.ctm.SB__ERROR_MESSAGE_TEXT_COLOR, + ) From 7fe1cb329a1aaaeba346cf8cbb0ff393e6da0802 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 14:42:06 +0900 Subject: [PATCH 243/355] =?UTF-8?q?[Refactor]=20=E5=91=BC=E3=81=B3?= =?UTF-8?q?=E5=87=BA=E3=81=97=E9=96=A2=E6=95=B0=E5=90=8D=E7=B5=B1=E4=B8=80?= =?UTF-8?q?=20.rowconfigure=20.columnconfigure=20=E3=82=92=20.grid=5Frowco?= =?UTF-8?q?nfigure=20.grid=5Fcolumnconfigure=20=E3=81=A8=E7=B5=B1=E4=B8=80?= =?UTF-8?q?=E3=80=82=20(.rowconfigure=20.columnconfigure=20=E5=81=B4?= =?UTF-8?q?=E3=81=AB=E7=B5=B1=E4=B8=80=E3=81=97=E3=81=AA=E3=81=8B=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E7=90=86=E7=94=B1=E3=81=AF=E3=80=81.configure?= =?UTF-8?q?=E9=96=A2=E6=95=B0=E3=81=A8=E5=A4=A7=E3=81=8D=E3=81=8F=E5=8C=BA?= =?UTF-8?q?=E5=88=A5=E3=81=99=E3=82=8B=E3=81=9F=E3=82=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateErrorWindow.py | 4 ++-- vrct_gui/_CreateModalWindow.py | 4 ++-- vrct_gui/_CreateSelectableLanguagesWindow.py | 16 ++++++++-------- .../_SettingBoxGenerator.py | 2 +- vrct_gui/main_window/createMainWindowWidgets.py | 4 ++-- .../createSidebarLanguagesSettings.py | 2 +- .../widgets/create_entry_message_box.py | 2 +- vrct_gui/main_window/widgets/create_textbox.py | 2 +- 8 files changed, 18 insertions(+), 18 deletions(-) diff --git a/vrct_gui/_CreateErrorWindow.py b/vrct_gui/_CreateErrorWindow.py index 5a9029b8..f92e47f6 100644 --- a/vrct_gui/_CreateErrorWindow.py +++ b/vrct_gui/_CreateErrorWindow.py @@ -55,8 +55,8 @@ class _CreateErrorWindow(CTkToplevel): - self.rowconfigure(0,weight=1) - self.columnconfigure(0,weight=1) + self.grid_rowconfigure(0,weight=1) + self.grid_columnconfigure(0,weight=1) self.modal_container = CTkFrame(self, corner_radius=0, fg_color=self.message_bg_color, width=0, height=0) self.modal_container.grid(row=0, column=0, sticky="nsew") diff --git a/vrct_gui/_CreateModalWindow.py b/vrct_gui/_CreateModalWindow.py index 8aa6cb64..17dcd421 100644 --- a/vrct_gui/_CreateModalWindow.py +++ b/vrct_gui/_CreateModalWindow.py @@ -31,8 +31,8 @@ class _CreateModalWindow(CTkToplevel): self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, self.x_pos, self.y_pos)) - self.rowconfigure(0,weight=1) - self.columnconfigure(0,weight=1) + self.grid_rowconfigure(0,weight=1) + self.grid_columnconfigure(0,weight=1) self.modal_container = CTkFrame(self, corner_radius=0, fg_color="black", width=0, height=0) self.modal_container.grid(row=0, column=0, sticky="nsew") diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index dbac7fc9..8b559a7a 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -70,15 +70,15 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): def _createContainer(self): - self.rowconfigure(0, minsize=50) - self.rowconfigure(1, weight=1) - self.columnconfigure(0, weight=1) + self.grid_rowconfigure(0, minsize=50) + self.grid_rowconfigure(1, weight=1) + self.grid_columnconfigure(0, weight=1) self.top_container = CTkFrame(self, corner_radius=0, fg_color=self.settings.ctm.TOP_BG_COLOR, width=0, height=0) self.top_container.grid(row=0, column=0, sticky="nsew") - self.top_container.rowconfigure((0,2), weight=1) - self.top_container.columnconfigure(1, weight=1) + self.top_container.grid_rowconfigure((0,2), weight=1) + self.top_container.grid_columnconfigure(1, weight=1) self.go_back_button_container = CTkFrame(self.top_container, corner_radius=0, fg_color=self.settings.ctm.GO_BACK_BUTTON_BG_COLOR, width=0, height=0, cursor="hand2") self.go_back_button_container.grid(row=1, column=0) @@ -105,8 +105,8 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.title_container = CTkFrame(self.top_container, corner_radius=0, fg_color=self.settings.ctm.TOP_BG_COLOR, width=0, height=0) self.title_container.grid(row=1, column=1, sticky="nsew") - self.title_container.columnconfigure((0,2), weight=1) - self.title_container.rowconfigure((0,2), weight=1) + self.title_container.grid_columnconfigure((0,2), weight=1) + self.title_container.grid_rowconfigure((0,2), weight=1) self.title_label = CTkLabel( self.title_container, textvariable=self._view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE, @@ -143,7 +143,7 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): - self.wrapper.rowconfigure((0,2), weight=1) + self.wrapper.grid_rowconfigure((0,2), weight=1) selectable_language_name_for_text = selectable_language_name.replace("\n", " ") label_widget = CTkLabel( self.wrapper, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index b2d25d9d..0abcc4c4 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -40,7 +40,7 @@ class _SettingBoxGenerator(): setting_box_item_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) setting_box_item_frame.grid(row=0, column=2, padx=0, sticky="nsew") - setting_box_item_frame.rowconfigure((0,2), weight=1) + setting_box_item_frame.grid_rowconfigure((0,2), weight=1) setting_box_item_frame.grid_columnconfigure(0, weight=1) return (setting_box_frame, setting_box_item_frame) diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 06e2783e..78436481 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -35,7 +35,7 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): - vrct_gui.main_topbar_container.columnconfigure(1,weight=1) + vrct_gui.main_topbar_container.grid_columnconfigure(1,weight=1) vrct_gui.main_topbar_center_container = CTkFrame(vrct_gui.main_topbar_container, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) vrct_gui.main_topbar_center_container.grid(row=0, column=1, sticky="nsew") @@ -88,7 +88,7 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): main_topbar_column+=1 - vrct_gui.update_available_container.rowconfigure((0,2), weight=1) + vrct_gui.update_available_container.grid_rowconfigure((0,2), weight=1) vrct_gui.update_available_icon = CTkLabel( vrct_gui.update_available_container, diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 0f22ffae..de928a32 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -51,7 +51,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): def createLanguageSettingBox(parent_widget, var_title_text, title_text_attr_name, arrow_img_attr_name, open_selectable_language_window_command, variable): sls__box = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) - sls__box.columnconfigure(1, weight=1) + sls__box.grid_columnconfigure(1, weight=1) sls__box_wrapper = CTkFrame(sls__box, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) sls__box_wrapper.grid(row=2, column=1, padx=10, pady=settings.uism.SLS__BOX_IPADY, sticky="ew") diff --git a/vrct_gui/main_window/widgets/create_entry_message_box.py b/vrct_gui/main_window/widgets/create_entry_message_box.py index 0e95c550..e583234f 100644 --- a/vrct_gui/main_window/widgets/create_entry_message_box.py +++ b/vrct_gui/main_window/widgets/create_entry_message_box.py @@ -5,7 +5,7 @@ def createEntryMessageBox(settings, main_window): main_window.main_entry_message_container.grid(row=2, column=0, sticky="ew") - main_window.main_entry_message_container.columnconfigure(0, weight=1) + main_window.main_entry_message_container.grid_columnconfigure(0, weight=1) main_window.entry_message_box = CTkEntry( main_window.main_entry_message_container, border_color=settings.ctm.TEXTBOX_ENTRY_BORDER_COLOR, diff --git a/vrct_gui/main_window/widgets/create_textbox.py b/vrct_gui/main_window/widgets/create_textbox.py index 891e78cb..ce1f2e7a 100644 --- a/vrct_gui/main_window/widgets/create_textbox.py +++ b/vrct_gui/main_window/widgets/create_textbox.py @@ -114,7 +114,7 @@ def createTextbox(settings, main_window, view_variable): - target_widget.columnconfigure((0,2), weight=1) + target_widget.grid_columnconfigure((0,2), weight=1) setattr(main_window, "label_widget", CTkLabel( target_widget, textvariable=textbox_setting["textvariable"], From 0b5b3a62fe3ca31c33f54adf3230a60259d9f928 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 16:14:31 +0900 Subject: [PATCH 244/355] =?UTF-8?q?[Update]=20Main=20Window:=20Language=20?= =?UTF-8?q?Selectable=20Window.=20UI=20Size=E5=A4=89=E6=9B=B4=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 2 +- vrct_gui/_CreateModalWindow.py | 2 +- vrct_gui/_CreateSelectableLanguagesWindow.py | 21 ++++++++++++-------- vrct_gui/ui_managers/UiScalingManager.py | 17 ++++++++++++++++ 4 files changed, 32 insertions(+), 10 deletions(-) diff --git a/view.py b/view.py index 297b2fcb..aea0c93b 100644 --- a/view.py +++ b/view.py @@ -49,7 +49,7 @@ class View(): self.settings.selectable_language_window = SimpleNamespace( ctm=all_ctm.selectable_language_window, - uism=all_uism.config_window, + uism=all_uism.selectable_language_window, **common_args ) diff --git a/vrct_gui/_CreateModalWindow.py b/vrct_gui/_CreateModalWindow.py index 17dcd421..2e4ba26b 100644 --- a/vrct_gui/_CreateModalWindow.py +++ b/vrct_gui/_CreateModalWindow.py @@ -29,7 +29,7 @@ class _CreateModalWindow(CTkToplevel): self.height_new = self.attach_window.winfo_height() - self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, self.x_pos, self.y_pos)) + self.geometry("{}x{}+{}+{}".format(self.width_new, self.height_new, self.x_pos, self.y_pos)) self.grid_rowconfigure(0,weight=1) self.grid_columnconfigure(0,weight=1) diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index 8b559a7a..8ca5810c 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -43,7 +43,7 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.height_new = self.attach.winfo_height() - self.geometry('{}x{}+{}+{}'.format(self.width_new, self.height_new, self.x_pos, self.y_pos)) + self.geometry("{}x{}+{}+{}".format(self.width_new, self.height_new, self.x_pos, self.y_pos)) @@ -70,7 +70,7 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): def _createContainer(self): - self.grid_rowconfigure(0, minsize=50) + self.grid_rowconfigure(0, minsize=self.settings.uism.TOP_BAR_MIN_HEIGHT) self.grid_rowconfigure(1, weight=1) self.grid_columnconfigure(0, weight=1) self.top_container = CTkFrame(self, corner_radius=0, fg_color=self.settings.ctm.TOP_BG_COLOR, width=0, height=0) @@ -87,11 +87,11 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): textvariable=self._view_variable.VAR_GO_BACK_LABEL_SELECTABLE_LANGUAGE, height=0, corner_radius=0, - font=CTkFont(family=self.settings.FONT_FAMILY, size=14, weight="normal"), + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.GO_BACK_BUTTON_LABEL_FONT_SIZE, weight="normal"), anchor="w", text_color=self.settings.ctm.BASIC_TEXT_COLOR, ) - self.go_back_button_label.grid(row=0, column=0, padx=10, pady=8) + self.go_back_button_label.grid(row=0, column=0, padx=self.settings.uism.GO_BACK_BUTTON_IPADX, pady=self.settings.uism.GO_BACK_BUTTON_IPADY) bindEnterAndLeaveColor([self.go_back_button_container, self.go_back_button_label], self.settings.ctm.GO_BACK_BUTTON_BG_HOVERED_COLOR, self.settings.ctm.GO_BACK_BUTTON_BG_COLOR) @@ -112,7 +112,7 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): textvariable=self._view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE, height=0, corner_radius=0, - font=CTkFont(family=self.settings.FONT_FAMILY, size=18, weight="normal"), + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.TITLE_FONT_SIZE, weight="normal"), anchor="w", text_color=self.settings.ctm.TITLE_TEXT_COLOR, ) @@ -124,6 +124,11 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.scroll_frame_container = CTkScrollableFrame(self, corner_radius=0, fg_color=self.settings.ctm.MAIN_BG_COLOR, width=self.width_new, height=self.height_new) self.scroll_frame_container.grid(row=1, column=0, sticky="nsew") + self.scroll_frame_container._scrollbar.grid_configure(padx=self.settings.uism.SCROLLBAR_IPADX) + + # This is for CustomTkinter's spec change or bug fix. + self.scroll_frame_container._scrollbar.configure(height=0) + self.scroll_frame_container._scrollbar.configure(width=self.settings.uism.SCROLLBAR_WIDTH) self.container = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.settings.ctm.MAIN_BG_COLOR, width=0, height=0) @@ -138,7 +143,7 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): for selectable_language_name in self._view_variable.LIST_SELECTABLE_LANGUAGES: self.wrapper = CTkFrame(self.container, corner_radius=0, fg_color=self.settings.ctm.LANGUAGE_BUTTON_BG_COLOR, width=0, height=0, cursor="hand2") - self.wrapper.grid(row=row, column=column, ipadx=6, ipady=6, sticky="nsew") + self.wrapper.grid(row=row, column=column, sticky="nsew") setattr(self, f"{row}_{column}", self.wrapper) @@ -150,13 +155,13 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): text=selectable_language_name_for_text, height=0, corner_radius=0, - font=CTkFont(family=self.settings.FONT_FAMILY, size=14, weight="normal"), + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.VALUES_TEXT_FONT_SIZE, weight="normal"), anchor="w", text_color=self.settings.ctm.BASIC_TEXT_COLOR, ) # setattr(self, f"l", label_widget) - label_widget.grid(row=1, column=0, padx=(8,0)) + label_widget.grid(row=1, column=0, padx=self.settings.uism.VALUES_TEXT_IPADX, pady=self.settings.uism.VALUES_TEXT_IPADY) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 6389d995..2e338d69 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -6,6 +6,7 @@ class UiScalingManager(): self.SCALING_FLOAT = max(scaling_float, 0.4) self.main = SimpleNamespace() self.config_window = SimpleNamespace() + self.selectable_language_window = SimpleNamespace() self.modal_window = SimpleNamespace() self._calculatedUiSizes() @@ -102,6 +103,22 @@ class UiScalingManager(): self.main.MINIMIZE_SIDEBAR_BUTTON_ICON_SIZE_Y = self._calculateUiSize(26) + + # Selectable Language Window + self.selectable_language_window.TOP_BAR_MIN_HEIGHT = self._calculateUiSize(50) + self.selectable_language_window.SCROLLBAR_IPADX = (self._calculateUiSize(2), self._calculateUiSize(2)) + self.selectable_language_window.SCROLLBAR_WIDTH = self._calculateUiSize(16) + + self.selectable_language_window.GO_BACK_BUTTON_LABEL_FONT_SIZE = self._calculateUiSize(14) + self.selectable_language_window.GO_BACK_BUTTON_IPADX = self._calculateUiSize(10) + self.selectable_language_window.GO_BACK_BUTTON_IPADY = self._calculateUiSize(8) + self.selectable_language_window.TITLE_FONT_SIZE = self._calculateUiSize(18) + + self.selectable_language_window.VALUES_TEXT_FONT_SIZE = self._calculateUiSize(14) + self.selectable_language_window.VALUES_TEXT_IPADX = (self._calculateUiSize(8), 0) + self.selectable_language_window.VALUES_TEXT_IPADY = self._calculateUiSize(8) + + self.modal_window.TEXT_FONT_SIZE = self._calculateUiSize(20) # Top bar common From 998fa6d3dc5a886378668c7473bec34c54957cdc Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 16:48:12 +0900 Subject: [PATCH 245/355] [bugfix] Main Window: Fix the config button size when it is disabled. It changed size unexpectedly before. --- vrct_gui/_changeMainWindowWidgetsStatus.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index 43599b6e..6093ff43 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -102,12 +102,12 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta if status == "disabled": vrct_gui.sidebar_config_button_wrapper.configure(cursor="") vrct_gui.sidebar_config_button.configure( - image=CTkImage((settings.image_file.CONFIGURATION_ICON_DISABLED)), + image=CTkImage(settings.image_file.CONFIGURATION_ICON_DISABLED, size=COMPACT_MODE_ICON_SIZE_TUPLES), ) elif status == "normal": vrct_gui.sidebar_config_button_wrapper.configure(cursor="hand2") vrct_gui.sidebar_config_button.configure( - image=CTkImage((settings.image_file.CONFIGURATION_ICON)), + image=CTkImage(settings.image_file.CONFIGURATION_ICON, size=COMPACT_MODE_ICON_SIZE_TUPLES), ) From ee524bc57e5bc82049a0d81e756ba4bdb70d7d7a Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 18:28:06 +0900 Subject: [PATCH 246/355] =?UTF-8?q?[Update]=20Main=20Window:=20UI=20Size?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E5=AF=BE=E5=BF=9C.=20Sidebar=20Preset=20tabs?= =?UTF-8?q?,=20Option=20Menu's=20arrow=20images=20and=20Textbox(=5FptintTo?= =?UTF-8?q?Textbox.py).=20[Refactor]=20Main=20Window:=20Textbox(=5FptintTo?= =?UTF-8?q?Textbox.py)=20=E5=BF=85=E8=A6=81=E3=81=AE=E3=81=AA=E3=81=84?= =?UTF-8?q?=E3=82=B3=E3=83=BC=E3=83=89=E3=81=AE=E5=89=8A=E9=99=A4=E3=81=A8?= =?UTF-8?q?=E7=B5=B1=E5=90=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_printToTextbox.py | 38 ++++++++----------- .../createSidebarLanguagesSettings.py | 14 +++---- vrct_gui/ui_managers/UiScalingManager.py | 17 ++++++++- vrct_gui/vrct_gui.py | 8 ++-- 4 files changed, 42 insertions(+), 35 deletions(-) diff --git a/vrct_gui/_printToTextbox.py b/vrct_gui/_printToTextbox.py index cb732f81..e99ab1c4 100644 --- a/vrct_gui/_printToTextbox.py +++ b/vrct_gui/_printToTextbox.py @@ -27,32 +27,26 @@ def _printToTextbox(vrct_gui, settings, target_type, original_message=None, tran # common tag settings # target_textbox._textbox.tag_configure("START", spacing1=16) - target_textbox._textbox.tag_configure("LABEL", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) - target_textbox._textbox.tag_configure("TIMESTAMP", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal"), foreground=settings.ctm.TEXTBOX_TIMESTAMP_TEXT_COLOR) - target_textbox._textbox.tag_configure("SECONDARY_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) - target_textbox._textbox.tag_configure("MAIN_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=16, weight="normal")) + target_textbox._textbox.tag_configure("LABEL", font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_FONT_SIZE__LABEL, weight="normal")) + target_textbox._textbox.tag_configure("TIMESTAMP", font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_FONT_SIZE__TIMESTAMP, weight="normal"), foreground=settings.ctm.TEXTBOX_TIMESTAMP_TEXT_COLOR) + target_textbox._textbox.tag_configure("SECONDARY_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_FONT_SIZE__SECONDARY_TEXT_FONT, weight="normal")) + target_textbox._textbox.tag_configure("MAIN_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_FONT_SIZE__MAIN_TEXT_FONT, weight="normal")) # System Tag Settings - target_textbox.tag_config("SYSTEM_FOR_FIRST_INSERT", spacing1=16) + target_textbox.tag_config("FIRST_INSERT_SPACING", spacing1=settings.uism.TEXTBOX_FIRST_INSERT_SPACING) target_textbox.tag_config("SYSTEM_TAG", foreground=settings.ctm.TEXTBOX_SYSTEM_TAG_TEXT_COLOR) target_textbox.tag_config("SYSTEM_TEXT", foreground=settings.ctm.TEXTBOX_TEXT_SUB_COLOR) - target_textbox._textbox.tag_configure("SYSTEM_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) + target_textbox._textbox.tag_configure("SYSTEM_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TEXTBOX_FONT_SIZE__SYSTEM_TEXT_FONT, weight="normal")) # Sent Tag Settings - target_textbox.tag_config("SENT_FOR_FIRST_INSERT", spacing1=16) target_textbox.tag_config("SENT_TAG", foreground=settings.ctm.TEXTBOX_SENT_TAG_TEXT_COLOR) target_textbox.tag_config("SENT_TEXT", foreground=settings.ctm.TEXTBOX_TEXT_COLOR) target_textbox.tag_config("SENT_SUB_TEXT", foreground=settings.ctm.TEXTBOX_TEXT_SUB_COLOR) - target_textbox._textbox.tag_configure("SENT_MAIN_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=16, weight="normal")) - target_textbox._textbox.tag_configure("SENT_SECONDARY_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) # Received Tag Settings - target_textbox.tag_config("RECEIVED_FOR_FIRST_INSERT", spacing1=16) target_textbox.tag_config("RECEIVED_TAG", foreground=settings.ctm.TEXTBOX_RECEIVED_TAG_TEXT_COLOR) target_textbox.tag_config("RECEIVED_TEXT", foreground=settings.ctm.TEXTBOX_TEXT_COLOR) target_textbox.tag_config("RECEIVED_SUB_TEXT", foreground=settings.ctm.TEXTBOX_TEXT_SUB_COLOR) - target_textbox._textbox.tag_configure("RECEIVED_MAIN_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=16, weight="normal")) - target_textbox._textbox.tag_configure("RECEIVED_SECONDARY_TEXT_FONT", font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal")) FAKE_MARGIN = " " # insert @@ -60,32 +54,32 @@ def _printToTextbox(vrct_gui, settings, target_type, original_message=None, tran target_textbox.insert("end", "\n") match (target_type): case "SYSTEM": - target_textbox.insert("end", "System", ("SYSTEM_TAG", "SYSTEM_FOR_FIRST_INSERT", "JUSTIFY_CENTER")) + target_textbox.insert("end", "System", ("SYSTEM_TAG", "FIRST_INSERT_SPACING", "JUSTIFY_CENTER", "LABEL")) target_textbox.insert("end", FAKE_MARGIN+original_message+FAKE_MARGIN, ("SYSTEM_TEXT", "SYSTEM_TEXT_FONT", "JUSTIFY_CENTER")) target_textbox.insert("end", now_hm, ("TIMESTAMP", "JUSTIFY_CENTER")) case "SENT": - target_textbox.insert("end", now_hm, ("TIMESTAMP", "SENT_FOR_FIRST_INSERT", "JUSTIFY_RIGHT")) - target_textbox.insert("end", FAKE_MARGIN+"Sent", ("SENT_TAG")) + target_textbox.insert("end", now_hm, ("TIMESTAMP", "FIRST_INSERT_SPACING", "JUSTIFY_RIGHT")) + target_textbox.insert("end", FAKE_MARGIN+"Sent", ("SENT_TAG", "LABEL")) target_textbox.insert("end", "\n") if is_only_one_message is False: - target_textbox.insert("end", original_message, ("SENT_SUB_TEXT", "SENT_SECONDARY_TEXT_FONT", "JUSTIFY_RIGHT")) + target_textbox.insert("end", original_message, ("SENT_SUB_TEXT", "SECONDARY_TEXT_FONT", "JUSTIFY_RIGHT")) target_textbox.insert("end", "\n") - target_textbox.insert("end", translated_message, ("SENT_TEXT", "SENT_MAIN_TEXT_FONT", "JUSTIFY_RIGHT")) + target_textbox.insert("end", translated_message, ("SENT_TEXT", "MAIN_TEXT_FONT", "JUSTIFY_RIGHT")) else: - target_textbox.insert("end", original_message, ("SENT_TEXT", "SENT_MAIN_TEXT_FONT", "JUSTIFY_RIGHT")) + target_textbox.insert("end", original_message, ("SENT_TEXT", "MAIN_TEXT_FONT", "JUSTIFY_RIGHT")) case "RECEIVED": - target_textbox.insert("end", "Received", ("RECEIVED_TAG", "RECEIVED_FOR_FIRST_INSERT", "JUSTIFY_LEFT")) + target_textbox.insert("end", "Received", ("RECEIVED_TAG", "FIRST_INSERT_SPACING", "JUSTIFY_LEFT", "LABEL")) target_textbox.insert("end", FAKE_MARGIN+now_hm, ("TIMESTAMP")) if is_only_one_message is False: target_textbox.insert("end", "\n") - target_textbox.insert("end", original_message, ("RECEIVED_SUB_TEXT", "RECEIVED_SECONDARY_TEXT_FONT")) + target_textbox.insert("end", original_message, ("RECEIVED_SUB_TEXT", "SECONDARY_TEXT_FONT")) target_textbox.insert("end", "\n") - target_textbox.insert("end", translated_message, ("RECEIVED_TEXT", "RECEIVED_MAIN_TEXT_FONT", "JUSTIFY_LEFT")) + target_textbox.insert("end", translated_message, ("RECEIVED_TEXT", "MAIN_TEXT_FONT", "JUSTIFY_LEFT")) else: target_textbox.insert("end", "\n") - target_textbox.insert("end", original_message, ("RECEIVED_TEXT", "RECEIVED_MAIN_TEXT_FONT", "JUSTIFY_LEFT")) + target_textbox.insert("end", original_message, ("RECEIVED_TEXT", "MAIN_TEXT_FONT", "JUSTIFY_LEFT")) target_textbox.configure(state="disabled") target_textbox.see("end") diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index de928a32..132cd86b 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -54,7 +54,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): sls__box.grid_columnconfigure(1, weight=1) sls__box_wrapper = CTkFrame(sls__box, corner_radius=0, fg_color=settings.ctm.SLS__BOX_BG_COLOR, width=0, height=0) - sls__box_wrapper.grid(row=2, column=1, padx=10, pady=settings.uism.SLS__BOX_IPADY, sticky="ew") + sls__box_wrapper.grid(row=2, column=1, padx=settings.uism.SLS__BOX_IPADX, pady=settings.uism.SLS__BOX_IPADY, sticky="ew") sls__box_wrapper.grid_columnconfigure(0, weight=1) @@ -86,13 +86,13 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): optionmenu_hovered_bg_color=settings.ctm.SLS__OPTIONMENU_HOVERED_BG_COLOR, optionmenu_clicked_bg_color=settings.ctm.SLS__OPTIONMENU_CLICKED_BG_COLOR, optionmenu_ipadx=(0,0), - optionmenu_ipady=2, + optionmenu_ipady=settings.uism.SLS__BOX_OPTION_MENU_IPADY, variable=variable, font_family=settings.FONT_FAMILY, - font_size=settings.uism.SLS__BOX_DROPDOWN_MENU_FONT_SIZE, + font_size=settings.uism.SLS__BOX_OPTION_MENU_FONT_SIZE, text_color=settings.ctm.LABELS_TEXT_COLOR, image_file=settings.image_file.ARROW_LEFT.rotate(180), - image_size=(20,20), + image_size=settings.uism.SLS__BOX_OPTION_MENU_ARROW_IMAGE_SIZE, optionmenu_clicked_command=open_selectable_language_window_command, optionmenu_position="center", @@ -127,7 +127,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): # Presets buttons main_window.sidebar_bg_container.grid_rowconfigure(2, weight=1) - main_window.sls__presets_buttons_container = CTkFrame(main_window.sls__container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=30) + main_window.sls__presets_buttons_container = CTkFrame(main_window.sls__container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=settings.uism.SLS__PRESET_TAB_NUMBER_HEIGHT) main_window.sls__presets_buttons_container.grid(row=1, column=0, sticky="nsew") main_window.sls__presets_buttons_box = CTkFrame(main_window.sls__presets_buttons_container, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) @@ -165,10 +165,10 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): preset_tab_attr_name, CTkFrame( main_window.sls__presets_buttons_box, - corner_radius=6, + corner_radius=settings.uism.SLS__PRESET_TAB_NUMBER_CORNER_RADIUS, fg_color=settings.ctm.SLS__PRESETS_TAB_BG_PASSIVE_COLOR, width=0, - height=36, + height=settings.uism.SLS__PRESET_TAB_NUMBER_ADJUSTED_HEIGHT, cursor="hand2", ) ) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 2e338d69..b38874f0 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -31,6 +31,13 @@ class UiScalingManager(): self.main.TEXTBOX_TAB_PADX = self._calculateUiSize(10) self.main.TEXTBOX_TAB_PADY = (self._calculateUiSize(4), self._calculateUiSize(10)) + self.main.TEXTBOX_FIRST_INSERT_SPACING = self._calculateUiSize(16) + self.main.TEXTBOX_FONT_SIZE__LABEL = self._calculateUiSize(12) + self.main.TEXTBOX_FONT_SIZE__TIMESTAMP = self._calculateUiSize(12) + self.main.TEXTBOX_FONT_SIZE__SYSTEM_TEXT_FONT = self._calculateUiSize(12) + self.main.TEXTBOX_FONT_SIZE__SECONDARY_TEXT_FONT = self._calculateUiSize(12) + self.main.TEXTBOX_FONT_SIZE__MAIN_TEXT_FONT = self._calculateUiSize(16) + self.main.TEXTBOX_ENTRY_FONT_SIZE = self._calculateUiSize(16) self.main.TEXTBOX_ENTRY_HEIGHT = self._calculateUiSize(40) self.main.TEXTBOX_ENTRY_PADX = self.main.TEXTBOX_PADX @@ -65,12 +72,18 @@ class UiScalingManager(): self.main.SLS__TITLE_PADY = (self._calculateUiSize(12), self._calculateUiSize(6)) self.main.SLS__PRESET_TAB_NUMBER_FONT_SIZE = self._calculateUiSize(16) + self.main.SLS__PRESET_TAB_NUMBER_HEIGHT = self._calculateUiSize(30) + self.main.SLS__PRESET_TAB_NUMBER_CORNER_RADIUS = self._calculateUiSize(6) + self.main.SLS__PRESET_TAB_NUMBER_ADJUSTED_HEIGHT = self._calculateUiSize(36) self.main.SLS__BOX_SECTION_TITLE_FONT_SIZE = self._calculateUiSize(16) self.main.SLS__BOX_SECTION_TITLE_BOTTOM_PADY = self._calculateUiSize(10) + self.main.SLS__BOX_IPADX = self._calculateUiSize(10) self.main.SLS__BOX_IPADY = (self._calculateUiSize(8),self._calculateUiSize(18)) - self.main.SLS__BOX_DROPDOWN_MENU_FONT_SIZE = self._calculateUiSize(14) - self.main.SLS__BOX_DROPDOWN_MENU_WIDTH = self._calculateUiSize(200) + self.main.SLS__BOX_OPTION_MENU_FONT_SIZE = self._calculateUiSize(14) + self.main.SLS__BOX_OPTION_MENU_IPADY = self._calculateUiSize(2) + self.main.SLS__BOX_OPTION_MENU_ARROW_IMAGE_SIZE = (self._calculateUiSize(20), self._calculateUiSize(20)) + # self.main.SLS__BOX_OPTION_MENU_WIDTH = self._calculateUiSize(200) self.main.SLS__BOX_ARROWS_PADY = self._calculateUiSize(10) self.main.SLS__BOX_ARROWS_IMAGE_SIZE = self.dupTuple(self._calculateUiSize(16)) self.main.SLS__BOX_ARROWS_DESC_FONT_SIZE = self._calculateUiSize(12) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 9f07392c..6102937b 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -126,7 +126,7 @@ class VRCT_GUI(CTk): # print("target", self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW) if selectable_language_window_type == "your_language": if self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW is False: - self.sls__arrow_img_your_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT),size=(20,20))) + self.sls__arrow_img_your_language.configure(image=CTkImage(self.settings.main.image_file.ARROW_LEFT, size=self.settings.main.uism.SLS__BOX_OPTION_MENU_ARROW_IMAGE_SIZE)) self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW = True self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW = False else: @@ -135,7 +135,7 @@ class VRCT_GUI(CTk): elif selectable_language_window_type == "target_language": if self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW is False: - self.sls__arrow_img_target_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT),size=(20,20))) + self.sls__arrow_img_target_language.configure(image=CTkImage(self.settings.main.image_file.ARROW_LEFT, size=self.settings.main.uism.SLS__BOX_OPTION_MENU_ARROW_IMAGE_SIZE)) self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW = True self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW = False else: @@ -150,8 +150,8 @@ class VRCT_GUI(CTk): def closeSelectableLanguagesWindow(self): - self.sls__arrow_img_your_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT).rotate(180),size=(20,20))) - self.sls__arrow_img_target_language.configure(image=CTkImage((self.settings.main.image_file.ARROW_LEFT).rotate(180),size=(20,20))) + self.sls__arrow_img_your_language.configure(image=CTkImage(self.settings.main.image_file.ARROW_LEFT.rotate(180), size=self.settings.main.uism.SLS__BOX_OPTION_MENU_ARROW_IMAGE_SIZE)) + self.sls__arrow_img_target_language.configure(image=CTkImage(self.settings.main.image_file.ARROW_LEFT.rotate(180), size=self.settings.main.uism.SLS__BOX_OPTION_MENU_ARROW_IMAGE_SIZE)) self.selectable_languages_window.withdraw() From 7d33a6dff291d4ea1176e33ef3d9ccde5605857f Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 18:48:28 +0900 Subject: [PATCH 247/355] =?UTF-8?q?[Update]=20UI=20Size=E5=A4=89=E6=9B=B4?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C.=20Main=20Window=20Root=20Geometry.=20Config?= =?UTF-8?q?=20Window=20Root=20Geometry(tmp)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/config_window/ConfigWindow.py | 2 +- vrct_gui/main_window/createMainWindowWidgets.py | 5 ++--- vrct_gui/ui_managers/UiScalingManager.py | 6 ++++++ 3 files changed, 9 insertions(+), 4 deletions(-) diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 4e469de2..81247055 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -14,7 +14,7 @@ class ConfigWindow(CTkToplevel): # configure window self.after(200, lambda: self.iconbitmap(getImagePath("vrct_logo_mark_black.ico"))) self.title("Settings") - self.geometry(f"{1080}x{680}") + self.geometry(f"{settings.uism.DEFAULT_WIDTH}x{settings.uism.DEFAULT_HEIGHT}") self.configure(fg_color="#ff7f50") diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 78436481..c3707f93 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -11,12 +11,11 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): vrct_gui.iconbitmap(getImagePath("vrct_logo_mark_black.ico")) vrct_gui.title("VRCT") - vrct_gui.geometry(f"{880}x{640}") - vrct_gui.minsize(400, 175) + vrct_gui.minsize(200, 200) # Main Container - vrct_gui.grid_columnconfigure(1, weight=1) + vrct_gui.grid_columnconfigure(1, weight=1, minsize=settings.uism.MAIN_AREA_MIN_WIDTH) vrct_gui.configure(fg_color="#ff7f50") diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index b38874f0..d2fcb257 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -23,6 +23,8 @@ class UiScalingManager(): # Main + self.main.MAIN_AREA_MIN_WIDTH = self._calculateUiSize(640) + self.main.TEXTBOX_PADX = self._calculateUiSize(16) self.main.TEXTBOX_CORNER_RADIUS = self._calculateUiSize(6) @@ -134,6 +136,10 @@ class UiScalingManager(): self.modal_window.TEXT_FONT_SIZE = self._calculateUiSize(20) + # Config Window + self.config_window.DEFAULT_WIDTH = self._calculateUiSize(1080) + self.config_window.DEFAULT_HEIGHT = self._calculateUiSize(680) + # Top bar common self.config_window.TOP_BAR__HEIGHT = self._calculateUiSize(40) self.config_window.TOP_BAR__IPADY = self._calculateUiSize(12) From 7b804aba445afe40d4c6e47c7f838b97509847e8 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 19:02:02 +0900 Subject: [PATCH 248/355] =?UTF-8?q?[Refactor]=20=E5=A4=89=E6=95=B0?= =?UTF-8?q?=E5=90=8D=E5=A4=89=E6=9B=B4=E3=80=81=E7=B5=B1=E4=B8=80=E3=80=82?= =?UTF-8?q?=20grid=5Frow/columnconfigure=E3=81=AA=E3=81=A9=E3=81=A7minsize?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B=E5=A4=89?= =?UTF-8?q?=E6=95=B0=E3=81=AFMIN=5F=E3=82=92=E3=81=A4=E3=81=91=E3=82=8B?= =?UTF-8?q?=E3=80=82=5FMIN=5FHEIGHT=E3=82=84=5FMIN=5FWIDTH=E3=81=AA?= =?UTF-8?q?=E3=81=A9=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../config_window/widgets/createConfigWindowTitle.py | 4 ++-- .../setting_box_containers/_SettingBoxGenerator.py | 6 +++--- vrct_gui/main_window/widgets/create_sidebar.py | 4 ++-- vrct_gui/ui_managers/UiScalingManager.py | 10 +++++----- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/vrct_gui/config_window/widgets/createConfigWindowTitle.py b/vrct_gui/config_window/widgets/createConfigWindowTitle.py index 302d8ccc..87d38480 100644 --- a/vrct_gui/config_window/widgets/createConfigWindowTitle.py +++ b/vrct_gui/config_window/widgets/createConfigWindowTitle.py @@ -2,8 +2,8 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkImage def createConfigWindowTitle(config_window, settings): - config_window.grid_columnconfigure(0, weight=0, minsize=settings.uism.TOP_BAR_SIDE__WIDTH) - config_window.grid_rowconfigure(0, weight=0, minsize=settings.uism.TOP_BAR__HEIGHT) + config_window.grid_columnconfigure(0, weight=0, minsize=settings.uism.TOP_BAR_SIDE_AREA_MIN_WIDTH) + config_window.grid_rowconfigure(0, weight=0, minsize=settings.uism.TOP_BAR__MIN_HEIGHT) config_window.side_menu_config_window_title_logo_frame = CTkFrame(config_window, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) config_window.side_menu_config_window_title_logo_frame.grid(row=0, column=0, sticky="nsew") diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 0abcc4c4..5705031e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -27,8 +27,8 @@ class _SettingBoxGenerator(): setting_box_frame_wrapper = CTkFrame(setting_box_frame, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) setting_box_frame_wrapper.grid(row=0, column=0, padx=self.settings.uism.SB__IPADX, pady=self.settings.uism.SB__IPADY, sticky="ew") - setting_box_frame_wrapper.grid_columnconfigure(0, weight=0, minsize=int(self.settings.uism.SB__MAIN_WIDTH / 2)) - setting_box_frame_wrapper.grid_columnconfigure(2, weight=1, minsize=int(self.settings.uism.SB__MAIN_WIDTH / 2)) + setting_box_frame_wrapper.grid_columnconfigure(0, weight=0, minsize=int(self.settings.uism.MAIN_AREA_MIN_WIDTH / 2)) + setting_box_frame_wrapper.grid_columnconfigure(2, weight=1, minsize=int(self.settings.uism.MAIN_AREA_MIN_WIDTH / 2)) setting_box_frame_wrapper_fix_border = CTkFrame(setting_box_frame, corner_radius=0, width=0, height=0) setting_box_frame_wrapper_fix_border.grid(row=1, column=0, sticky="ew") @@ -67,7 +67,7 @@ class _SettingBoxGenerator(): anchor="w", justify="left", # height=0, - wraplength=int(self.settings.uism.SB__MAIN_WIDTH / 2), + wraplength=int(self.settings.uism.MAIN_AREA_MIN_WIDTH / 2), font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__DESC_FONT_SIZE, weight="normal"), text_color=self.settings.ctm.LABELS_DESC_TEXT_COLOR ) diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 75aecb91..32aad519 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -16,8 +16,8 @@ def createSidebar(settings, main_window, view_variable): main_window.sidebar_compact_mode_bg_container = CTkFrame(main_window.sidebar_bg_container_wrapper, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) - main_window.sidebar_bg_container.grid_columnconfigure(0, weight=0, minsize=settings.uism.SIDEBAR_WIDTH) - main_window.sidebar_compact_mode_bg_container.grid_columnconfigure(0, weight=0, minsize=settings.uism.COMPACT_MODE_SIDEBAR_WIDTH) + main_window.sidebar_bg_container.grid_columnconfigure(0, weight=0, minsize=settings.uism.SIDEBAR_MIN_WIDTH) + main_window.sidebar_compact_mode_bg_container.grid_columnconfigure(0, weight=0, minsize=settings.uism.COMPACT_MODE_SIDEBAR_MIN_WIDTH) createSidebarFeatures(settings, main_window, view_variable) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index d2fcb257..24acbd9b 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -49,8 +49,8 @@ class UiScalingManager(): # Sidebar - self.main.SIDEBAR_WIDTH = self._calculateUiSize(230) - self.main.COMPACT_MODE_SIDEBAR_WIDTH = self._calculateUiSize(60) + self.main.SIDEBAR_MIN_WIDTH = self._calculateUiSize(230) + self.main.COMPACT_MODE_SIDEBAR_MIN_WIDTH = self._calculateUiSize(60) # Sidebar Features self.main.SF__LOGO_MAX_SIZE = self._calculateUiSize(120) @@ -141,11 +141,11 @@ class UiScalingManager(): self.config_window.DEFAULT_HEIGHT = self._calculateUiSize(680) # Top bar common - self.config_window.TOP_BAR__HEIGHT = self._calculateUiSize(40) + self.config_window.TOP_BAR__MIN_HEIGHT = self._calculateUiSize(40) self.config_window.TOP_BAR__IPADY = self._calculateUiSize(12) # Top bar Side - self.config_window.TOP_BAR_SIDE__WIDTH = self._calculateUiSize(220) + self.config_window.TOP_BAR_SIDE_AREA_MIN_WIDTH = self._calculateUiSize(220) self.config_window.TOP_BAR_SIDE__CONFIG_LOGO_MARK_SIZE = self.dupTuple(self._calculateUiSize(28)) self.config_window.TOP_BAR_SIDE__CONFIG_TITLE_FONT_SIZE = self._calculateUiSize(22) self.config_window.TOP_BAR_SIDE__CONFIG_TITLE_LEFT_PADX = int(self.config_window.TOP_BAR_SIDE__CONFIG_TITLE_FONT_SIZE + self._calculateUiSize(16)) @@ -163,7 +163,7 @@ class UiScalingManager(): # Setting Box - self.config_window.SB__MAIN_WIDTH = self._calculateUiSize(720) + self.config_window.MAIN_AREA_MIN_WIDTH = self._calculateUiSize(720) self.config_window.SB__TOP_PADY_IF_WITH_SECTION_TITLE = (self._calculateUiSize(24)) self.config_window.SB__TOP_PADY_IF_WITHOUT_SECTION_TITLE = (self._calculateUiSize(64)) self.config_window.SB__BOTTOM_PADY = (self._calculateUiSize(40)) From 33ffa14edf75f0b96b2dc3cf4ed5d7821c982e66 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 19:13:16 +0900 Subject: [PATCH 249/355] =?UTF-8?q?[Update]=20Config=20Window:=20=E3=82=BF?= =?UTF-8?q?=E3=82=A4=E3=83=88=E3=83=AB=E3=81=A8=E3=82=A6=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=83=89=E3=82=A6=E3=81=AE=E3=82=BF=E3=82=A4=E3=83=88=E3=83=AB?= =?UTF-8?q?=E3=81=AElocalize,=20UI=E6=97=A5=E6=9C=AC=E8=AA=9E=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 1 + vrct_gui/config_window/ConfigWindow.py | 4 ++-- vrct_gui/config_window/widgets/createConfigWindowTitle.py | 5 +++-- 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/view.py b/view.py index aea0c93b..903285e3 100644 --- a/view.py +++ b/view.py @@ -136,6 +136,7 @@ class View(): CALLBACK_SELECTED_SETTING_BOX_TAB=None, VAR_ERROR_MESSAGE=StringVar(value=""), VAR_VERSION=StringVar(value=config.VERSION), + VAR_CONFIG_WINDOW_TITLE=StringVar(value=i18n.t("config_window.config_title")), # Side Menu Labels diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 81247055..c8a59555 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -13,7 +13,6 @@ class ConfigWindow(CTkToplevel): # configure window self.after(200, lambda: self.iconbitmap(getImagePath("vrct_logo_mark_black.ico"))) - self.title("Settings") self.geometry(f"{settings.uism.DEFAULT_WIDTH}x{settings.uism.DEFAULT_HEIGHT}") @@ -23,10 +22,11 @@ class ConfigWindow(CTkToplevel): self.settings = settings self._view_variable = view_variable + self.title(self._view_variable.VAR_CONFIG_WINDOW_TITLE.get()) # When the configuration window's compact mode is turned on, it will call `grid_remove()` on each widget appended to this array. In the opposite case, `grid()` will be called. self.additional_widgets = [] - createConfigWindowTitle(config_window=self, settings=self.settings) + createConfigWindowTitle(config_window=self, settings=self.settings, view_variable=self._view_variable) createSettingBoxTopBar(config_window=self, settings=self.settings, view_variable=self._view_variable) diff --git a/vrct_gui/config_window/widgets/createConfigWindowTitle.py b/vrct_gui/config_window/widgets/createConfigWindowTitle.py index 87d38480..4e949667 100644 --- a/vrct_gui/config_window/widgets/createConfigWindowTitle.py +++ b/vrct_gui/config_window/widgets/createConfigWindowTitle.py @@ -1,6 +1,6 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkImage -def createConfigWindowTitle(config_window, settings): +def createConfigWindowTitle(config_window, settings, view_variable): config_window.grid_columnconfigure(0, weight=0, minsize=settings.uism.TOP_BAR_SIDE_AREA_MIN_WIDTH) config_window.grid_rowconfigure(0, weight=0, minsize=settings.uism.TOP_BAR__MIN_HEIGHT) @@ -18,7 +18,8 @@ def createConfigWindowTitle(config_window, settings): config_window.side_menu_config_window_title_logo_wrapper.grid_rowconfigure(0,weight=1) config_window.side_menu_config_window_title = CTkLabel( config_window.side_menu_config_window_title_logo_frame, - text="Settings", + # text="Settings", + textvariable=view_variable.VAR_CONFIG_WINDOW_TITLE, height=0, anchor="w", font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.TOP_BAR_SIDE__CONFIG_TITLE_FONT_SIZE, weight="bold"), From 0efecc6daf48cccff8977834a37b277e24c70160 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 19:32:32 +0900 Subject: [PATCH 250/355] =?UTF-8?q?[bugfix]=20Main=20Window:=20minimize=20?= =?UTF-8?q?sidebar=20button.=20=E3=82=B9=E3=83=86=E3=83=BC=E3=82=BF?= =?UTF-8?q?=E3=82=B9=E3=82=92=E4=B8=80=E5=BA=A6disabled=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=80=81=E3=81=9D=E3=81=AE=E5=BE=8C=E3=82=B5=E3=82=A4?= =?UTF-8?q?=E3=83=89=E3=83=90=E3=83=BC=E9=96=8B=E9=96=89=E3=82=92=E8=A1=8C?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E6=99=82=E3=81=AB=E7=9F=A2=E5=8D=B0=E3=81=AE?= =?UTF-8?q?=E5=90=91=E3=81=8D=E3=81=8C=E3=81=8A=E3=81=8B=E3=81=97=E3=81=8F?= =?UTF-8?q?=E3=81=AA=E3=81=A3=E3=81=A6=E3=81=84=E3=81=9F=E3=81=AE=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_changeMainWindowWidgetsStatus.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index 6093ff43..e785d810 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -117,21 +117,17 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta vrct_gui.minimize_sidebar_button_container__for_opening.configure(cursor="") vrct_gui.minimize_sidebar_button_container__for_closing.configure(cursor="") - if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - image_file = CTkImage((settings.image_file.ARROW_LEFT_DISABLED).rotate(180), size=MINIMIZE_SIDEBAR_IMAGE_SIZE) - else: - image_file = CTkImage((settings.image_file.ARROW_LEFT_DISABLED), size=MINIMIZE_SIDEBAR_IMAGE_SIZE) + image_file__for_opening = CTkImage((settings.image_file.ARROW_LEFT_DISABLED).rotate(180), size=MINIMIZE_SIDEBAR_IMAGE_SIZE) + image_file__for_closing = CTkImage((settings.image_file.ARROW_LEFT_DISABLED), size=MINIMIZE_SIDEBAR_IMAGE_SIZE) elif status == "normal": vrct_gui.minimize_sidebar_button_container__for_opening.configure(cursor="hand2") vrct_gui.minimize_sidebar_button_container__for_closing.configure(cursor="hand2") - if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - image_file = CTkImage((settings.image_file.ARROW_LEFT).rotate(180), size=MINIMIZE_SIDEBAR_IMAGE_SIZE) - else: - image_file = CTkImage((settings.image_file.ARROW_LEFT), size=MINIMIZE_SIDEBAR_IMAGE_SIZE) - vrct_gui.minimize_sidebar_button__for_opening.configure(image=image_file) - vrct_gui.minimize_sidebar_button__for_closing.configure(image=image_file) + image_file__for_opening = CTkImage((settings.image_file.ARROW_LEFT).rotate(180), size=MINIMIZE_SIDEBAR_IMAGE_SIZE) + image_file__for_closing = CTkImage((settings.image_file.ARROW_LEFT), size=MINIMIZE_SIDEBAR_IMAGE_SIZE) + vrct_gui.minimize_sidebar_button__for_opening.configure(image=image_file__for_opening) + vrct_gui.minimize_sidebar_button__for_closing.configure(image=image_file__for_closing) case "entry_message_box": From 153764f48ad94fcbb6598bda2756b8a67e417546 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 10 Oct 2023 19:46:25 +0900 Subject: [PATCH 251/355] =?UTF-8?q?[bugfix]=20Main=20Window:=20=E3=82=B5?= =?UTF-8?q?=E3=82=A4=E3=83=89=E3=83=90=E3=83=BC=E3=82=92=E9=96=89=E3=81=98?= =?UTF-8?q?=E3=81=9F=E6=99=82=E3=81=AB=E3=80=81root=20geometry(Window?= =?UTF-8?q?=E3=82=B5=E3=82=A4=E3=82=BA)=E3=81=AE=E9=AB=98=E3=81=95?= =?UTF-8?q?=E3=81=8C=E5=A4=89=E3=82=8F=E3=81=A3=E3=81=A6=E3=81=97=E3=81=BE?= =?UTF-8?q?=E3=81=86=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82=20(?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=91=E3=82=AF=E3=83=88=E3=81=A7=E3=81=AF?= =?UTF-8?q?=E3=81=82=E3=82=8B=E3=81=8C=E3=80=81=E6=A9=9F=E8=83=BD=E3=81=A8?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=81=AF=E3=82=B5=E3=82=A4=E3=83=89=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=81=AE=E9=96=8B=E9=96=89=E3=81=AA=E3=81=AE=E3=81=A7?= =?UTF-8?q?=E3=80=81=E6=84=8F=E5=9B=B3=E3=81=97=E3=81=9F=E3=82=B5=E3=82=A4?= =?UTF-8?q?=E3=82=BA=E5=A4=89=E6=9B=B4=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8B?= =?UTF-8?q?=E3=81=A3=E3=81=9F=E3=80=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/vrct_gui.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 6102937b..e4f4464f 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -11,7 +11,7 @@ from ._printToTextbox import _printToTextbox from .main_window import createMainWindowWidgets from .config_window import ConfigWindow -from .ui_utils import _setDefaultActiveTab +from .ui_utils import _setDefaultActiveTab, getLatestHeight from utils import callFunctionIfCallable @@ -86,6 +86,8 @@ class VRCT_GUI(CTk): ) + self.update() + self.geometry("{}x{}".format(self.winfo_width(), self.winfo_height())) def startMainLoop(self): From e860a4d912cdebd653b23d45ab64514fdc2951fb Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 11 Oct 2023 02:11:30 +0900 Subject: [PATCH 252/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20READE=20:=20?= =?UTF-8?q?=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1=E3=83=B3=E3=83=88=E3=81=AB?= =?UTF-8?q?=E6=9B=B8=E3=81=84=E3=81=A6=E3=81=82=E3=82=8B=E9=83=A8=E5=88=86?= =?UTF-8?q?=E3=82=92=E5=89=8A=E9=99=A4/=E6=96=87=E8=A8=80=E5=A4=89?= =?UTF-8?q?=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 137 +++++++++++++------------------------------ docs/main_window.png | Bin 0 -> 36990 bytes docs/vrct_logo.png | Bin 7182 -> 152106 bytes 3 files changed, 42 insertions(+), 95 deletions(-) create mode 100644 docs/main_window.png diff --git a/README.md b/README.md index 20da4395..7c315272 100644 --- a/README.md +++ b/README.md @@ -2,115 +2,62 @@ ![](docs/vrct_logo.png) -# VRCT (VRChat Chatbox Translator & Transcription) +

+翻訳や文字起こしでVRChatの会話をサポートするソフトウェア +

+ +
-## Overview -VRChatのChatBoxにOSC経由でメッセージを送信するツール -翻訳エンジンを使用してメッセージとその翻訳部分を同時に送信することができる +# Download & Install +好きな場所からダウンロードしてください +- [releases page](https://github.com/misyaguziya/VRCT/releases/) +- [BOOTH.pm](https://misyaguziya.booth.pm/) -## Requirement -- python 3.9.13 -- pillow -- PyAudioWPatch -- python-osc -- customtkinter -- deepl -- deepl-translate(https://github.com/misyaguziya/deepl-translate) -- translators(https://github.com/misyaguziya/translators) -- custom_speech_recognition(https://github.com/misyaguziya/custom_speech_recognition) +ダウンロードしてexeを起動するだけです。 -**deepl-translate/translators/custom_speech_recognitionについては追加実装をしています** -**`pip install`でinstallした場合、動かないので注意** +# What is VRCT? +VRCTは非母国語同士が会話を行うためにチャットもしくはボイスの翻訳を行う会話サポートソフトウェアです。 +これらの機能はVRChat内で使用するために設計されていますがその他の用途(映画鑑賞等)でも使用されています。 -## install -```bash -./install.bat -``` +VRCTはあなたの会話を以下でサポートをします。 +- 💬チャット機能 +- 🌐翻訳機能 +- 🎙マイクの文字起こし機能 +- 🔈スピーカーの文字起こし機能 -## Usage -```bash -python VRCT.py -``` +![](docs/main_window.png) -## Features +その他の機能ついて詳しくは[Documents](#Documents)まで -### init -0. VRChatのOSCを有効にする(重要) +# Documents +初期設定や基本機能、その他の機能について記載してあります。 +- [Documents](https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246?pvs=4) -(任意) -1. DeepLのAPIを使用するためにアカウント登録し、認証キーを取得する -2. ギアアイコンのボタンでconfigウィンドウを開く -3. ParameterタブのDeepL Auth Keyに認証キーを記載 -4. configウィンドウを閉じる -### Normal use -1. メッセージボックスにメッセージを記入 -2. Enterキーを押し、メッセージを送信する +# If you want to run it in python +1. 以下のバージョンのpythonをインストールしてください。 + ``` + python 3.11.5 + ``` -### About Checkboxes -- translation: 翻訳の有効無効 -- voice2chatbox: マイクの音声を文字起こししてチャットボックスに送信する -- speaker2log: スピーカーの音声から文字起こししてログに表示する -- foreground: 最前面表示の有効無効 +2. install packages + ```bash + ./install.bat + ``` -### About Textbox -- log tab: すべてのログを表示 -- send tab: 送信したメッセージを表示 -- receive tab: 受信したメッセージを表示 -- system tab: 機能についてのメッセージを表示 - -### About Config Window -- UI tab - - Transparency: ウィンドウの透過度の調整 - - Appearance Theme: ウィンドウテーマを選択 - - UI Scaling: UIサイズを調整 - - Font Family: 表示フォントを選択 - - UI Language: UIの表示言語を選択 -- Translation tab - - Select Translator: 翻訳エンジンの変更 - - Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択 - - Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択 -- Transcription tab - - Input Mic Host: マイクのホストAPIを選択 - - Input Mic Device: マイクを選択 - - Input Mic Voice Language: 入力する音声の言語 - - Input Mic Energy Threshold: 音声取得のしきい値 - - Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化 - - Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - - Input Mic Phase Timeout: 文字起こしする音声時間の上限 - - Input Mic Record Timeout: 音声の区切りの無音時間 - - Input Mic Max Phrases: 保留する単語の上限 - - Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC) - - Input Speaker Device: スピーカーを選択 - - Input Speaker Voice Language: 受信する音声の言語 - - Input Speaker Energy Threshold: 音声取得のしきい値 - - Check threshold point: Input Speaker Energy Thresholdのしきい値を視覚化 - - Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - - Input Speaker Record Timeout: 音声の区切りの無音時間 - - Input Speaker Phase Timeout: 文字起こしする音声時間の上限 - - Input Speaker Max Phrases: 保留する単語の上限 -- Parameter tab - - OSC IP address: 変更不要 - - OSC port: 変更不要 - - DeepL Auth key: DeepLの認証キーの設定 - - Message Format: 送信するメッセージのデコレーションの設定 - - [message]がメッセージボックスに記入したメッセージに置換される - - [translation]が翻訳されたメッセージに置換される - - 初期フォーマット:`[message]([translation])` -- Others tab - - Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする - - **(New!) Notification XSOverlay: XSOverlayの通知機能を有効(VR only)** +3. Usage + ```bash + python main.py + ``` ## Author -みしゃ(misyaguzi) -- Main開発 -- twitter: https://twitter.com/misya_ai -- booth: https://misyaguziya.booth.pm/items/4814313 +- [みしゃ(misyaguzi)](https://github.com/misyaguziya) (メイン開発) +- [しいな(Shiina_12siy)](https://github.com/ShiinaSakamoto) (メイン開発, UIデザイン, 翻訳:英語/日本語) +- [レラ](https://github.com/soumt-r) (翻訳:韓国語) +- [done_san]() (ロゴデザイン) -しいな(Shiina_12siy) -- Main開発, 翻訳(英語) +--- -レラ -- 翻訳(韓国語) \ No newline at end of file +※「VRChat」は、米国VRChat, Inc.の登録商標です。 \ No newline at end of file diff --git a/docs/main_window.png b/docs/main_window.png new file mode 100644 index 0000000000000000000000000000000000000000..815d9c12d95d821c83f6efebe2708750c79cc52b GIT binary patch literal 36990 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 literal 0 HcmV?d00001 diff --git a/docs/vrct_logo.png b/docs/vrct_logo.png index de568b6ece6dc291dfa26a31dd8815fbf8e7f14c..badb9be80658728e8e6978dc0eb7b0321e3386cc 100644 GIT binary patch literal 152106 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%0D&1Jv*aytfTIOOWz_Z0K&_-kt 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%0DSh7mva8`|=!V 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 From 2996c4e67e908d347e8431d30394ccd6f3a739d0 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 11 Oct 2023 02:23:22 +0900 Subject: [PATCH 253/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20README=20:=20?= =?UTF-8?q?=E5=9F=8B=E3=82=81=E8=BE=BC=E3=81=BF=E3=81=8C=E5=87=BA=E6=9D=A5?= =?UTF-8?q?=E3=81=AA=E3=81=8B=E3=81=A3=E3=81=9F=E3=81=AE=E3=81=A7=E5=89=8A?= =?UTF-8?q?=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 7c315272..d5e1c1be 100644 --- a/README.md +++ b/README.md @@ -6,8 +6,6 @@ 翻訳や文字起こしでVRChatの会話をサポートするソフトウェア - -
# Download & Install From b76ae66b79485c1bee1ba2125c8a562c7ff73ff4 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 11 Oct 2023 02:42:52 +0900 Subject: [PATCH 254/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20README=20:=20?= =?UTF-8?q?=E7=94=BB=E5=83=8F=E5=B7=AE=E3=81=97=E6=9B=BF=E3=81=88?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +++- docs/main_window.png | Bin 36990 -> 39236 bytes 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d5e1c1be..4a5a2958 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ 翻訳や文字起こしでVRChatの会話をサポートするソフトウェア +![](docs/main_window.png) +
# Download & Install @@ -25,7 +27,7 @@ VRCTはあなたの会話を以下でサポートをします。 - 🎙マイクの文字起こし機能 - 🔈スピーカーの文字起こし機能 -![](docs/main_window.png) + その他の機能ついて詳しくは[Documents](#Documents)まで diff --git a/docs/main_window.png b/docs/main_window.png index 815d9c12d95d821c83f6efebe2708750c79cc52b..17a87cbf9ba6221856c65883a6fc5f263da0ea19 100644 GIT binary patch literal 39236 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 literal 36990 zcmeFZWl&pj`!+~%heB~L1&Tv)r$CY7QrsyT+zDR1cqy)Jk+uW~?(W`Vf#U82cXe zM{4n1R1pxKgxN|l94S=riKA|Sj>^iLF%@027BUo8Bn@e|h*-=$AYWaiBq z+(ef56@%fqu{nV2ceoEqY^{yOF^duuBO`n;v^M`(fTzN{12D7J5_@nmv!khjp_^gVUM?y!TuSs46tTpT%Q*3_fL>$E?n>iJA ziPUBDD4*+w*njUD?=pKc`O>7X9^$;P0@2=u_^aM`n7%gXcg~(7CD4^3V2(^smqU4{ z8#z{*X#QMz(*^J0`>JZ9BBKsU3?!upr!q<>I3N<}i7&iWXyp`?)pUY!Ne~cd5fo%zYkB|KoA>d1cNTnqG+J+P zE{WLp3BgQGo%m@-+yLYEk|4FfXX>-e*#&ln(4neq6{^`+U-45t3u`9rezQD>hf_J>(`QOKw z^Ts;5l`LUdQQOOZ2ato|=DWsrhp#eBv!{sfpLxEK0Ur8RHg-5L5dS+tTl-&{lFyz# zKI##lK(9|?F-(-nka19?{~k+Z#Q9+54|B5FvQr}ECBJNx`V#s0kRK0i7Y=m#8Eni! zj7&j@Nrd|MzA`&Bm=px46cg{O=*)iAh4$iLgkZ+}8%YMDiL~_c8yXY4!=I|85V%_^ z0q(zx^g(J(x`)MQ^@?OiC+1&DOE;fk|2xB>iVxu<<@c0Ky$)|5kcb6CzDfRPVwaM- zuu-fWkP@xE{BW*%yqb6UpKe8r(tit~<9k@VU;W4Ko`V=QB;H~88EUaa%QUv0v<{$QK4@6T~)^XQISd@BT1@u`#EVjQCXqJnN26a1aOEwS;E;9+1J%`SSd zRRO+@#%RW~cI`+pym8jazHaI1;)8hQ<{=9mH_cSKKOix+bJ8>g!tnWRY` z`n1C2fe#;h^+A3qm#z*iZ_u42UNgym#@fYg`iu{T00#L`-p<#Ba-plmU1NXzyUvEp z#(v4})nTulsFr_$iYs;=PI9Uq8TROY$u(7pe zrjCz@j6{cMFCr8HPd|MkfM~NWFE6(T=^7gs{%6ND9wc?^g=|cZCKA>~gL_lJp{)&4 z%WqIROalVMRYl8#Zs57S=lddD*hrmReSLjPH;b*gx%cjLVPRn{b#@tPg$wT<_at#@ zC>4vNrHVIwwuT#ie*^OOU%0zSs&_EZq#irK3*+}2mIE2EO{QLBV`>`UL%dGZO=3-i z8ol6FnZ!&W2J_)y8SALTh_fA+-2maWYQ;YV! z9`EACGlm{~zv|DY#-0;JQ^=NBtQ|%X(EfRk_;-a%p^}c@9c-hF!B>{|r@cCM*kLCJ zt+^6+M?}D(^+M77lJJ5;F5SCLlwha=2(1|Y$FAD6TEDo-CEN`78=@@FxsiM^_3A8V zhuMkWt0H`T8zI7bIavIZ8$0GVXzL_GYXhy*l%)lYkCE7_48U-n) zvM4nkb$rp=>QY;9c^p({yE!G6OzoAQ)Q1C6BZGt4#urjs(LSl_ZYs zPae6{IB(R)k1MIIx2j6xU5>St)}t_8Heysf>MnTTL!;@q`ZzD=AL2`3$&Vu>pF=3G z5w?cEmC>w?EHrQXZ=uLuC_JYk^^m5)rJ62j)Oehse!(?v%b3Q^x;OJX!~6`p%9 z9{MeCNdMq{P3suf-EPR~f$uj*qDO8P%si&=I`IXAJ2h| zIy&ix8ZIOjmH3o-JOb7?Wz+l7@G|znA54U?VXf^4si8i$Rh8+h5-na{Vfa29U4+}RI`!>Px@iCWj*}|(w z3wWAX_>?q=LCr$9Io&2tqYS*O=H$Q^$~Q@%|8u_$bB*s*>D#y2CC^4?cJ|-1jQ{*# zdqcx(KQ9^!gf<`42*xopa(k+c7a6OerS-j5@vm#l*Bv#zq%BX;*pjFWKa;bCVMw zvN~DRha&w;O?}BaIwxv7-b&I)%M3U#>HpnUxwI5t*J81~&0>&&`frw%kty|~g{Y$E zgKet5@BRCX(kM0E+>8zux-m~?IZ8fo)<>f?aveiMh!j4s8n*48{`JjZg#7T{iBms+ z5c;I(zdPkyzOuX26C<6{Rs7EQ?cWE&R9-^hCz7jZ$?CEH!6t)PVby#zCEVjb!vB*g zckn)(`5Yi(hW_UotCvk%4gdRTa6PT6?XrQDFqC8ZAT~9 z2R+1|g)?!=p)Kf%Qif>y*AG&0R3a)WBeg7#=qa&S1beDh;>!lzGuz?P#slGlW-osJ z;@ER4Vduq*7C9T6G)!6GR{6RRsV=~;2D=eq5!(q+pp zD9|)CG%WL<-S_N?qY4?e>f}}a8v-?Tb>+d=)(A0_LLX`zX0?S>l(E~kT@#a%Iuq#^ z)wH#VMT^>mVBK8UO5@11uU>tk79)s^j{fE_TcgW{%D_;NH6-4j=oFWbnMpE-+6KyK zF-v&f>2ey`>0Zyxx$MuS)MnLf;Ej86G+uH~-Fh<8Sbu-L%go8yYgMV9VA3}Wzl{YD z!5tDUu=p^_#`(Cxb-VLb_x(TFbOLyVPKyEVJJV5HTQ3nlejIVR9IH#|n^eO& zubkmSWP1yh5z-O3NL=2(Xg7v4qsO^!lh9z({H@$n-jc4ynZXXOr@RmkPG z?*0;prfV4!b7oM*RKY@}^o|5NX&YE6l}b_4%8L2hw|>dObac3=bIwT8sCZT@p;3F& z4P&6%@kLUz!+Fqo#y4ppmoG*V>2@|_k%f%POg_6 z9LuNM<0KQ$$-`e88j?`rq5;``wO3VZMsBHS-Vx8yx>~=#zn_ktK8abAQBzGVXxQpo zRn=z-$GYHvyX$hN#TI?gMw2loH#avng{LBvEYCKh%62?FULc_3hh`BA)uNZtKkzrJ zv*-l9DugNe_y{pDFvz@lb3D5!+PSLAW(q*n;;eWxQeYFnXFL5o4|lxt4ITw~c?bXw z5LG#I#hf5WNlis^zS*z+U1lPYa}T$k2H-4l=ON$4T1SI3Az^f2>$`ytUhXEVGf~nx zRS?m9Ax)mheEfJKUuR##RzKb zawMdZBmBn73UwFeWwkT$BZi764vpseNuqip#GqCs7|OtnjWiBgIRl5#G2#1BgSVdC zFZwS&uE^J~Znrk~@%@8?y<9O%4M?bSZHkZ-Tn(E~mOg`%-BwPzz$ z@#M$Z{T|0!LMkew0_}>gUq6hEjhXU{H8uIItTf=ymS#J(YO}`i|0d;U4eurwGy@`I zEt;2}&t$$$#QEs)`P8DMB1 z#fQ2- zidgZDaR6WW@S2gAH#8vuOIpj@Tab>9E=$B~q&Bvd$JPjuFG9pCpnsv+cYC&$6EGbB z;|>fClJQva1_qq*eT?R$Jf&BY_5zFpB{LZ;O8uOkUdVX~Avf^;P4XFwTjM-nYl%7c zWbi=P%L`uYAU2<{Slp{L(l-Qt!68HB-s7>O?gfK~su_Y827gJ{ouq zZ&$Ocoek-YjEee_mlsrGPw}4qwOy!jd5eNqITr!}%6qtm(du=sOPYa7Lz+Hg)#IYW-zo zR-r8_=z6N;A@T_lDsnC>J9~j*Dn|^t(5EvB@t?2il=7@f<)jb=76gMnefng&pEvJl zJ%5b@@Frojd;soeiTZpR6`$4bjJ?M2y*RLJPf|rja?Fd`W0!W?=){AWmFpgQiz>Bl zHqN<^E4OE~OLE0VMbUZeM*xg%P|y>y)5ZXRh&i84UTVLk0CRKm%e#krnjatu?3^-z zvV^g~_fy^rQz}&G%`HrhMdLaOTYTl=;Y-5qlrSAMkIHKta=}Q2q#|UwWIpT=K9IKC zVcvlJqf>Jq)LF1(3wv#R!=bp$4|kFKQ)AmXAp(EjhJysvFQFV39{7W&DFm+C+4`Dm ze;ax*@5|*g_TurXvgY%jh-T3`Kk6+UkSa*S)h$m)uH(W8(CsOJKz8T0iBC-(H@lA; zKB|z!R1wfA_s;Gb(agRhbQUmqS_ui7=E^nrC%5TGrB2Tz0?yp@LgE2|y4j zoT_FI_jhxz++RAiT-7dZeGfOgyuR0q!CFKb??NwkYv(E}jOw;Rie5JOdB)JS_Mxx= zc3rE(V?ENf;Z(EhEI3`^!(&imUvF2|zj`zQ`jb?Al1oBA-C9f6(b>r@2R=V%j@PH z0bUz{OSJ-rKQ#Ab>=)CaHyDC0yDu7VTnYiFZkVw#o~ zX_KP{^2;rsKX+wzqz3cs|Eadv9LWaq^Tr3*jY*)GboiW-_LKmd{;5HZ&qnJ^trzEf zBa`#EG7_im!z_>WbiErIaM9|<7XgzgkUFhBa^^5GHfxL1F(;dB@gzBf;lzJ+@$jpP z(Ty3`5{fvsoQw>Y%SxzeBX_|NS)A#yK~5n*|W1 z3T+9fsFZ6RTc9M^cH!+aZxt{qU-ZZtAE7%&=8lZZ_U;R{N;agbC`G_ z`s*4bwdJW*M@2^3cm(o%`SQghU|J{ryV8y(U>~VJRToz8du0|@z30|$U`*a>F0cj9^NuGroqB85??`v zF5TVT{W&?+;!I$ZjfL&!#XS-Yxb+^)Y2sg<`ThG#K(!%`4g=s}PdtU3yu7?KIaZJY zA(IwHoVvRD-k&of-*Z1BYy4t1wG)C%vhV#^czBzM5fAKw?N;lrtv&4*pU#PkiD6U0Ha9upS&&!>Xy>eBkK&G?-*~rF5r~RU?dKVHM%&M-&$xEdGqE?yV_G?Vp&g5 z{!HSL!0=hGGWylux`IyYA!vznH0Q{%UP(?PLx)VTf z(@j2Z(Gr3F>XOcXG7%zLTb)3}b=(T#2iH5+ zU7tA5d%(B@@8hH5(7XVGT-R%)wFmiu(?QX5)@8 z*45JgYCY9nw>7TupdlJCGNtjg5fS*x#P&F)-7?_kX~Mxq@*eG;uHkzCi&>; z;*XwQPZr+p)p}xdeY!7gWyLs{T2xgx%5V1yjN#;%H<}6CEs%~l7X>I*`BFdAhhZ(m zUEUh)A@g!IL&G7a+}n^}zkacV3}RD(d9p=rekL)g)QNdppk&wu9QPNR26kKUb1rRGu0L($iVq#)mu(8D~a)48YGi$ZTAcb{xFD`xa@GJmv7)XG~ z@pL*Pax@O60B=Cfq(DQ>ii(YO_`O-e`ONO#mk5}`qmItZA{;Sq#U#wqFmfg(B~@qE z7F(C4BJTYlHZbrOWF#QWLf$y6qW-&n5X^qP1BnvPuL1D91W?-n))R=^8TnCfmq*;NQ+gY4ZtueY(wJRs%plm26>vfzs5#r+t1G76i2l{l9jS4bp-Gg^l2XX>NzOc{y%0S1i$HUT(-m z;^kqqvJx2?^I~_J8l@wJ;1bd87)455**_hTu$M;IJq@7eXt}Mr!-jdeRKdB`S%8WA zohTEFc<#Ml>wEf)hzO;Yiu-vY5cYr_`QU~mD<@aUz#jWeE(QLu{Ez=Qz6Shrmmtd#DDMgs{jK|%iIgt#h%y2|tm?sYS>m{Ho?X;Y{i z#jslP{(K#Q>YAs)39Zl85OXU~hoBjcLBL_!j^N6V8eNx4md9yi{&W%cY-rJif)H(Pr|XOJ7NVE`ZjX|jjd_t%EvE+F3n9^7QI(v1u1cHsRf<&%HAl; zf)oe%DGDqLfJtVlSZ5QO!L)XX(yYsBy7xWNHnoabMNzMDN)(I?7}AncQe5wFU~Z#g ziv?9xyq6i>=0|wm(#>duT#wWUABY{DG!LgNf!8O+w--FE%ZJPKp%D>iXg>hw-{_%0 zJM5$p*D6reT)LJRz5OsZXSnC-3-tk+nb83W%34#X4^b9oI>*=Ns+5lM+G`%%V|j#BI1RxND|;K87&cI;{eDbqE^Xazh*y$f!&))jQz4GWV!5 z_b!IRXa!^h2aW^c3VMBgz5Rpv&HAtySOk=knK=l=KeO5yghkO0MXI^WaCjg-WEhY# z0Gnw!$!(8UIyZ@mVhUdb+U$JA8Gtf1=}1;F)?Grxt0hnj|lqi$M9 zM@BCF`qKH{D!>?|RuT>H2)kN3a_jWy za#Zi-x`QU2hr=#&kUyG`=sIp;><9y&1C4d@i}l8I0nuNJyzwyt;-)lr^}D~t*8*{y z!QBLeA+1-IXtIlZu?FBL&D5sGaOtLU;Sx(@w}_V075pDU9!5;R%y_+&_m%C>Hg{j9 zN^|-lOxpGRi4O>}%|DjaSK05~2^2UfJpFL%*C!%Fov3NN_32Va`esU=bSEa2JG`i{6y1 z0MTefJA_#}aR=NA{;6@Gz$ORKS4W4`LH%Z942?uwv+uO^2I#+Xmg`h8~YLd_8Zf?wXb5UVTKaz!Wsz0CVd4DM&p znlyPjO4!_2$sMv(Fdx{2E^dkcJk0MEDdR2+NnjMU@X+UlJF`XcJfu5t`*O9%=j|HL zp-eNC)09# z_>}AQ1he?<;c?*85=mV-Q!J%j{KI>+VxNt4lcUiG(cFidRvj+v5P++QFI-~m0?i3i zZclV;1CT1M7o&%*u=ty?kr@HZ4V0C4VS!qfnLU`uO%iuk?+6GZEGlO{^a8AHY099N z2hfyj>=m$bLb}Z#m5jyP^FZC>TXP`ayad#1$65l=!2?7IYo7tv`}N$1bk7KMJnf z%fr`VOsP$2;#CpE#+j>dY5Bl|j12i!T-%ny#C^N^k^Ay4>IHp1Y>nRQHEql{Kv*ci zyPN54!S&xxu8kFN4Z5$kpXEqN>qHy9upSa>TfSXj-!zsl_7rRXGbwsp^uiTpGp*n3 zS#P|aELYmrClvyp9#LY-`os^-{HDwfX}r6jnM!ykf_N$SGb>m_ zJdg{lr}l0R>*j&et_tH*%=r8vZQrwlo!@6LN<3U~`T(li;S$ox<>0OpLJP(W64EbT z-pEPov3<#;Awc{pXQvMV=!*tWL)#PBIs@B|7vg|4V4sqb#$Qz11zS*ycxDl?>KwH- z-GrP_0%|Ovk^;V|>UV!d(th<6>5KEtIo0io#6xuM!``ATpeaHXAl>He`<{R(t7f66 z6X17u1{-hm;`H2`{-NJtb-JI<({yv-8UQ3cjn4)^Wui&i$49T24J+x#oH%hqtbek@ zg%0>jH6I0zRj@x~WUKRA=gpWniC|N#0LS;Q+kwi4t4&?^`6#6&;k)({*TH`-Tzv5(Na>RsNT|M-2^5?y74RV|yXi1dA$MwW4m!V+ z<8m;5?n72Rfo&3fkr-KJ2h?5xP0j`0T$z4z=i?5~Y5P+R;w0Y__Bt2>wt!5;iqbJY z!fVk;1q{AU?iU49fHncmHKbn)_XVI8NicD{2of^Q0jbo{dA{u$M~|G`T>}Qeq3#7h z!wp`8A;%O%!QoeRz2U*nkU1?8k}_Dd8R_l5lat$X5sHY;;K6moyt=wHYB8`gyqV|2 za=GivIiMTl;XFxhx*9|RsM&ysT{zNaUpZqlTf+{#rW6Bz&hO1J?TyO@2a&66T7K~;7rzA$~obg09hVgeRcHok7Tx@f)HcySx z!|#}bzySB;^L=wV&eY0!pS6n}=?9R@QnJ5+5M^ec(Os~9>B(NO^e<3X7|dkUb*7d* zX?nqHT;v?z;9|0Qe+DUHDdp)Vy~e}S%f|KU7~l55xbkMWl}uUFNp)w-THlO6O#+w{ zd{#=gW;FPBxp${ZRyoU>-@bnRB#BXZqe#N24+&4S3u@N6Jyy??n5ZrzEBm{^%^DEH zM;=e81~w!pBt6RgZ6?OP$v}0*sQg3+#|%j;$JxJKQ(~Q@^^y!@zVW2TWU+~D`I+fW zTsEbUYpA&Ywc+8H`{FWB9lctP#Kc5^d*hRn6Eu|`LZMhwJlG*_n;(7lt6^8&qYw0g z{eEq&wB3OT1f%(1pO(51xR3J!3CjYeV^V&! zsyco6ySIJGr`*(9pH1+OjM+4Vlo+e9m*UoF2i}P%VSi?+R#0!VbierEYdo9PBmFQH z&({h<34_8B=J;aeiY^J{@0XPpPGKAQrHz@kZl6dGoc{O=u6m4dj1_?vzjR{fe(Gvk zNN4v_BXgA>>|6c4{LS`3R-YY~-a|p=#p9$xpm}^EF)?Yjz1KIXwb;dP?c9!sw*KxqOaaI5vBT0uo8xe9LGxe`YpNvI43z04ju-wB`Ui zGC7lf>dN(9U(rVETF<9k>)Q4SB*=1N%caEiSDEDDV`ZR7Rjl8{I4}fY9|FGPJ#F{B zF=WGaE*2q3fdE@P?C(*%wBM}(=XN5UY>WZw@&GH#horH2W00+Rl!M|lyTFo%FgFfXI4qi-H>RRF!kI>7y%F?I&l~9dj4s5s1<*a3FE4;MgrzG?QV$jHe( zQPXmd8@E<*TWSjcRG8?hQ3-ff6zxd?vNT=TIfV^Av@13w7*iS2M4 zs`~+HnKV4O`(ZqF0Q>vI+89KegZROPT^ozIc>Ti(A2yPG%@Ph!Qu5z4uQqoPgkBeY zg-$rzm-U9|ZKshSn?0z+{=&y@9M;i9PfH$cckD#RE71>6zW5QyBO^l&z$67xVZM}3 z5Fck=WM8$EKRW0)P;(5&YYYPf{jYADDyKV>(NHKC0PCW=xpb2nQgVQ>H{M3!Qg28p z?iUI$h{qBzAh{ytj&CEIwgpl2_4i}&M^lQ~y_hF#&{Q{W|83EWj)TKMPhZ*NfQCcf z7ezSpC}BKY_A&yj&AFuws7x)r%zfvYG%wqCCRXkl+3vr`I}#$-z5>#S8veL_o`ok1 zD3XDJ;q$1tXy=aE&Ie*EH}mT~U0n+$JO@caX^wc|^Wm^{@j%IFl=0nes4n#4Ae>Ja z5%yct+x6Iwti)RMyfpi0ZEa=Ff7;7FzG$EvSh4m)JOV;N&!zHh*PF4d?zTVtn5r&h zb#MKa$M<`nGf)r5c&1te#aWxVf_2l%;939s+32@7Bu^8=XKIDXJgx|z07b37c*(MzSJ%qLniidm8n8O!6(&mzG1A{*X#bg)(ag6gRTW63aILZ01nt&Y^53J ztzW+0bqN)mzBw~4RL&fjav*cxCm%KPQtmv>$S(-&c`RCog}E%XJ^A|W>m?&=^g=WE zXnTXb&d8DFoVwlYw9Du4r5QcyvQQ=*OYaLEU|%<(3cJ2Nw5r=dFYCXm=_-Ni(FFP0bsb>DZLG&J6#Q?C~shcDN@t7Yc%L0OCsTKr+1C z_kud$G6YQs`hWm$s?55-yLQ<+d`cb278MeKdxh%%gbN!xo`jwk*~v%fAWIZF`+d5c zq--#ZkDpveg?c1JZ(6u32q^4(bFs2OW#r5qC={;F%Z^f#rNg2tjBfm^gFGo1W?N2C zI*pG6w zjd&}}Y&}y~!jNncwUl~_(H~AObH4~H>pp)pLW8P*0lGpAwb;i@Cu*sOfV-E%UbsOO6|Tovm_Yf5>+a^Fch?Eg zuKeiF1k^`Ui}`W^Zx|RDh>VYa6c?}0c1iXOBF%yV10=_{dkhY`&@tMl)`cO7 z%S-2%#D47DV%8_+vU29J@X*?PY$YV$)Lm#zY|p7Tw-l9k^dN0luviOW_;7d;l(cgn zcxsY23nEko$!_+$!S zWGZgIeYG11RyA?^+=Hxbrx)meG0Vu9?z&RRMj|=ck`Xt~!)ShQcR`%Z;eRkdjijO| zZ`#}Ku;al`2PO+@#D`1t+~T$%Ks_Ncvg+q38E?R6_1*8kS6xfb5O@CY?Z9p7HtExTB8p6>4#8uWk}$pM6qUiO8CmTni&u4Gy{ z^OCbH4+6ms0+=E|{WSeb(~!{cOe_ENalWMG=MNwIq~S>BiRDx8^9a-qBheqFor8wm zt*H`L6ciMIJQxG5kU$|g`|9?DGcq=IdAfY{bStq9i1aOqWbCLZkz7p->$>!vN6&~X zqM|zfaG*K`&@H$!70x12;{gkxC_|cpk9OeX7V>(GdJ)4(o%^5?MfbVX5pl9QbwHgc+{qB>;7_s9!h&10YmOZUy2 zewsGkgMj;l2M>p{*Wi>wl9r&$@{yhXENJ8VXZ!S`&j1IMyAW*J99b4O*aFFt6D?zf z)6sdevY>ARcYh6x!AJLb$6*$0_aDYF7~47dq!#^7dZABCjUIr3WHLSj@`<@^#&ta_ z-@Z&Sdg${KZhV%%{ph@{ZQmbsUg7`j{~l>5`L_){|H`L%fpwwD2xyx7d8n0x`gjYO z((O@WpuJ8b4Aty!Q=6C_W+?fH%HMPO$*BK2?SM4%?~4ad|I>Q+w7zhf!*UhK%gK52 zSmRoRMXs;Xy2<39xcuF^B~HyjZKoOigz1p(}ZaD^tKynbmjNuUuV=-*c`3_W+G zbyZhM3J;IxLKUZ_5#3DQnoV|;vkbBB!4y-cM6k8-!iE{}G5@yWaugB2lNJmx!&K-5 z`SN8F#WR(&DLQ5FF2u+^guRrr`Bu~XP%d}5J4TYZKLj`Ai!LY7UJ|S23GbL+013oUZ1V?m5`F z&v;V86bq#X5P|Eufp-5k|H+!NEVNz0g~B(LX*pRK?>~uJ5RZop%NFljIKEO$YJl>2 z524r(=ctT*Dk3$3pAi3TGPQjZ%oT^daL-#j;HEd2WBI#C?_R%>Ev3L(%20H^*x_QX zVz|Ylg8$@vzQST&6Ir}L|m$)tk!j{4g zPISPw&@O1x+5GQB$0mjF%+8O`<~6{M6(# zVrH^V1^rJQkumS`oBUg5tQE0+2@AC!*hjdsG`;v&|Kmd{<0d)OK|sv9p8CLQ>TKatlgDo1~s{>+t9e=+=i z>eKd+zFu{!i6)IIM=O%G>hIdbZ(&x{Gcd?zVbu1ct**H?^2?G@31p_bFe&Jr5@%La z^bHKy-L_QlSK6x+NGOcFaFiwB^PB{IT(zXyM0V-&D!%W~mBo#4*v|Cu;txp;ymJ3m zBeKb6-MQ<;H|yimYz*{9ouU3UJ4CY(Dv{Uxouu2UP8h2jQ0FOV_&&WEF4kro&iSC? z7OL~7RlQizg>u68<6)(%b|QIYoTla)uiSdC;xiK3=EC9=(waZkN=y#V7%trHC83tU zkHC7j#U^~SR{)?=S5I6ZecWOILVt$TdOKP&9`O)Y+F4?%#(k*Od_~a|Ke}RF;ckgp ztX&A@eX1>Le6p2yCTu)qkpI4IDF2yu354Iy#x&VdLA2)h1^p+#8@=lH-1NHL7I>hy zQ5&b*73*iXVQ+{mZ+A!}Ks9bh;O1w<#5>B5pP9(o@tqhG=RxDj#WcH370VO_LddAn zuMVWzz4iy)w%F{3WvKSPif`%Hm~K^xdHdcoaPzP>Yg&?SGL|-m1DksbZLF|=#P?*Ph@0(!2kCFM67>L{$~#4 z+dk%wii%gsd4HX}@7>$~rj3#bhDrSJu&^Jw)@YB|A$BZzgI9L zQuz&S$$VKV8y(9F$bg$ld}f&{jm0|rVVbR%SD7$%@QtnQj|GH(_VuiOE=R8-bj$8F zSDRTiw_qt#NZ_5p!t6O-O>1kZLc9YhDsBXjSsr_LRf;s7{;tYnHo{djxu()qywg`& zO!8)xOp54uL`Vo>f3tG^Ug@%UD8`UKbJ#nq-kLfdidL?$)J;Z1zMJN$Tk!T6IHG;$ z3$z~jUlx)t#t8*-5Ok+N=tWgmP%2kM7-s?0t{~87o4E(}shNPFr{b_+?PRx9$?D4o z{b;V$-+^=kPyc*(fCug+T8Y&HkgoalW1blUBP|!6Dn(L^q-9iScD$7BvaDR$A3mEi zAReU7={|jvnYlLC6CbrHGk{2)$sN7TL!6K`AC}Lc%H48pJECm0{VBc6+B)I(5|P^1 z?@7E^%Ll@vO4~6dK}TH9vZljJjoSa4DfYH-9< zaf%4Dcrd4gfM0-Jzr8Oxp39Ze{+btmEXRBsTldJDV6ft6_&9_Svv9MUu;`|?UCIH| z=y5X7*?I6Q(ra}_-rKIcBuKGz5!YnkV&sn7bJ*4z(Zg8HS?Sm7j67lbwzu~#U-W@E z^OiOCUn;y*C~)^|=O^Plp8God{t1R#?n*yd=k06OOo#q{w(F`E4Bp`S%J9Ty*Volt zy1m57aXMF`ew=ZZ%Y-}%*f}1F;Al+RruMe^_*Wq<%Fyl2RnCg!(oK`hC(QAq=GQTLqA6Lg{l?6R zJcAG86UNsW+H}};n^Q312 zDTJx8b6^b&1ONRRP2c14391$Kk2roF)Fq-;Kh)ITfW_Cg|uZX zzu-;6p5f&dz0N46k;NvS%J!Quwm9p~q03U7mjZ#~^9<2+zC`iNIEZQoo_xFa&BkSz ze<^^zFYT!SMK*U9H-=MU_oFL*I=0o^yL!SD8>-(lpG|u%@aQ(ny zr8a59EIe7`Hh=*N`#;&HF~6TR%~u0BXsRF8$aPrw{u0eA>}caYlrsQ`7}&) zbYK2xY-f>Np#|!F8tSOG7%k&*1l}(G?!gB=KASevEaucHfy)sW%NnQHEStle2f#bF1r24J&KN zO1VDh@TcP{C?I{fCb|Fp6TN8Swz(HpC>N>9=L1BdsohuN;R?~5dlqR%S7~}|zTQuI zH%F|rSLoTYpXxGYIxzRM4gB1!wr=irV_o%e?J?ILJ~V>4F{i&^d}qYOU?HfWz&xZR zEU_+B}up8`02wJoy6NqgLd+%=wyQq;M006KixtlLIVM>{-z-hHpd+-O|)ea zH>#lw@|9cg{Ou9VGT?o%U6i?R*koMR@i)I4a-+TjVf;T~ojb=h!#8BnWn;AegBa%o zIpO-hy#W8;L8Sj5@BF{PJM$0L@P%(*Tj2f=t*D_1X*Qxnl&_t+!VdB>V}0M>0G{FRNTz zDIF=;qDcz<6gf(RlnwWJ*3Y zUKoyJ<37K_^Jj<%2zjF}IA#=I$4;vGXJqKY^M)pi=W# zP-2u+Mg^ZS0gco1z5kldtiG+XSz|G*_|a8JNYk~(6QV#VyeS$q^zi2A!QuI~#%$QA z1gzMCLdf+64L)$2>xm$PS9WhlrgpBz&i6>(>6-;QnRlrYnQ}#Bjd&J)Rd|Lq)VW&|nuo2CzhD7lAk7Q@<_Q*R3EG+}S1YJ4VIJo936?%GR zCl-=WUn)6`sQ$7zG0{4I5-8TfVt{iIY>V!EHAr|meTyuX>#s&QwxHH9Q!Zv+_&K*^ zlWM=#0Iu`r<5bx!Pr8X619r_~U?-_{Kt@UL22&Lk>M#$Vw8>Vb$`EeGr^;&KVUyg= z*6+Znt@3rw9k*SJw+3gQzGk542KwjhPg}2BXCxe^(Ki9+fzT$|^h4jrEIl48NDO#b>TOW1u zCoyVNpDn(FO-`!62gZVro{84^mZ8i?I$nb8$FLqlwvD|*P1iWXJ7yo{i~xJF@3+Y_ z!tDJ7r<(inZZrFtqo+P?ZeYJRU*D##*M~NqUwiExDO*zX21v()B64zbnH0;2;4v)h zw;vjXeeNMrjo!7tI9u)&%i?0u#*qM*EyMYMHa=^8Z-}CV?(V~+!BeD&CeEJ7mV7Py zBm;j!HgzL+=O~D0V01t;vHN*+LCKodkKw_EvEZQIDq*oPTY0L}#klXM0;hFn>|!;L zXfx=rg-CYmi|v46-o`xwUbATv>AG`)GLIkgwS2JCqeh=*{?dj9VWURR*oWs&fS@3V z)*%nfSfSr4Nr~}>mF0?|K)KfLMmzO?_4bxwSw>&pC*6yT*Ec-607Ic>7IIHFN06`mV?YMr-?!S=!_3y!^OZ!AmMr`;+#u`y&R zX-vJHapO8@zv^6E>#IHe)<+H&=c(X4oxe1tvshKjrYS=tr#GJFevG>OE+ zo`uTFHhGP!`Ol>}V(QF|nO^F`+v9P4LIrQ{76yaX@5o@v`~*0^#C=jPI`|?>nbR`j zY@2o*svd8qz1XjQ9l%!Vv&VfoG(p^_pEmi{DK4wzY3?sJPXo&+li1^y9gopxQtlpy zEe|}5t_f$D4Jt|IGC2?1hCfwv1ex}Z!nYsHd_GB;c%fmnVJ3roRhPLmBf55I&6i2+ zvEV+z^n)4DvXW^A0NY+9RNPzd?J^KdW^q#__;o1p|3WWk4GT7ROK z+SN0_t;PY}uW3k_fs)G!V`hearJryJ{%vh|d5!F%Z?{V8l!Xob8XQ+p#i3N4 z1oGR4?~+3e`U#;xzsUaf3?%^i2jPD^$&ZBP{bR%We%%^rw>d}as#7bwh2aM}feW6V z-Y}x7)>i$RnwpE z+sW+h?cF^+x0?1^RpjK*Nug1o1PB>uwya_v968u`9c8m-Q5N=y8u`!KHOWPx1cD6f zPP(F8stJ#4PFidzlc~ieSu|j-yl9O<9Sd+QRIm`{75}oLS6s`eFlzRY z6tB_zno-#$xK1H{hQaHa+gAHZ9TsN$>)Yo>k7HWW$DryUd$Q`aHgL?&3*~uROv%x* zkpH}e&+FL{vbcJ|!Uy{MLouN~-WQSLv90)k_91PR_5Cm+_=0vOG(`f$kd{b?w!1`h zGNs_K8YJdFlYIY4dPt*#laba}Bnh5}{Ow{PRC`m?-b&g>it&4!!AYJv@wY*B^*1U< zHnsDB#jp8hljh4!u7y(;@0`r<*2_xk9|pbnMMYgawV?}Gqer(ef7?riC;1g^h3OpO zogNOs3*Y;!pG4{qQh|OTplt|Qv~?@aq+MlkJk_Cymg-e#&8$|#&N*+>Q0fe4g|o2= z>O`1o>CU7nogCc9)uM>zR$|O)$#k9;)$VV79^3kp8}ZwW7nbeGPIvtr^K&^yqNy^@ zwHy5>Dmics{`{v=f-a~vcH!7iYctGDdn^RM6V%({VZ`V#_Kz%5QP2KK>Zw8|m#X|{ z{^-!yW#)%rRXqAVy`{9;`+Sek{4sr(d!Hcnz+1A)uvVFv*c6E>s2(^gg%{tP0=Lewrdc`Ot5+7AtP_*y$+s5bz=)7xocY~Ylyc0Tx{73jKe zmucX26|YZBh?$2BHoO6$E@obx>Lwwgg-GO9(5rzI2I8~ooQJll^RPs-_`3Hms56jz z*vY?**+hKAzT2qyfgv1Q`!ad5acp-s$@=1KzFD78Lr*WYOs}aM5+M=p8qID`j{fCM zk4j`;Q05Ef<4+LJXwUh~4q+6D&ldcOkFkj|d84b4a1xb8z>$ny!?AQtLI6YztJu+|ytQ2yoNNdCqbV) zdep2Be`smHzRz~)IpNPsKsq~|_Lt*Ovenk~T`ah^SiYG_OUtWPJC}Akx?Jre_AnYh zBEgS0b*inTn!5DWjXM6J<8jFETbee~K;R5o4&l5Zm4df@|Fq$-*gx4enG9>0ur@9% zE`E*%h-t8Br6HXMP_?w72`_90{VNU&>d6A0&wZk=mrH_qtgyT1x4bZ~#_u*yz%u0O@;cq~ z#?iCOy|(=KP;*SLm_=`?|Njm1`5$vLQCU9{ANN@GJlSj<&>0~{0x+9rLo$;gGzL~H zdjezleIp)WdASx#{wlxL%x@Yt-z8e&1;daLJxKGF8K@wk4k55OLFx=q?$QSMzD%HHM4u#Adi6)mucn`l1t5G zb(`TtFcc5>s3(KGa6Frocs(C578F;h0~NK1@UGP5snbH`g`yQ6lh{A`1EU>ZaXqvL zIt%8>uecd?>994{O;@Z&Ka27h+tQ=ej;;l*@UYQ~(_!Z}o5@J%v`%jBm^A9mLJgF} z)G5;u&60+vQ{ABJvI1Y7eoG&lKDl-zVsfqz1($^XHXN5U?V)IUL$FsJk={C%he^z- zRY3yZy;3_!W>LEi7kX{rnrMpoGTi&R`BW1#0(4GR<&TZ!Vo{z}7i+M<*upYA<~PUg zuDgZ5dP$$!cnn3UW~($~{`b*p1Q*zl{JN{{y-x=!v4hh|wZ39_fc$-7>P9o7I#)=} zKSSR8oc@Q%ZN`RVwJ4m}@(I)6R8h|KNrr3pqgSof4bE1)`9%!6;*+bIbKLTNNKGk>^!vZJ=1h4 zO8mNk8Y~S;z`7>1sWW#Aua!MH?crKd=AZVp6Q?#bvz;bd@j`ifHoDoJCqyp}_iQ}Q zR&YbY((@uXh&FxF=OaE5ryXXZYw_SP$zk>kjrjK)AOE<{+rojwlBFQZ?A@d%FwHjF zo%!FH7+R$~#yeIY60$t?G!zJQ7EDKd4&z=d_nf;pz9c5lLy!KVdOEJP+IdWMP;*)}Q@WxTP3GtWD8U3F9K?s3sh zllomDd~4&e3IVL;Fr7nzk#no3=y^}kFHYW8=7KW?nGyf!RM!oQmv+Q!k>Moqc@%kD zOF^l3@3PtIOB3F)Od|2&7ROYuF6ys#r;$$4&Gl}=Y*@@Uey~<=S+=%(pr2n=NVrDC zq@XaAyjs7ic)%&m&uR=|8EEn^@m*G)7JpqCfcf#p)q#_Al*zF}njcuDVX8f(`u(&AEEgomAdlyzOL=eUOui$Qip=1 z@tZZSV2+ZQRr)dik90W67MPN1mx2>{>i44L*1`j4Pi@~8Z)%3FWD=iDI}jWEr0se! zL(2T7AojE|tJA60ryzTOL%V{d*Yc0N8!sfd1>yTA4Lv3xVqDuJJh436B=1#;TDw>E zeLSWd`TqA&;Fra6FGL@M%G)JnNptqjufABic`E}Qo&H4%F!Q9~N%p4tWwvQ>{HsQq zjiCPGTK`G(xK9`e*D8CT4aT`siJ$f0hu1(YAw9oqM4HSo^oBU?SA8pk9-Z0c#?(TZ zH;KLhu{qbo6OaXuR+D9Bl4xiz8Sb#vUqrhz+f=w=wCTUY)mc${kLZ@KAjMznvPc8Z zZ~+j(_cJ0u(PDv|>mqdG{8dT*J@FqeNy54A9w{m5Jn%25T1o8U;?f8ltOY{lm@=F_ zgCd#MB|h`>IRkGfD!1H3lnN`Hgg*(jv9M|A2}F1O?%<;E^VQ#f1eFOgC4&T%|FYgA zVlf{)r~&2h?qQC-}e+3=cNUa($`TXOt{Y2;fC;s~Cfhs6&@q3D!qO{eAka62in z5lqQVZ1*AH)UQ-xxt)qjDa+a?G1BmR+7$tqwZ@B^A=ZB9Cq-q_1X6E620x>B$$c50 z_sa~!VpP?-RO5B*NMTLyC8ZZr;fvJxAO}+BqZkA@F-y>5vF9HOHSgYIf8w#t;3_lP zsW&Q##cKIST3QYD`Dh?3N&w#2oi!`q!83+!>Ycsm<%Nvi$PU5UTK;FB_9X5eg?xd_ z0NyYlT=@xoXg6u@SmWAd>sx*?=en}3rT5u{SBGJqBn#OLiSDdy|ANkl0MvP31P#M& zJ-BMyC~_KNQlRA0FW2lpM3e1d&DKR7sdak%`E6InFDJWdnDm#bUP!GKIqz;llL{V^ z`OJNQ#2-+J`ty~wwS$b0du5yUzPT*)dLwF5?d|Oc*53xfQubFqul$?_=A8)zf9%f) zHd0Uj`DKtdL955BgP333du{y6au4R+mO2na{1zfgH5%jPt*xzTp8bcXE$y%P^Pg@1 zf2A1vwTB~*o@O!|cPrjMYTOkFh7|cuWYz%i>wi(7|93^I7Ar9~0t0|jp@Nk`_(Il& zMh><5zW^Wn+vbpif9J?vanQ?mKhgP=6dhM+@D=~kRJXf+Z59u>V20ZC6q{~+rWO$f z-*%YH!7DDPpi-^xk${{ygT!EpSrS`10a!WS+B8@8>eqn6#<%Hx1<+ji2^-$0T(^4h za`=JMd$Zs@CH=(hDt}tIcKo!k@!P1h4-caEG%tt7*Fsh6T_XBy!m$!-Wm^&PPXq{N zzVgFzje2T0JR@u>8I{7y^s_}45vA)rONv4#*L__~pTN1)J}Pum+mskHttn9ZT9+BW zl8@z&oLD~^J!ESz4E2efJuWwHcB@n^7q{-Wsq8J_mg|N~@tp>-kDkX#5F!mcs!h06|PDw>ysjz<)AM*1&aSJzI%$Gj5W>qzd{5kt|2Lwz?J2fivGr zICQpIf8yq}+Fwhh;cm(yc~-TtY)q9?(Mqu9m}2W0K6P3_JaYZ>@BqDOvI6L7PCQx7 z@3Hy$Ko3~ess@xtX7aSeb*4#heJBIv68jh-Rr&{#8A?+Zvy27C=*K^+RsJj&y&7vS zKXv;#dfKEF-@}xYsqX5##Cp6Y&tc}1uQGnouVGBwubCX&u!$|1Ox)vICe1SIpHy1v z#G_Gab$&bt#INuXg;s*%^4!D_$DK1eA6AIK0=`$CS{$XCyWG+&R?*nl^k%q#+wvif zy0>niPThJ6#caLi6-Q>(m1<$pZ0}fVMZHtXA+2L$8noMl*2+z)C!=1+7O|_hF6(m2 zj{j=P#qJ4bYhC6oJ3knCgYK5GA+=NyLZFk|F~?6^HEmMm!Y&?YS-G{$(S6QixB2XJ zu(!0r*&&5v3zAIygr@ekCl>=L`C!~j;SMHjR`Sa4I|ey*?JY**sRsKeiHf<=n*yk4 zE_Qc`-BU)Wvmv>v!8G9oZ^0szMx?@-=%vgyZLrRv zxO|IEv{q$n{W(pOXL_TVmy1fL2a=AU#CpUKyOKS3wWm)l2xVSDOHlV8XxMoHk<9XK znw!NPg96gV86@VB#s9!u#Q}$37uDC-pSSX@-v0eqij#8Q!F}BIcVl~$lCxm`Jg-*k zTCt1W2O@~><1qlaIp%jg#lQp3nRFQoHd?-2cY5}S@sIEME73cap~>jPpTx>i=?f>( z_!-?_UA^SdcDSY%( z9Xl}tl9IAAdEcAM#X_DRc_Z19{j;;lBA~7ejiYHaawDwuW?;R(0RYHsXtS;3db5aX z>gmblzGDEWV3EWpymncrZ4V-V1p9vhKF_G3v-|JKSY%ijPn=lQlIk=wk))wH|F_-k z6kDH@wWXm^wUo+~mf@fQw9TJftk6BMFNqLV!nd|VzFQ~fVwD_UIHs6?k5D8gsit?N zR#B(&XYz%d%O7_IScRUhUo2hVrWszu7mMf=eX^jggL zIHo!ZJc!}u_&QL# zXQ`Ac?WC`AQFh=un_>H0GJI@%x}x!TV%GWUf*m}Q>>ce?ao2HlIb4-Pg#_BWOXaA3 z?ZV3972d0Mc6l5ouNsD$^NyldqR92Nx_WnN+pJ$_I5%3`CKt|S#pLASe>HP09euC2 zl{4HmbC7@Br(6x^wenZ@26FVpX!>>Clbcg>_CYRrc6RoV)WZxk5@kEPG`odnVTjER zuK{tN6pcJ23{BN?$u8gUg;w8AKa=S4K!}6o!6#%V{%Y+u3T*Y{wcQ zm;fQ%adxgtj0iZ(*%D^LpkZAF@zqPGiT1SJ$ckH zQY&}7;;|Mx>B8gay-X}BbI37muYOp=_U7~Q)lZYcUu!4(wUbE1MI|L;v-WMJz>Cbc z*;$=Qn1P0>YW0&Bj4|=?oQUAG2&J>s2Z;DDul(`u6hJrNr*M}(_~gyhI+N z!Qd-$(&Qok{i7XfB;L{8yMJPn8ddMyxh^U!{2UPx5h4>3d$`+FR9b3V69$99E`IKn zmM#PGkm2U+Rx>qH*(pOYALHD)qBh&Ug~S>lBv`IJw2tYknGHM>LAMLwk$A`_rY*3e z{HSIrr1o6;xM??X_N(fq(z0FJBaKuztYCC~xvWU=B~bqkSbQsS1vjUsy`R+E>Ix35pro zn_0ZO^`b+3hphNL5gKxGVa+WqHEw(QE32!Us{@38C!=F&x9sCPT6!B_S0-n>yxJI? zqUs#QT*~Aa_Q{HNtgNwS5z~P%4fV9c6RgwOJf50?ej9=3^-Y?&Ro^|neOVjB)qv@b zUte7wU7arWf*t93vaO`AUj|OytsW*sMs$LX|kB6{5U5^BL$}SM?0U+-{hxq2LphxM?q7^?Z~9SCz+y7ogMk#IruO*eLl#u z!?$r}&jUX54F$e|4zm>gDI%U=pkax;vzLTtag+DZHkmo#8;)w>sKH?(RPw&aQMr2A z2x|Z}2Yf|>H8v`@d5+`>M1nRbUn`Be_}-GBty@}NTDy~+^YI;SWz3-z#Nj~Mk_*Qj zy3bc0f<1bIGNF_Gy8DfAH|~?^7NrJU;XdlxMM^etTza!}X_=fD-OE%F&X-Y+>AmHG z-5p`IJ0YBOKcnxQy@E7a1U)l{2Z8@u8^pgf^s3=SE-Wj{gWGu%?DzGt7gN(@AGY^{ zZ7_G7lz=^tL*{9t3wYX9%~797i&IoYVcXfoMG@omf)&sjIl$!o`xuQGz=<#BrH4L0 zX%Q$VK_I=@hM6+BhcQ9t5si7y)H!21HWRT4X(Cd-wMy~fnaRbtZQZ;Cgnj_Ljc~L7 zSpGGe3tpB)IFS_ead`Q{q_+SDW4y^gY+OecJi8+fzJB?E9uVc-JG(xTUC1@u%e21K zp4$q(uM>MWnvBiP&hGG1Lh=NH$5Rzd==cBLee%Cm2%+@?3wGPc5Fxl~2FF$vqHzdF zG-kKg*Z)>tfztjPk{1TnyLZAD@xD@#@slV%_lAKx6#f$4T|9AePz^YARuW+(xms0b zG=?I6ByY(?ko?~*-j4l#N9N${tc=nsbu2OcXhn7pbZ{+!f44snCWe3)7c`J0<=>u> zus(gUWpIx8(D(6WRBOi zxa>mh2C0#yFfaF903%QDuNByW3!+`0im-|!0-Xn2DwE}AMw&w`pN)F!z zx2Tw{N{lB5Ayy%zffh=cEqE~!!FDk?a_Mrg8S(xVQ0U;q5l;3F<;y61;vR{3KNT__#DUJoG7JK|#49 zr~QP>GWE$ySbTUGJcb5daidf6P-Os>l-_bIRoS#+fst#aIvodxO*&gbDwYymhP`LK z6|XAq{sN6u(aY(A?gy_ti@+$Y)1MOCQXmQxNy_2U(sGqNY_B@Dc-uKM(NF8SIByzX zO1!LUToC#Z6~1of&BHOzsk1~s*wdTpW~G{&8_TRa94O;P%l9^l?N~qluzt#Cv7Naz zWjV(zT~vM$`A;k~6P?^x>KYm@^+Ss{`==ya%8~DnNc(yl3~wra+>66W9GjMYDvB^>cu!$thiIx@3hYH^{5N{z53(CI!Q&1CBBw4 zRwTeC%BJo?n{&udb)k7MNl~%7~06zm%~e`R}Z$_y*;(>-tf;HcGYm#^g-@D zdEf(ES}6jFz18I$o=TAI5ig+A7F0fNJZA}C4|xux^KKS%X$vy*J`EIN{ZmhtN&gX?}gpF%*zn0_&dPRTG32^7hIBmD$XZ)`;dx2Pay z+3e*GO87|DtDeW-`f+L9Z>*)F=Qa;xaz&OtD{Lr@D&gW1sVnu`h+qAp2{foxXi?ROc&!qe;maRqjP z;PQhhU+SL1^wqUD74v>ts*gwexLtRh8okbSysyvAL&C%1lB7GTas$U^v*v?dLh%gx zaNp}S-vb$RARdwTWUN*NAhDOyv2nHVrTh!rlOIpvBAi!(=Gik5;JW;)JpzT4+s&vT z4U03toVm2Cebg7uV*g14Q;>33E?bcggEGF%!*lo4HUxvV1Gfk@ zmwkdO82?FFcRd9FIje#j52;jD6-&zldU=+racZUocwpG)Ix!k%*0m|@OLd9T!w**6 zO!I~Z_U%<{mbr@BXyLVUtE{;@>7hza@VMSOGEsNaL0erjJzhK+H){wl0FYOe`|pKb z<@P6~^i{ntvR5hSRXU>I@T@ggv)R0R@ziDn=4d<68L36vXOJDyZxhZGLuQcbHvMm* z%h=53Ikeb7s*Sn_o7!00zs}ORlrn7F`eFwFY%7C(BsQm%Z#cW(aP(EPQIB1aLmVUMt{IBwhkwQ> zS9sNm$EK{Y8e zPsOSRCkB4zxI5|)N%IqWg)^2?jfh$-P}WhZ<18O#t6Soe{38mvSf|OSR9cN#e_~=b zqSnFw53$pluE*<__cF~uxi7Ai>MangIZrC; z`~@JI$q~;3?BBx8{XvQ#@Yai zKwg$8Fhqbho_q-RrM$&2!+7y=N#b+3CDxWNdl{RTMZ;MMOr1SktpB76JWX!pd!ygF zI@Mi4*cpxE?M7uat#UhEWFINB=gnucZM#}MI+|tM*m+5M6dgp2>|Qp!?ALovNw3%e zm!BE5Y<|DeK6!OJ#8**hRsu2>4$UcT_EY&w#laK`QfU>K z-5M$#BxavR8RIUB$_XPr})~)T?Ij4MS%ov=TxF&#+Fi{{cv@nExw4@(qJ%>un}qfY17} z$gBN~et>FSKeBp$NJ~2fKN@x!G_{y4i7z(+RxO4O8$*=Y_eH>-AOkcFFYjF>Wj3@} zOtO#uCxWwIyYc_exa4b0Qvm(soYPs`VLIEOC|RXtGXmwFXLHis7aEA@1d23Mx$5eb z?!yI|y_S1pd+Hv)>`(!>Lh$=*g-V9?`iGTfMcJbT!%v1YmO+`QzN z*hWn)%H*WI<+*rcZVL;myjr%GN&`A2!D#ZANOewpP$Q7C_~q-d{3Mnc$3u%p5H1-4 zp_j_i?_-YoUI!8oku#pgv&7q|hj42@TAftQK@oR$E+6jlU{~sM&&wQIO@97Nj@BbL zb~&?ubM%n7o9|JXL;TwoR}BrW*TExKRe)8#&E_ z?30xuxa(@eI5{Y2r>^?Cl28&Y%2N4*bEPhXM@BRB%(_>`F7GqDBn1Zt0~adC-=0T& z>*A|$vy1F`m{owGf2?Lj!y`#||AutvTWkjthRE?k>0D)TxORNFwnK3Ac zu48t)Kl8NO9Yf_cl=~K(H^*qMmUe0?Q~Bu_85#B3T;)J_tVXf|fxSYO5U2)#)oug! zv?>Mkg?2BOOBPFy%xl-3bFyuBRak7NO5ou$-t=<1zbsTzUdH~>-Do;P^H4E0&0X-h zv;}I9#KOWtqt8_%h`>_AVIhFHuI>NjEMX6z%+Q@yG8V$3GN}x#+u#=%Xx)N0d=A!Fth2Iss%NWvGh) zqkH!JyT@i>{>sk4EslSI8|AIKpx;$P&D|NK)Mz*r(asb4Ti3MC~M z!b{Hd34A{&8=U8U&*!A;A}amx%_8@g>Z|BR*X=aCtzN;_yr16;POYhT9zybI9i@yZ zTeya;Z7G=FYTp|H+rH^r+c1Q84ooQC99oZXyJn`7BM?zAHd|Un&H9PXK0034>rfG| zIXF)8v{V&`_UwMFn26s>O+ls1{fargnEan2kbjIIL0E_jZ;qW=F*h(Zh1f<@I|&fI zX7xBr9(sOn>S9I0$M!yVMjJXU0Of) zsCO&GLQ}#D(Dq%ALUWsxI_S_6%F$K-5JgJjz zK%6J5k7`?W<3fbwQrN0}kwHkVLJ&!`2$*UMLTKF)FOWg-WKoqf%xJRf74Zl6G?H+5 zXkNXG#l6rmzbs*#?A629E}s?1u3K^meFoP_zd%cE(xe%Al!Rx}CX)xCP(6I>bDpag zDjqH)9+dP&s+NP+(u-BPu9oP?e1rT%Q$rB88eg-QJP1nHpQ^vu0H874U0~6Tajg4Li~chsH;+B zE~%oJmn$pb^8>a9=~E+`YYcWpDh=ZKbg7*3@ULG`wGx;hMGYn3p0antN$jgt&HVBz zI#HTL+@yqx1)sI=KaaK9DynzzSvXVvEStq5anCnYwd(FQA2eP`Fon6TJM+C=IDf%R z8Av1!3Z z$t#D~{ucpY-eR9RxKoZetVbo3)tta?O6xS2t z=$rKD;utlC=vq9E$0w9 zkU&1@k4#i|Pfv~QtdsWdL`17&*aXN@y)(V$Jk*bm(FT>zFM8e<55>`;n(Gzjh%zr zCvM2p94Co76PAj9n{X#Eb@u|7ZcNr#D0EXTr#?z_$w{huDy6ow*ECHnMMctt93_*z z5rPwG{DLYhi7_DlV<+ZKB8QD8EWhr$L5ys7-s`HA-DY&da?zM;*iavb99aYgO8>W0tIIA9v_3aBS78NekM3mQ9T zX>40Ree7&N&9_5Yhx3{7u{wpO+FiL|CEwzS(omVs;OZt`3U{5cReQBy&KU2>j+G@f z$#{iLPhp)=-toDyXW2~#k8o$G@MY{i-@UBn#ZeLqO5&a9Tb#(Uc(E=|PYUCi(OyyJ z<0N`|SqAW;Ty^Cza#2b3a`Xs+qRGX#Dmt})6i3!g>=_iiTfi0O@j<_540!q_)oY^6 z*w!ow-dG-tO`1o9iDh{l6q0U)ar%~mqSBdJYz*m*9Cq;TkEVK|Xoq&_#pYx&cFF_~ zHVl~V}g~a>VAfZ zSMjn(KJY?3l?p-IR&%q+YOoi(Cu-EYqMmAd9xz6X5`U$42f=Up9YF``lQ0RFGY5yh z6GFRY;q(+9Q};oteEE$aLi=y{er8@?y1|&pxL4XSBI1^LXtdF@=}ZD%FW1(-y^=iu zVX{oUST=vf$)uIZ9mqXU$s4M$`0Nw<2pywme0)7tgj^~pj;$UCBS7}_qxIW9n8z#8 zCH3=pm#DroUg`_J#Dfpw{k(OW?qV0XD4gFGYoySZGVUT@QYCvmnc;lnZXcSY&i0L7 zBX8QK-r-!#IHX^1!42qbC;|1=;B|AgSAzmX6ZiM`w@*g%<+rZYPPNhw&k60vV2g9< zRi$}(5^f9Uk}RKiP;ql_g1?HR(;DL^hsf(0;g=jdU;0u0Q!mjhyIYx^x*$zv>{CU| zT7&A!qs@iF$R#zW(RuBWl~Gg`Qi)6Nprpx~VsefWp&7VUNr@hbCDV?LkM>&D)xL-*|K13&?-29ko+)YK?x=3x%tPj()jqTe5}xicLP zSNAqRZ{-rRvFdG}DRupd@_ccv@C*(U;+nS^(}a^HTGQF?EvscqqGHOukUZ^}Tg`T{ zo7|?0c=hVuJa0`g502W-&zRG@H|YBBJ0D7E_s8T9V??CH|@5pj|L<) zeAt+6yP>yrKL{q^^U=k0bv3D@di=n4u~jA1zEw-XZo0(&=G@rBvEPio_1p}6XXVZK zM8Q8bo!Q>L?D4u^1&!LjatuERZB#i<7W-@9^p8hyMa_e=$>QnchB%{dvvY+kO~<(~ z4xG6B3Kcm4%eTWs9`Gyt!@beD5n2Vl0)KMLtm(r3Iu=j+&e=lTw0^Bd@xD$ACCMtz zS*lS@YE$bI)Sb|7oOsEw#HXyy^qWj=niQi?hZhUCZ2<#_KRoDarGxQ+)70pEx8@v} zConus`lNpk=sg9k7vm=>wiR!61uvTNU1W^2KZysO?D_UPl8DsJ=GHCx1dE8d>k6DV zIeJD0UiRS`IcB9N`$l_Rf3L=#XM8oVqrGH6mu?XCGXxp4(41%Bv1kwONnIcIuG%$1 z#^-mbGo|u1rhCz8PmF=*&Lif_u{FDR!BGVUl3g{0;nK38e!K?{<0RXHk|v6+jkmr& z%t3bVjj_%2<`=G;@v^QNiBH6!z>4chcUufUYPw`K9UaA1P2N~BKWYN9VtkBfLrnn2 z=u9)0oOONtxa^roDn3=aQ+PUy{uyMvU!?tTJZFdl+mF{YdZ-Q3hvg8s&iipC`8(GcTRC~y&@&iyDO6B3xMwg#;ZR5eDJ~@)D*X%9u*?bUZ7Zn>fO22vYw3+VhZ_Y2Zt6xR2u_(LKcL0uEeX=0I-O)00 zTpV$33KZ@H72of%%F2))ZN8wgFp!yaLL=Jsu52TH|t@t2M37U=C~}i ztdw+I)o}1{0v6wEuT*|dj>|*r zZ;PHKz^{<|cST=Ck66F*srS50^}i@?^8!xOZu(X(QhW~ngum~BE?N@CmEE_pdazMo zM#vb=x%ZG0bRgib;ESTBGLuP+68IaTUg9q*p5d7L^2uMrT~*EBbeEdoZ<*ucW5}t` z7s}UoK@=N6gNUa^i^IW6Wqm7(fNS`ACL}3|fL^~j;VzJ56O1Med|T%1>T>YYH|g56 zEQ&Pqj6oY9+BRQdceey2d;_o71A`HuYkr9?z+xm3!+*slUeyDgoc;Y5b`uMW^(?A- zpkN6tG2w+tnDd||O`eY`Xrj}0@&zE2q=J~lB2bTcrr5FRwXMHpKo5Bk6uC2pi%2s~ zcYbssi6ni7RID%CDE_XQOs3dGlr$?T-C-#2p^QvTca18N+VM-tDWE}tT#?^jb|rj#a)_^NV1{!2=qpEk=epz|&Ey>CurG4yBY6%@;Ux}MJ{x`w>bt-Wqm#QgBz)C$vC_wnA>|F>$3am0 z!>0_*ZqK~_ggq89ROYA&REr`?G=1M$%}~pAbLn<7etDj3cOe+uskG<7B@pdKj}Sk7 zcE%M5N-MStGsQ}WeF_V^a$J9Fq444t<-r7YIyT2;WoY(yq4n=CF{IQIht%9mrEc1jvkaO-D7vB5~O5=I^)y(OtB zP5pi1vAV)xJ28haQ=0^%CH7rnccWo1`r>ycC05AhjW=X>AZHR^WRaUU^s^OLlGOx7 zidzH3FQ2H--ft0MG%F)_)(j9nGgr%Ni^)nO@oDnuPCIAf8iXu*pvUehHswbG_Vs>d#=9 z{HQ;rP52NDU-k(~z~dT!&^?5*c}2~VbBoNRju>&S?`b+VF-nJPF>=3?rqb8~d5a^> zIMo96RFn#1A From 3d59e3b265c68f44042baf63fa9087d098f80666 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 11 Oct 2023 02:51:50 +0900 Subject: [PATCH 255/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20README=20:=20?= =?UTF-8?q?=E6=96=87=E8=A8=80=E3=82=92=E4=BF=AE=E6=AD=A3=20youtube?= =?UTF-8?q?=E3=83=AA=E3=83=B3=E3=82=AF=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4a5a2958..4dce0670 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ ダウンロードしてexeを起動するだけです。 # What is VRCT? -VRCTは非母国語同士が会話を行うためにチャットもしくはボイスの翻訳を行う会話サポートソフトウェアです。 +VRCTは話す言語の異なる人同士が会話を行うためにチャットもしくは音声の翻訳を行うことで会話をサポートするソフトウェアです。 これらの機能はVRChat内で使用するために設計されていますがその他の用途(映画鑑賞等)でも使用されています。 VRCTはあなたの会話を以下でサポートをします。 @@ -27,14 +27,14 @@ VRCTはあなたの会話を以下でサポートをします。 - 🎙マイクの文字起こし機能 - 🔈スピーカーの文字起こし機能 - - その他の機能ついて詳しくは[Documents](#Documents)まで # Documents -初期設定や基本機能、その他の機能について記載してあります。 +初期設定や基本機能、その他の機能についても記載してあります。 - [Documents](https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246?pvs=4) +# How to Use (YouTube) +[![](https://img.youtube.com/vi/mI4DQaeaAPI/0.jpg)](https://www.youtube.com/watch?v=mI4DQaeaAPI) # If you want to run it in python 1. 以下のバージョンのpythonをインストールしてください。 From fe34d269dc58317fec1cfecd778946cbfc7cebd1 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Wed, 11 Oct 2023 11:27:55 +0900 Subject: [PATCH 256/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Controller=20:?= =?UTF-8?q?=20main.py=E3=82=92=E5=88=87=E3=82=8A=E5=88=86=E3=81=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 713 +++++++++++++++++++++++++++++++++++++++++++++++++ main.py | 715 +------------------------------------------------- 2 files changed, 715 insertions(+), 713 deletions(-) create mode 100644 controller.py diff --git a/controller.py b/controller.py new file mode 100644 index 00000000..ab976748 --- /dev/null +++ b/controller.py @@ -0,0 +1,713 @@ +from time import sleep +from threading import Thread +from config import config +from model import model +from view import view +from utils import get_key_by_value +from languages import selectable_languages + +# Common +def callbackUpdateSoftware(): + model.updateSoftware() + +def callbackRestartSoftware(): + print("callbackRestartSoftware") + # model.updateSoftware(restart=True) + model.reStartSoftware() + +# func transcription send message +def sendMicMessage(message): + if len(message) > 0: + translation = "" + if model.checkKeywords(message): + view.printToTextbox_DetectedByWordFilter(detected_message=message) + return + elif config.ENABLE_TRANSLATION is False: + pass + elif model.getTranslatorStatus() is False: + view.printToTextbox_AuthenticationError() + else: + translation = model.getInputTranslate(message) + + if translation == None: + view.printToTextbox_AuthenticationError() + translation = "" + + if config.ENABLE_TRANSCRIPTION_SEND is True: + if config.ENABLE_SEND_MESSAGE_TO_VRC is True: + if len(translation) > 0: + osc_message = config.MESSAGE_FORMAT.replace("[message]", message) + osc_message = osc_message.replace("[translation]", translation) + else: + osc_message = message + model.oscSendMessage(osc_message) + + view.printToTextbox_SentMessage(message, translation) + if config.ENABLE_LOGGER is True: + if len(translation) > 0: + translation = f" ({translation})" + model.logger.info(f"[SENT] {message}{translation}") + +def startTranscriptionSendMessage(): + model.startMicTranscript(sendMicMessage) + view.setMainWindowAllWidgetsStatusToNormal() + +def stopTranscriptionSendMessage(): + model.stopMicTranscript() + view.setMainWindowAllWidgetsStatusToNormal() + +def startThreadingTranscriptionSendMessage(): + view.printToTextbox_enableTranscriptionSend() + th_startTranscriptionSendMessage = Thread(target=startTranscriptionSendMessage) + th_startTranscriptionSendMessage.daemon = True + th_startTranscriptionSendMessage.start() + +def stopThreadingTranscriptionSendMessage(): + view.printToTextbox_disableTranscriptionSend() + th_stopTranscriptionSendMessage = Thread(target=stopTranscriptionSendMessage) + th_stopTranscriptionSendMessage.daemon = True + th_stopTranscriptionSendMessage.start() + +def startTranscriptionSendMessageOnCloseConfigWindow(): + model.startMicTranscript(sendMicMessage) + +def stopTranscriptionSendMessageOnOpenConfigWindow(): + model.stopMicTranscript() + +def startThreadingTranscriptionSendMessageOnCloseConfigWindow(): + th_startTranscriptionSendMessage = Thread(target=startTranscriptionSendMessageOnCloseConfigWindow) + th_startTranscriptionSendMessage.daemon = True + th_startTranscriptionSendMessage.start() + +def stopThreadingTranscriptionSendMessageOnOpenConfigWindow(): + th_stopTranscriptionSendMessage = Thread(target=stopTranscriptionSendMessageOnOpenConfigWindow) + th_stopTranscriptionSendMessage.daemon = True + th_stopTranscriptionSendMessage.start() + +# func transcription receive message +def receiveSpeakerMessage(message): + if len(message) > 0: + translation = "" + if config.ENABLE_TRANSLATION is False: + pass + elif model.getTranslatorStatus() is False: + view.printToTextbox_AuthenticationError() + else: + translation = model.getOutputTranslate(message) + + if translation == None: + view.printToTextbox_AuthenticationError() + translation = "" + + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + if config.ENABLE_NOTICE_XSOVERLAY is True: + xsoverlay_message = config.MESSAGE_FORMAT.replace("[message]", message) + xsoverlay_message = xsoverlay_message.replace("[translation]", translation) + model.notificationXSOverlay(xsoverlay_message) + view.printToTextbox_ReceivedMessage(message, translation) + if config.ENABLE_LOGGER is True: + if len(translation) > 0: + translation = f" ({translation})" + model.logger.info(f"[RECEIVED] {message}{translation}") + +def startTranscriptionReceiveMessage(): + model.startSpeakerTranscript(receiveSpeakerMessage) + view.setMainWindowAllWidgetsStatusToNormal() + +def stopTranscriptionReceiveMessage(): + model.stopSpeakerTranscript() + view.setMainWindowAllWidgetsStatusToNormal() + +def startThreadingTranscriptionReceiveMessage(): + view.printToTextbox_enableTranscriptionReceive() + th_startTranscriptionReceiveMessage = Thread(target=startTranscriptionReceiveMessage) + th_startTranscriptionReceiveMessage.daemon = True + th_startTranscriptionReceiveMessage.start() + +def stopThreadingTranscriptionReceiveMessage(): + view.printToTextbox_disableTranscriptionReceive() + th_stopTranscriptionReceiveMessage = Thread(target=stopTranscriptionReceiveMessage) + th_stopTranscriptionReceiveMessage.daemon = True + th_stopTranscriptionReceiveMessage.start() + +def startTranscriptionReceiveMessageOnCloseConfigWindow(): + model.startSpeakerTranscript(receiveSpeakerMessage) + +def stopTranscriptionReceiveMessageOnOpenConfigWindow(): + model.stopSpeakerTranscript() + +def startThreadingTranscriptionReceiveMessageOnCloseConfigWindow(): + th_startTranscriptionReceiveMessage = Thread(target=startTranscriptionReceiveMessageOnCloseConfigWindow) + th_startTranscriptionReceiveMessage.daemon = True + th_startTranscriptionReceiveMessage.start() + +def stopThreadingTranscriptionReceiveMessageOnOpenConfigWindow(): + th_stopTranscriptionReceiveMessage = Thread(target=stopTranscriptionReceiveMessageOnOpenConfigWindow) + th_stopTranscriptionReceiveMessage.daemon = True + th_stopTranscriptionReceiveMessage.start() + +# func message box +def sendChatMessage(message): + if len(message) > 0: + translation = "" + if config.ENABLE_TRANSLATION is False: + pass + elif model.getTranslatorStatus() is False: + view.printToTextbox_AuthenticationError() + else: + translation = model.getInputTranslate(message) + + if translation == None: + view.printToTextbox_AuthenticationError() + translation = "" + + # send OSC message + if config.ENABLE_SEND_MESSAGE_TO_VRC is True: + if len(translation) > 0: + osc_message = config.MESSAGE_FORMAT.replace("[message]", message) + osc_message = osc_message.replace("[translation]", translation) + else: + osc_message = message + model.oscSendMessage(osc_message) + + # update textbox message log + view.printToTextbox_SentMessage(message, translation) + if config.ENABLE_LOGGER is True: + if len(translation) > 0: + translation = f" ({translation})" + model.logger.info(f"[SENT] {message}{translation}") + + # delete message in entry message box + if config.ENABLE_AUTO_CLEAR_MESSAGE_BOX is True: + view.clearMessageBox() + +def messageBoxPressKeyEnter(e): + model.oscStopSendTyping() + message = view.getTextFromMessageBox() + sendChatMessage(message) + +def messageBoxPressKeyAny(e): + model.oscStartSendTyping() + +# func select languages +def initSetLanguageAndCountry(): + select = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] + language, country = model.getLanguageAndCountry(select) + config.SOURCE_LANGUAGE = language + config.SOURCE_COUNTRY = country + + select = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] + language, country = model.getLanguageAndCountry(select) + config.TARGET_LANGUAGE = language + config.TARGET_COUNTRY = country + + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) + +def setYourLanguageAndCountry(select): + languages = config.SELECTED_TAB_YOUR_LANGUAGES + languages[config.SELECTED_TAB_NO] = select + config.SELECTED_TAB_YOUR_LANGUAGES = languages + language, country = model.getLanguageAndCountry(select) + config.SOURCE_LANGUAGE = language + config.SOURCE_COUNTRY = country + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) + view.printToTextbox_selectedYourLanguages(select) + +def setTargetLanguageAndCountry(select): + languages = config.SELECTED_TAB_TARGET_LANGUAGES + languages[config.SELECTED_TAB_NO] = select + config.SELECTED_TAB_TARGET_LANGUAGES = languages + language, country = model.getLanguageAndCountry(select) + config.TARGET_LANGUAGE = language + config.TARGET_COUNTRY = country + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) + view.printToTextbox_selectedTargetLanguages(select) + +def callbackSelectedLanguagePresetTab(selected_tab_no): + config.SELECTED_TAB_NO = selected_tab_no + view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) + languages = config.SELECTED_TAB_YOUR_LANGUAGES + select = languages[config.SELECTED_TAB_NO] + language, country = model.getLanguageAndCountry(select) + config.SOURCE_LANGUAGE = language + config.SOURCE_COUNTRY = country + languages = config.SELECTED_TAB_TARGET_LANGUAGES + select = languages[config.SELECTED_TAB_NO] + language, country = model.getLanguageAndCountry(select) + config.TARGET_LANGUAGE = language + config.TARGET_COUNTRY = country + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) + view.printToTextbox_changedLanguagePresetTab(config.SELECTED_TAB_NO) + +def callbackSetAuthKeys(keys): + config.AUTH_KEYS = keys + +# command func +def callbackToggleTranslation(is_turned_on): + config.ENABLE_TRANSLATION = is_turned_on + if config.ENABLE_TRANSLATION is True: + view.printToTextbox_enableTranslation() + else: + view.printToTextbox_disableTranslation() + +def callbackToggleTranscriptionSend(is_turned_on): + view.setMainWindowAllWidgetsStatusToDisabled() + config.ENABLE_TRANSCRIPTION_SEND = is_turned_on + if config.ENABLE_TRANSCRIPTION_SEND is True: + startThreadingTranscriptionSendMessage() + else: + stopThreadingTranscriptionSendMessage() + +def callbackToggleTranscriptionReceive(is_turned_on): + view.setMainWindowAllWidgetsStatusToDisabled() + config.ENABLE_TRANSCRIPTION_RECEIVE = is_turned_on + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + startThreadingTranscriptionReceiveMessage() + else: + stopThreadingTranscriptionReceiveMessage() + +def callbackToggleForeground(is_turned_on): + config.ENABLE_FOREGROUND = is_turned_on + if config.ENABLE_FOREGROUND is True: + view.printToTextbox_enableForeground() + view.foregroundOn() + else: + view.printToTextbox_disableForeground() + view.foregroundOff() + +def callbackEnableMainWindowSidebarCompactMode(): + config.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = True + view.enableMainWindowSidebarCompactMode() + +def callbackDisableMainWindowSidebarCompactMode(): + config.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = False + view.disableMainWindowSidebarCompactMode() + +# Config Window +def callbackOpenConfigWindow(): + view.setMainWindowAllWidgetsStatusToDisabled() + if config.ENABLE_TRANSCRIPTION_SEND is True: + stopThreadingTranscriptionSendMessageOnOpenConfigWindow() + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + stopThreadingTranscriptionReceiveMessageOnOpenConfigWindow() + if config.ENABLE_FOREGROUND is True: + view.foregroundOff() + +def callbackCloseConfigWindow(): + model.stopCheckMicEnergy() + model.stopCheckSpeakerEnergy() + view.replaceMicThresholdCheckButton_Passive() + # view.initProgressBar_MicEnergy() # ProgressBarに0をセットしたい + view.replaceSpeakerThresholdCheckButton_Passive() + # view.initProgressBar_SpeakerEnergy() # ProgressBarに0をセットしたい + + if config.ENABLE_TRANSCRIPTION_SEND is True: + startThreadingTranscriptionSendMessageOnCloseConfigWindow() + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + sleep(2) + if config.ENABLE_TRANSCRIPTION_RECEIVE is True: + startThreadingTranscriptionReceiveMessageOnCloseConfigWindow() + if config.ENABLE_FOREGROUND is True: + view.foregroundOn() + view.setMainWindowAllWidgetsStatusToNormal() + +# Compact Mode Switch +def callbackEnableConfigWindowCompactMode(): + config.IS_CONFIG_WINDOW_COMPACT_MODE = True + model.stopCheckMicEnergy() + view.replaceMicThresholdCheckButton_Passive() + model.stopCheckSpeakerEnergy() + view.replaceSpeakerThresholdCheckButton_Passive() + + view.enableConfigWindowCompactMode() + +def callbackDisableConfigWindowCompactMode(): + config.IS_CONFIG_WINDOW_COMPACT_MODE = False + model.stopCheckMicEnergy() + view.replaceMicThresholdCheckButton_Passive() + model.stopCheckSpeakerEnergy() + view.replaceSpeakerThresholdCheckButton_Passive() + + view.disableConfigWindowCompactMode() + +# Appearance Tab +def callbackSetTransparency(value): + print("callbackSetTransparency", int(value)) + config.TRANSPARENCY = int(value) + view.setMainWindowTransparency(config.TRANSPARENCY/100) + +def callbackSetAppearance(value): + print("callbackSetAppearance", value) + config.APPEARANCE_THEME = value + +def callbackSetUiScaling(value): + print("callbackSetUiScaling", value) + config.UI_SCALING = value + new_scaling_float = int(value.replace("%", "")) / 100 + print("callbackSetUiScaling_new_scaling_float", new_scaling_float) + +def callbackSetFontFamily(value): + print("callbackSetFontFamily", value) + config.FONT_FAMILY = value + +def callbackSetUiLanguage(value): + print("callbackSetUiLanguage", value) + value = get_key_by_value(selectable_languages, value) + print("callbackSetUiLanguage__after_get_key_by_value", value) + config.UI_LANGUAGE = value + +# Translation Tab +def callbackSetDeeplAuthkey(value): + print("callbackSetDeeplAuthkey", str(value)) + if len(value) > 0 and model.authenticationTranslator(callbackSetAuthKeys, choice_translator="DeepL(auth)", auth_key=value) is True: + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) + view.printToTextbox_AuthenticationSuccess() + elif len(value) == 0: + auth_keys = config.AUTH_KEYS + auth_keys["DeepL(auth)"] = None + config.AUTH_KEYS = auth_keys + model.authenticationTranslator(callbackSetAuthKeys) + else: + view.printToTextbox_AuthenticationError() + +# Transcription Tab (Mic) +def callbackSetMicHost(value): + print("callbackSetMicHost", value) + config.CHOICE_MIC_HOST = value + config.CHOICE_MIC_DEVICE = model.getInputDefaultDevice() + + view.updateSelected_MicDevice(config.CHOICE_MIC_DEVICE) + view.updateList_MicDevice(model.getListInputDevice()) + + model.stopCheckMicEnergy() + view.replaceMicThresholdCheckButton_Passive() + +def callbackSetMicDevice(value): + print("callbackSetMicDevice", value) + config.CHOICE_MIC_DEVICE = value + + model.stopCheckMicEnergy() + view.replaceMicThresholdCheckButton_Passive() + +def callbackSetMicEnergyThreshold(value): + print("callbackSetMicEnergyThreshold", value) + try: + value = int(value) + if 0 <= value and value <= config.MAX_MIC_ENERGY_THRESHOLD: + view.clearErrorMessage() + config.INPUT_MIC_ENERGY_THRESHOLD = value + view.setGuiVariable_MicEnergyThreshold(config.INPUT_MIC_ENERGY_THRESHOLD) + else: + raise ValueError() + except: + view.showErrorMessage_MicEnergyThreshold() + +def callbackSetMicDynamicEnergyThreshold(value): + print("callbackSetMicDynamicEnergyThreshold", value) + config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value + +def setProgressBarMicEnergy(energy): + view.updateSetProgressBar_MicEnergy(energy) + +def callbackCheckMicThreshold(is_turned_on): + print("callbackCheckMicThreshold", is_turned_on) + if is_turned_on is True: + view.setWidgetsStatus_ThresholdCheckButton_Disabled() + model.startCheckMicEnergy(setProgressBarMicEnergy) + view.replaceMicThresholdCheckButton_Active() + view.setWidgetsStatus_ThresholdCheckButton_Normal() + else: + view.setWidgetsStatus_ThresholdCheckButton_Disabled() + model.stopCheckMicEnergy() + view.replaceMicThresholdCheckButton_Passive() + view.setWidgetsStatus_ThresholdCheckButton_Normal() + +def callbackSetMicRecordTimeout(value): + print("callbackSetMicRecordTimeout", value) + try: + value = int(value) + if 0 <= value and value <= config.INPUT_MIC_PHRASE_TIMEOUT: + view.clearErrorMessage() + config.INPUT_MIC_RECORD_TIMEOUT = value + view.setGuiVariable_MicRecordTimeout(config.INPUT_MIC_RECORD_TIMEOUT) + else: + raise ValueError() + except: + view.showErrorMessage_MicRecordTimeout() + +def callbackSetMicPhraseTimeout(value): + print("callbackSetMicPhraseTimeout", value) + try: + value = int(value) + if 0 <= value and value >= config.INPUT_MIC_RECORD_TIMEOUT: + view.clearErrorMessage() + config.INPUT_MIC_PHRASE_TIMEOUT = value + view.setGuiVariable_MicPhraseTimeout(config.INPUT_MIC_PHRASE_TIMEOUT) + else: + raise ValueError() + except: + view.showErrorMessage_MicPhraseTimeout() + +def callbackSetMicMaxPhrases(value): + print("callbackSetMicMaxPhrases", value) + try: + value = int(value) + if 0 <= value: + view.clearErrorMessage() + config.INPUT_MIC_MAX_PHRASES = value + view.setGuiVariable_MicMaxPhrases(config.INPUT_MIC_MAX_PHRASES) + else: + raise ValueError() + except: + view.showErrorMessage_MicMaxPhrases() + +def callbackSetMicWordFilter(value): + print("callbackSetMicWordFilter", value) + word_filter = str(value) + word_filter = [w.strip() for w in word_filter.split(",") if len(w.strip()) > 0] + word_filter = ",".join(word_filter) + print("callbackSetMicWordFilter_afterSplitting", word_filter) + if len(word_filter) > 0: + config.INPUT_MIC_WORD_FILTER = word_filter.split(",") + else: + config.INPUT_MIC_WORD_FILTER = [] + model.resetKeywordProcessor() + model.addKeywords() + +# Transcription Tab (Speaker) +def callbackSetSpeakerDevice(value): + print("callbackSetSpeakerDevice", value) + config.CHOICE_SPEAKER_DEVICE = value + + model.stopCheckSpeakerEnergy() + view.replaceSpeakerThresholdCheckButton_Passive() + +def callbackSetSpeakerEnergyThreshold(value): + print("callbackSetSpeakerEnergyThreshold", value) + try: + value = int(value) + if 0 <= value and value <= config.MAX_SPEAKER_ENERGY_THRESHOLD: + view.clearErrorMessage() + config.INPUT_SPEAKER_ENERGY_THRESHOLD = value + view.setGuiVariable_SpeakerEnergyThreshold(config.INPUT_SPEAKER_ENERGY_THRESHOLD) + else: + raise ValueError() + except: + view.showErrorMessage_SpeakerEnergyThreshold() + +def callbackSetSpeakerDynamicEnergyThreshold(value): + print("callbackSetSpeakerDynamicEnergyThreshold", value) + config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value + +def setProgressBarSpeakerEnergy(energy): + view.updateSetProgressBar_SpeakerEnergy(energy) + +def callbackCheckSpeakerThreshold(is_turned_on): + print("callbackCheckSpeakerThreshold", is_turned_on) + if is_turned_on is True: + view.setWidgetsStatus_ThresholdCheckButton_Disabled() + model.startCheckSpeakerEnergy(setProgressBarSpeakerEnergy) + view.replaceSpeakerThresholdCheckButton_Active() + view.setWidgetsStatus_ThresholdCheckButton_Normal() + else: + view.setWidgetsStatus_ThresholdCheckButton_Disabled() + model.stopCheckSpeakerEnergy() + view.replaceSpeakerThresholdCheckButton_Passive() + view.setWidgetsStatus_ThresholdCheckButton_Normal() + +def callbackSetSpeakerRecordTimeout(value): + print("callbackSetSpeakerRecordTimeout", value) + try: + value = int(value) + if 0 <= value and value <= config.INPUT_SPEAKER_PHRASE_TIMEOUT: + view.clearErrorMessage() + config.INPUT_SPEAKER_RECORD_TIMEOUT = value + view.setGuiVariable_SpeakerRecordTimeout(config.INPUT_SPEAKER_RECORD_TIMEOUT) + else: + raise ValueError() + except: + view.showErrorMessage_SpeakerRecordTimeout() + +def callbackSetSpeakerPhraseTimeout(value): + print("callbackSetSpeakerPhraseTimeout", value) + try: + value = int(value) + if 0 <= value and value >= config.INPUT_SPEAKER_RECORD_TIMEOUT: + view.clearErrorMessage() + config.INPUT_SPEAKER_PHRASE_TIMEOUT = value + view.setGuiVariable_SpeakerPhraseTimeout(config.INPUT_SPEAKER_PHRASE_TIMEOUT) + else: + raise ValueError() + except: + view.showErrorMessage_SpeakerPhraseTimeout() + +def callbackSetSpeakerMaxPhrases(value): + print("callbackSetSpeakerMaxPhrases", value) + try: + value = int(value) + if 0 <= value: + view.clearErrorMessage() + config.INPUT_SPEAKER_MAX_PHRASES = value + view.setGuiVariable_SpeakerMaxPhrases(config.INPUT_SPEAKER_MAX_PHRASES) + else: + raise ValueError() + except: + view.showErrorMessage_SpeakerMaxPhrases() + + +# Others Tab +def callbackSetEnableAutoClearMessageBox(value): + print("callbackSetEnableAutoClearMessageBox", value) + config.ENABLE_AUTO_CLEAR_MESSAGE_BOX = value + +def callbackSetEnableNoticeXsoverlay(value): + print("callbackSetEnableNoticeXsoverlay", value) + config.ENABLE_NOTICE_XSOVERLAY = value + +def callbackSetEnableAutoExportMessageLogs(value): + print("callbackSetEnableAutoExportMessageLogs", value) + config.ENABLE_LOGGER = value + + if config.ENABLE_LOGGER is True: + model.startLogger() + else: + model.stopLogger() + +def callbackSetMessageFormat(value): + print("callbackSetMessageFormat", value) + if len(value) > 0: + config.MESSAGE_FORMAT = value + +def callbackSetEnableSendMessageToVrc(value): + print("callbackSetEnableSendMessageToVrc", value) + config.ENABLE_SEND_MESSAGE_TO_VRC = value + +def callbackSetStartupOscEnabledCheck(value): + print("callbackSetStartupOscEnabledCheck", value) + config.STARTUP_OSC_ENABLED_CHECK = value + +# Advanced Settings Tab +def callbackSetOscIpAddress(value): + print("callbackSetOscIpAddress", str(value)) + config.OSC_IP_ADDRESS = str(value) + +def callbackSetOscPort(value): + print("callbackSetOscPort", int(value)) + config.OSC_PORT = int(value) + +def showMainWindow(): + # create GUI + view.createGUI() + + # init config + initSetLanguageAndCountry() + + if model.authenticationTranslator(callbackSetAuthKeys) is False: + # error update Auth key + view.printToTextbox_AuthenticationError() + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) + + # set word filter + model.addKeywords() + + # check OSC started + if config.STARTUP_OSC_ENABLED_CHECK is True and config.ENABLE_SEND_MESSAGE_TO_VRC is True: + model.checkOSCStarted(view.printToTextbox_OSCError) + + # check Software Updated + if model.checkSoftwareUpdated() is True: + view.showUpdateAvailableButton() + + # init logger + if config.ENABLE_LOGGER is True: + model.startLogger() + + # set UI and callback + view.register( + common_registers={ + "callback_update_software": callbackUpdateSoftware, + "callback_restart_software": callbackRestartSoftware, + }, + + window_action_registers={ + "callback_open_config_window": callbackOpenConfigWindow, + "callback_close_config_window": callbackCloseConfigWindow, + }, + + main_window_registers={ + "callback_enable_main_window_sidebar_compact_mode": callbackEnableMainWindowSidebarCompactMode, + "callback_disable_main_window_sidebar_compact_mode": callbackDisableMainWindowSidebarCompactMode, + + "callback_toggle_translation": callbackToggleTranslation, + "callback_toggle_transcription_send": callbackToggleTranscriptionSend, + "callback_toggle_transcription_receive": callbackToggleTranscriptionReceive, + "callback_toggle_foreground": callbackToggleForeground, + + "callback_your_language": setYourLanguageAndCountry, + "callback_target_language": setTargetLanguageAndCountry, + "values": model.getListLanguageAndCountry(), + + "callback_selected_language_preset_tab": callbackSelectedLanguagePresetTab, + "bind_Return": messageBoxPressKeyEnter, + "bind_Any_KeyPress": messageBoxPressKeyAny, + }, + + config_window_registers={ + # Compact Mode Switch + "callback_disable_config_window_compact_mode": callbackEnableConfigWindowCompactMode, + "callback_enable_config_window_compact_mode": callbackDisableConfigWindowCompactMode, + + # Appearance Tab + "callback_set_transparency": callbackSetTransparency, + "callback_set_appearance": callbackSetAppearance, + "callback_set_ui_scaling": callbackSetUiScaling, + "callback_set_font_family": callbackSetFontFamily, + "callback_set_ui_language": callbackSetUiLanguage, + + # Translation Tab + "callback_set_deepl_authkey": callbackSetDeeplAuthkey, + + # Transcription Tab (Mic) + "callback_set_mic_host": callbackSetMicHost, + "list_mic_host": model.getListInputHost(), + "callback_set_mic_device": callbackSetMicDevice, + "list_mic_device": model.getListInputDevice(), + "callback_set_mic_energy_threshold": callbackSetMicEnergyThreshold, + "callback_set_mic_dynamic_energy_threshold": callbackSetMicDynamicEnergyThreshold, + "callback_check_mic_threshold": callbackCheckMicThreshold, + "callback_set_mic_record_timeout": callbackSetMicRecordTimeout, + "callback_set_mic_phrase_timeout": callbackSetMicPhraseTimeout, + "callback_set_mic_max_phrases": callbackSetMicMaxPhrases, + "callback_set_mic_word_filter": callbackSetMicWordFilter, + + # Transcription Tab (Speaker) + "callback_set_speaker_device": callbackSetSpeakerDevice, + "list_speaker_device": model.getListOutputDevice(), + "callback_set_speaker_energy_threshold": callbackSetSpeakerEnergyThreshold, + "callback_set_speaker_dynamic_energy_threshold": callbackSetSpeakerDynamicEnergyThreshold, + "callback_check_speaker_threshold": callbackCheckSpeakerThreshold, + "callback_set_speaker_record_timeout": callbackSetSpeakerRecordTimeout, + "callback_set_speaker_phrase_timeout": callbackSetSpeakerPhraseTimeout, + "callback_set_speaker_max_phrases": callbackSetSpeakerMaxPhrases, + + # Others Tab + "callback_set_enable_auto_clear_chatbox": callbackSetEnableAutoClearMessageBox, + "callback_set_enable_notice_xsoverlay": callbackSetEnableNoticeXsoverlay, + "callback_set_enable_auto_export_message_logs": callbackSetEnableAutoExportMessageLogs, + "callback_set_message_format": callbackSetMessageFormat, + "callback_set_enable_send_message_to_vrc": callbackSetEnableSendMessageToVrc, + "callback_set_startup_osc_enabled_check": callbackSetStartupOscEnabledCheck, + + # Advanced Settings Tab + "callback_set_osc_ip_address": callbackSetOscIpAddress, + "callback_set_osc_port": callbackSetOscPort, + }, + ) + + view.startMainLoop() \ No newline at end of file diff --git a/main.py b/main.py index db2bdcbc..b498c824 100644 --- a/main.py +++ b/main.py @@ -1,715 +1,4 @@ -from time import sleep -from threading import Thread -from config import config -from model import model -from view import view -from utils import get_key_by_value -from languages import selectable_languages - -# Common -def callbackUpdateSoftware(): - model.updateSoftware() - -def callbackRestartSoftware(): - print("callbackRestartSoftware") - # model.updateSoftware(restart=True) - model.reStartSoftware() - -# func transcription send message -def sendMicMessage(message): - if len(message) > 0: - translation = "" - if model.checkKeywords(message): - view.printToTextbox_DetectedByWordFilter(detected_message=message) - return - elif config.ENABLE_TRANSLATION is False: - pass - elif model.getTranslatorStatus() is False: - view.printToTextbox_AuthenticationError() - else: - translation = model.getInputTranslate(message) - - if translation == None: - view.printToTextbox_AuthenticationError() - translation = "" - - if config.ENABLE_TRANSCRIPTION_SEND is True: - if config.ENABLE_SEND_MESSAGE_TO_VRC is True: - if len(translation) > 0: - osc_message = config.MESSAGE_FORMAT.replace("[message]", message) - osc_message = osc_message.replace("[translation]", translation) - else: - osc_message = message - model.oscSendMessage(osc_message) - - view.printToTextbox_SentMessage(message, translation) - if config.ENABLE_LOGGER is True: - if len(translation) > 0: - translation = f" ({translation})" - model.logger.info(f"[SENT] {message}{translation}") - -def startTranscriptionSendMessage(): - model.startMicTranscript(sendMicMessage) - view.setMainWindowAllWidgetsStatusToNormal() - -def stopTranscriptionSendMessage(): - model.stopMicTranscript() - view.setMainWindowAllWidgetsStatusToNormal() - -def startThreadingTranscriptionSendMessage(): - view.printToTextbox_enableTranscriptionSend() - th_startTranscriptionSendMessage = Thread(target=startTranscriptionSendMessage) - th_startTranscriptionSendMessage.daemon = True - th_startTranscriptionSendMessage.start() - -def stopThreadingTranscriptionSendMessage(): - view.printToTextbox_disableTranscriptionSend() - th_stopTranscriptionSendMessage = Thread(target=stopTranscriptionSendMessage) - th_stopTranscriptionSendMessage.daemon = True - th_stopTranscriptionSendMessage.start() - -def startTranscriptionSendMessageOnCloseConfigWindow(): - model.startMicTranscript(sendMicMessage) - -def stopTranscriptionSendMessageOnOpenConfigWindow(): - model.stopMicTranscript() - -def startThreadingTranscriptionSendMessageOnCloseConfigWindow(): - th_startTranscriptionSendMessage = Thread(target=startTranscriptionSendMessageOnCloseConfigWindow) - th_startTranscriptionSendMessage.daemon = True - th_startTranscriptionSendMessage.start() - -def stopThreadingTranscriptionSendMessageOnOpenConfigWindow(): - th_stopTranscriptionSendMessage = Thread(target=stopTranscriptionSendMessageOnOpenConfigWindow) - th_stopTranscriptionSendMessage.daemon = True - th_stopTranscriptionSendMessage.start() - -# func transcription receive message -def receiveSpeakerMessage(message): - if len(message) > 0: - translation = "" - if config.ENABLE_TRANSLATION is False: - pass - elif model.getTranslatorStatus() is False: - view.printToTextbox_AuthenticationError() - else: - translation = model.getOutputTranslate(message) - - if translation == None: - view.printToTextbox_AuthenticationError() - translation = "" - - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - if config.ENABLE_NOTICE_XSOVERLAY is True: - xsoverlay_message = config.MESSAGE_FORMAT.replace("[message]", message) - xsoverlay_message = xsoverlay_message.replace("[translation]", translation) - model.notificationXSOverlay(xsoverlay_message) - view.printToTextbox_ReceivedMessage(message, translation) - if config.ENABLE_LOGGER is True: - if len(translation) > 0: - translation = f" ({translation})" - model.logger.info(f"[RECEIVED] {message}{translation}") - -def startTranscriptionReceiveMessage(): - model.startSpeakerTranscript(receiveSpeakerMessage) - view.setMainWindowAllWidgetsStatusToNormal() - -def stopTranscriptionReceiveMessage(): - model.stopSpeakerTranscript() - view.setMainWindowAllWidgetsStatusToNormal() - -def startThreadingTranscriptionReceiveMessage(): - view.printToTextbox_enableTranscriptionReceive() - th_startTranscriptionReceiveMessage = Thread(target=startTranscriptionReceiveMessage) - th_startTranscriptionReceiveMessage.daemon = True - th_startTranscriptionReceiveMessage.start() - -def stopThreadingTranscriptionReceiveMessage(): - view.printToTextbox_disableTranscriptionReceive() - th_stopTranscriptionReceiveMessage = Thread(target=stopTranscriptionReceiveMessage) - th_stopTranscriptionReceiveMessage.daemon = True - th_stopTranscriptionReceiveMessage.start() - -def startTranscriptionReceiveMessageOnCloseConfigWindow(): - model.startSpeakerTranscript(receiveSpeakerMessage) - -def stopTranscriptionReceiveMessageOnOpenConfigWindow(): - model.stopSpeakerTranscript() - -def startThreadingTranscriptionReceiveMessageOnCloseConfigWindow(): - th_startTranscriptionReceiveMessage = Thread(target=startTranscriptionReceiveMessageOnCloseConfigWindow) - th_startTranscriptionReceiveMessage.daemon = True - th_startTranscriptionReceiveMessage.start() - -def stopThreadingTranscriptionReceiveMessageOnOpenConfigWindow(): - th_stopTranscriptionReceiveMessage = Thread(target=stopTranscriptionReceiveMessageOnOpenConfigWindow) - th_stopTranscriptionReceiveMessage.daemon = True - th_stopTranscriptionReceiveMessage.start() - -# func message box -def sendChatMessage(message): - if len(message) > 0: - translation = "" - if config.ENABLE_TRANSLATION is False: - pass - elif model.getTranslatorStatus() is False: - view.printToTextbox_AuthenticationError() - else: - translation = model.getInputTranslate(message) - - if translation == None: - view.printToTextbox_AuthenticationError() - translation = "" - - # send OSC message - if config.ENABLE_SEND_MESSAGE_TO_VRC is True: - if len(translation) > 0: - osc_message = config.MESSAGE_FORMAT.replace("[message]", message) - osc_message = osc_message.replace("[translation]", translation) - else: - osc_message = message - model.oscSendMessage(osc_message) - - # update textbox message log - view.printToTextbox_SentMessage(message, translation) - if config.ENABLE_LOGGER is True: - if len(translation) > 0: - translation = f" ({translation})" - model.logger.info(f"[SENT] {message}{translation}") - - # delete message in entry message box - if config.ENABLE_AUTO_CLEAR_MESSAGE_BOX is True: - view.clearMessageBox() - -def messageBoxPressKeyEnter(e): - model.oscStopSendTyping() - message = view.getTextFromMessageBox() - sendChatMessage(message) - -def messageBoxPressKeyAny(e): - model.oscStartSendTyping() - -# func select languages -def initSetLanguageAndCountry(): - select = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] - language, country = model.getLanguageAndCountry(select) - config.SOURCE_LANGUAGE = language - config.SOURCE_COUNTRY = country - - select = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] - language, country = model.getLanguageAndCountry(select) - config.TARGET_LANGUAGE = language - config.TARGET_COUNTRY = country - - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) - -def setYourLanguageAndCountry(select): - languages = config.SELECTED_TAB_YOUR_LANGUAGES - languages[config.SELECTED_TAB_NO] = select - config.SELECTED_TAB_YOUR_LANGUAGES = languages - language, country = model.getLanguageAndCountry(select) - config.SOURCE_LANGUAGE = language - config.SOURCE_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) - view.printToTextbox_selectedYourLanguages(select) - -def setTargetLanguageAndCountry(select): - languages = config.SELECTED_TAB_TARGET_LANGUAGES - languages[config.SELECTED_TAB_NO] = select - config.SELECTED_TAB_TARGET_LANGUAGES = languages - language, country = model.getLanguageAndCountry(select) - config.TARGET_LANGUAGE = language - config.TARGET_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) - view.printToTextbox_selectedTargetLanguages(select) - -def callbackSelectedLanguagePresetTab(selected_tab_no): - config.SELECTED_TAB_NO = selected_tab_no - view.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) - languages = config.SELECTED_TAB_YOUR_LANGUAGES - select = languages[config.SELECTED_TAB_NO] - language, country = model.getLanguageAndCountry(select) - config.SOURCE_LANGUAGE = language - config.SOURCE_COUNTRY = country - languages = config.SELECTED_TAB_TARGET_LANGUAGES - select = languages[config.SELECTED_TAB_NO] - language, country = model.getLanguageAndCountry(select) - config.TARGET_LANGUAGE = language - config.TARGET_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) - view.printToTextbox_changedLanguagePresetTab(config.SELECTED_TAB_NO) - -def callbackSetAuthKeys(keys): - config.AUTH_KEYS = keys - -# command func -def callbackToggleTranslation(is_turned_on): - config.ENABLE_TRANSLATION = is_turned_on - if config.ENABLE_TRANSLATION is True: - view.printToTextbox_enableTranslation() - else: - view.printToTextbox_disableTranslation() - -def callbackToggleTranscriptionSend(is_turned_on): - view.setMainWindowAllWidgetsStatusToDisabled() - config.ENABLE_TRANSCRIPTION_SEND = is_turned_on - if config.ENABLE_TRANSCRIPTION_SEND is True: - startThreadingTranscriptionSendMessage() - else: - stopThreadingTranscriptionSendMessage() - -def callbackToggleTranscriptionReceive(is_turned_on): - view.setMainWindowAllWidgetsStatusToDisabled() - config.ENABLE_TRANSCRIPTION_RECEIVE = is_turned_on - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - startThreadingTranscriptionReceiveMessage() - else: - stopThreadingTranscriptionReceiveMessage() - -def callbackToggleForeground(is_turned_on): - config.ENABLE_FOREGROUND = is_turned_on - if config.ENABLE_FOREGROUND is True: - view.printToTextbox_enableForeground() - view.foregroundOn() - else: - view.printToTextbox_disableForeground() - view.foregroundOff() - -def callbackEnableMainWindowSidebarCompactMode(): - config.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = True - view.enableMainWindowSidebarCompactMode() - -def callbackDisableMainWindowSidebarCompactMode(): - config.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = False - view.disableMainWindowSidebarCompactMode() - -# Config Window -def callbackOpenConfigWindow(): - view.setMainWindowAllWidgetsStatusToDisabled() - if config.ENABLE_TRANSCRIPTION_SEND is True: - stopThreadingTranscriptionSendMessageOnOpenConfigWindow() - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - stopThreadingTranscriptionReceiveMessageOnOpenConfigWindow() - if config.ENABLE_FOREGROUND is True: - view.foregroundOff() - -def callbackCloseConfigWindow(): - model.stopCheckMicEnergy() - model.stopCheckSpeakerEnergy() - view.replaceMicThresholdCheckButton_Passive() - # view.initProgressBar_MicEnergy() # ProgressBarに0をセットしたい - view.replaceSpeakerThresholdCheckButton_Passive() - # view.initProgressBar_SpeakerEnergy() # ProgressBarに0をセットしたい - - if config.ENABLE_TRANSCRIPTION_SEND is True: - startThreadingTranscriptionSendMessageOnCloseConfigWindow() - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - sleep(2) - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - startThreadingTranscriptionReceiveMessageOnCloseConfigWindow() - if config.ENABLE_FOREGROUND is True: - view.foregroundOn() - view.setMainWindowAllWidgetsStatusToNormal() - -# Compact Mode Switch -def callbackEnableConfigWindowCompactMode(): - config.IS_CONFIG_WINDOW_COMPACT_MODE = True - model.stopCheckMicEnergy() - view.replaceMicThresholdCheckButton_Passive() - model.stopCheckSpeakerEnergy() - view.replaceSpeakerThresholdCheckButton_Passive() - - view.enableConfigWindowCompactMode() - -def callbackDisableConfigWindowCompactMode(): - config.IS_CONFIG_WINDOW_COMPACT_MODE = False - model.stopCheckMicEnergy() - view.replaceMicThresholdCheckButton_Passive() - model.stopCheckSpeakerEnergy() - view.replaceSpeakerThresholdCheckButton_Passive() - - view.disableConfigWindowCompactMode() - -# Appearance Tab -def callbackSetTransparency(value): - print("callbackSetTransparency", int(value)) - config.TRANSPARENCY = int(value) - view.setMainWindowTransparency(config.TRANSPARENCY/100) - -def callbackSetAppearance(value): - print("callbackSetAppearance", value) - config.APPEARANCE_THEME = value - -def callbackSetUiScaling(value): - print("callbackSetUiScaling", value) - config.UI_SCALING = value - new_scaling_float = int(value.replace("%", "")) / 100 - print("callbackSetUiScaling_new_scaling_float", new_scaling_float) - -def callbackSetFontFamily(value): - print("callbackSetFontFamily", value) - config.FONT_FAMILY = value - -def callbackSetUiLanguage(value): - print("callbackSetUiLanguage", value) - value = get_key_by_value(selectable_languages, value) - print("callbackSetUiLanguage__after_get_key_by_value", value) - config.UI_LANGUAGE = value - -# Translation Tab -def callbackSetDeeplAuthkey(value): - print("callbackSetDeeplAuthkey", str(value)) - if len(value) > 0 and model.authenticationTranslator(callbackSetAuthKeys, choice_translator="DeepL(auth)", auth_key=value) is True: - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) - view.printToTextbox_AuthenticationSuccess() - elif len(value) == 0: - auth_keys = config.AUTH_KEYS - auth_keys["DeepL(auth)"] = None - config.AUTH_KEYS = auth_keys - model.authenticationTranslator(callbackSetAuthKeys) - else: - view.printToTextbox_AuthenticationError() - -# Transcription Tab (Mic) -def callbackSetMicHost(value): - print("callbackSetMicHost", value) - config.CHOICE_MIC_HOST = value - config.CHOICE_MIC_DEVICE = model.getInputDefaultDevice() - - view.updateSelected_MicDevice(config.CHOICE_MIC_DEVICE) - view.updateList_MicDevice(model.getListInputDevice()) - - model.stopCheckMicEnergy() - view.replaceMicThresholdCheckButton_Passive() - -def callbackSetMicDevice(value): - print("callbackSetMicDevice", value) - config.CHOICE_MIC_DEVICE = value - - model.stopCheckMicEnergy() - view.replaceMicThresholdCheckButton_Passive() - -def callbackSetMicEnergyThreshold(value): - print("callbackSetMicEnergyThreshold", value) - try: - value = int(value) - if 0 <= value and value <= config.MAX_MIC_ENERGY_THRESHOLD: - view.clearErrorMessage() - config.INPUT_MIC_ENERGY_THRESHOLD = value - view.setGuiVariable_MicEnergyThreshold(config.INPUT_MIC_ENERGY_THRESHOLD) - else: - raise ValueError() - except: - view.showErrorMessage_MicEnergyThreshold() - -def callbackSetMicDynamicEnergyThreshold(value): - print("callbackSetMicDynamicEnergyThreshold", value) - config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value - -def setProgressBarMicEnergy(energy): - view.updateSetProgressBar_MicEnergy(energy) - -def callbackCheckMicThreshold(is_turned_on): - print("callbackCheckMicThreshold", is_turned_on) - if is_turned_on is True: - view.setWidgetsStatus_ThresholdCheckButton_Disabled() - model.startCheckMicEnergy(setProgressBarMicEnergy) - view.replaceMicThresholdCheckButton_Active() - view.setWidgetsStatus_ThresholdCheckButton_Normal() - else: - view.setWidgetsStatus_ThresholdCheckButton_Disabled() - model.stopCheckMicEnergy() - view.replaceMicThresholdCheckButton_Passive() - view.setWidgetsStatus_ThresholdCheckButton_Normal() - -def callbackSetMicRecordTimeout(value): - print("callbackSetMicRecordTimeout", value) - try: - value = int(value) - if 0 <= value and value <= config.INPUT_MIC_PHRASE_TIMEOUT: - view.clearErrorMessage() - config.INPUT_MIC_RECORD_TIMEOUT = value - view.setGuiVariable_MicRecordTimeout(config.INPUT_MIC_RECORD_TIMEOUT) - else: - raise ValueError() - except: - view.showErrorMessage_MicRecordTimeout() - -def callbackSetMicPhraseTimeout(value): - print("callbackSetMicPhraseTimeout", value) - try: - value = int(value) - if 0 <= value and value >= config.INPUT_MIC_RECORD_TIMEOUT: - view.clearErrorMessage() - config.INPUT_MIC_PHRASE_TIMEOUT = value - view.setGuiVariable_MicPhraseTimeout(config.INPUT_MIC_PHRASE_TIMEOUT) - else: - raise ValueError() - except: - view.showErrorMessage_MicPhraseTimeout() - -def callbackSetMicMaxPhrases(value): - print("callbackSetMicMaxPhrases", value) - try: - value = int(value) - if 0 <= value: - view.clearErrorMessage() - config.INPUT_MIC_MAX_PHRASES = value - view.setGuiVariable_MicMaxPhrases(config.INPUT_MIC_MAX_PHRASES) - else: - raise ValueError() - except: - view.showErrorMessage_MicMaxPhrases() - -def callbackSetMicWordFilter(value): - print("callbackSetMicWordFilter", value) - word_filter = str(value) - word_filter = [w.strip() for w in word_filter.split(",") if len(w.strip()) > 0] - word_filter = ",".join(word_filter) - print("callbackSetMicWordFilter_afterSplitting", word_filter) - if len(word_filter) > 0: - config.INPUT_MIC_WORD_FILTER = word_filter.split(",") - else: - config.INPUT_MIC_WORD_FILTER = [] - model.resetKeywordProcessor() - model.addKeywords() - -# Transcription Tab (Speaker) -def callbackSetSpeakerDevice(value): - print("callbackSetSpeakerDevice", value) - config.CHOICE_SPEAKER_DEVICE = value - - model.stopCheckSpeakerEnergy() - view.replaceSpeakerThresholdCheckButton_Passive() - -def callbackSetSpeakerEnergyThreshold(value): - print("callbackSetSpeakerEnergyThreshold", value) - try: - value = int(value) - if 0 <= value and value <= config.MAX_SPEAKER_ENERGY_THRESHOLD: - view.clearErrorMessage() - config.INPUT_SPEAKER_ENERGY_THRESHOLD = value - view.setGuiVariable_SpeakerEnergyThreshold(config.INPUT_SPEAKER_ENERGY_THRESHOLD) - else: - raise ValueError() - except: - view.showErrorMessage_SpeakerEnergyThreshold() - -def callbackSetSpeakerDynamicEnergyThreshold(value): - print("callbackSetSpeakerDynamicEnergyThreshold", value) - config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value - -def setProgressBarSpeakerEnergy(energy): - view.updateSetProgressBar_SpeakerEnergy(energy) - -def callbackCheckSpeakerThreshold(is_turned_on): - print("callbackCheckSpeakerThreshold", is_turned_on) - if is_turned_on is True: - view.setWidgetsStatus_ThresholdCheckButton_Disabled() - model.startCheckSpeakerEnergy(setProgressBarSpeakerEnergy) - view.replaceSpeakerThresholdCheckButton_Active() - view.setWidgetsStatus_ThresholdCheckButton_Normal() - else: - view.setWidgetsStatus_ThresholdCheckButton_Disabled() - model.stopCheckSpeakerEnergy() - view.replaceSpeakerThresholdCheckButton_Passive() - view.setWidgetsStatus_ThresholdCheckButton_Normal() - -def callbackSetSpeakerRecordTimeout(value): - print("callbackSetSpeakerRecordTimeout", value) - try: - value = int(value) - if 0 <= value and value <= config.INPUT_SPEAKER_PHRASE_TIMEOUT: - view.clearErrorMessage() - config.INPUT_SPEAKER_RECORD_TIMEOUT = value - view.setGuiVariable_SpeakerRecordTimeout(config.INPUT_SPEAKER_RECORD_TIMEOUT) - else: - raise ValueError() - except: - view.showErrorMessage_SpeakerRecordTimeout() - -def callbackSetSpeakerPhraseTimeout(value): - print("callbackSetSpeakerPhraseTimeout", value) - try: - value = int(value) - if 0 <= value and value >= config.INPUT_SPEAKER_RECORD_TIMEOUT: - view.clearErrorMessage() - config.INPUT_SPEAKER_PHRASE_TIMEOUT = value - view.setGuiVariable_SpeakerPhraseTimeout(config.INPUT_SPEAKER_PHRASE_TIMEOUT) - else: - raise ValueError() - except: - view.showErrorMessage_SpeakerPhraseTimeout() - -def callbackSetSpeakerMaxPhrases(value): - print("callbackSetSpeakerMaxPhrases", value) - try: - value = int(value) - if 0 <= value: - view.clearErrorMessage() - config.INPUT_SPEAKER_MAX_PHRASES = value - view.setGuiVariable_SpeakerMaxPhrases(config.INPUT_SPEAKER_MAX_PHRASES) - else: - raise ValueError() - except: - view.showErrorMessage_SpeakerMaxPhrases() - - -# Others Tab -def callbackSetEnableAutoClearMessageBox(value): - print("callbackSetEnableAutoClearMessageBox", value) - config.ENABLE_AUTO_CLEAR_MESSAGE_BOX = value - -def callbackSetEnableNoticeXsoverlay(value): - print("callbackSetEnableNoticeXsoverlay", value) - config.ENABLE_NOTICE_XSOVERLAY = value - -def callbackSetEnableAutoExportMessageLogs(value): - print("callbackSetEnableAutoExportMessageLogs", value) - config.ENABLE_LOGGER = value - - if config.ENABLE_LOGGER is True: - model.startLogger() - else: - model.stopLogger() - -def callbackSetMessageFormat(value): - print("callbackSetMessageFormat", value) - if len(value) > 0: - config.MESSAGE_FORMAT = value - -def callbackSetEnableSendMessageToVrc(value): - print("callbackSetEnableSendMessageToVrc", value) - config.ENABLE_SEND_MESSAGE_TO_VRC = value - -def callbackSetStartupOscEnabledCheck(value): - print("callbackSetStartupOscEnabledCheck", value) - config.STARTUP_OSC_ENABLED_CHECK = value - -# Advanced Settings Tab -def callbackSetOscIpAddress(value): - print("callbackSetOscIpAddress", str(value)) - config.OSC_IP_ADDRESS = str(value) - -def callbackSetOscPort(value): - print("callbackSetOscPort", int(value)) - config.OSC_PORT = int(value) - - - -# create GUI -view.createGUI() - -# init config -initSetLanguageAndCountry() - -if model.authenticationTranslator(callbackSetAuthKeys) is False: - # error update Auth key - view.printToTextbox_AuthenticationError() - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) - -# set word filter -model.addKeywords() - -# check OSC started -if config.STARTUP_OSC_ENABLED_CHECK is True and config.ENABLE_SEND_MESSAGE_TO_VRC is True: - model.checkOSCStarted(view.printToTextbox_OSCError) - -# check Software Updated -if model.checkSoftwareUpdated() is True: - view.showUpdateAvailableButton() - -# init logger -if config.ENABLE_LOGGER is True: - model.startLogger() - -# set UI and callback -view.register( - common_registers={ - "callback_update_software": callbackUpdateSoftware, - "callback_restart_software": callbackRestartSoftware, - }, - - window_action_registers={ - "callback_open_config_window": callbackOpenConfigWindow, - "callback_close_config_window": callbackCloseConfigWindow, - }, - - main_window_registers={ - "callback_enable_main_window_sidebar_compact_mode": callbackEnableMainWindowSidebarCompactMode, - "callback_disable_main_window_sidebar_compact_mode": callbackDisableMainWindowSidebarCompactMode, - - "callback_toggle_translation": callbackToggleTranslation, - "callback_toggle_transcription_send": callbackToggleTranscriptionSend, - "callback_toggle_transcription_receive": callbackToggleTranscriptionReceive, - "callback_toggle_foreground": callbackToggleForeground, - - "callback_your_language": setYourLanguageAndCountry, - "callback_target_language": setTargetLanguageAndCountry, - "values": model.getListLanguageAndCountry(), - - "callback_selected_language_preset_tab": callbackSelectedLanguagePresetTab, - "bind_Return": messageBoxPressKeyEnter, - "bind_Any_KeyPress": messageBoxPressKeyAny, - }, - - config_window_registers={ - # Compact Mode Switch - "callback_disable_config_window_compact_mode": callbackEnableConfigWindowCompactMode, - "callback_enable_config_window_compact_mode": callbackDisableConfigWindowCompactMode, - - # Appearance Tab - "callback_set_transparency": callbackSetTransparency, - "callback_set_appearance": callbackSetAppearance, - "callback_set_ui_scaling": callbackSetUiScaling, - "callback_set_font_family": callbackSetFontFamily, - "callback_set_ui_language": callbackSetUiLanguage, - - # Translation Tab - "callback_set_deepl_authkey": callbackSetDeeplAuthkey, - - # Transcription Tab (Mic) - "callback_set_mic_host": callbackSetMicHost, - "list_mic_host": model.getListInputHost(), - "callback_set_mic_device": callbackSetMicDevice, - "list_mic_device": model.getListInputDevice(), - "callback_set_mic_energy_threshold": callbackSetMicEnergyThreshold, - "callback_set_mic_dynamic_energy_threshold": callbackSetMicDynamicEnergyThreshold, - "callback_check_mic_threshold": callbackCheckMicThreshold, - "callback_set_mic_record_timeout": callbackSetMicRecordTimeout, - "callback_set_mic_phrase_timeout": callbackSetMicPhraseTimeout, - "callback_set_mic_max_phrases": callbackSetMicMaxPhrases, - "callback_set_mic_word_filter": callbackSetMicWordFilter, - - # Transcription Tab (Speaker) - "callback_set_speaker_device": callbackSetSpeakerDevice, - "list_speaker_device": model.getListOutputDevice(), - "callback_set_speaker_energy_threshold": callbackSetSpeakerEnergyThreshold, - "callback_set_speaker_dynamic_energy_threshold": callbackSetSpeakerDynamicEnergyThreshold, - "callback_check_speaker_threshold": callbackCheckSpeakerThreshold, - "callback_set_speaker_record_timeout": callbackSetSpeakerRecordTimeout, - "callback_set_speaker_phrase_timeout": callbackSetSpeakerPhraseTimeout, - "callback_set_speaker_max_phrases": callbackSetSpeakerMaxPhrases, - - # Others Tab - "callback_set_enable_auto_clear_chatbox": callbackSetEnableAutoClearMessageBox, - "callback_set_enable_notice_xsoverlay": callbackSetEnableNoticeXsoverlay, - "callback_set_enable_auto_export_message_logs": callbackSetEnableAutoExportMessageLogs, - "callback_set_message_format": callbackSetMessageFormat, - "callback_set_enable_send_message_to_vrc": callbackSetEnableSendMessageToVrc, - "callback_set_startup_osc_enabled_check": callbackSetStartupOscEnabledCheck, - - # Advanced Settings Tab - "callback_set_osc_ip_address": callbackSetOscIpAddress, - "callback_set_osc_port": callbackSetOscPort, - }, -) +import controller if __name__ == "__main__": - view.startMainLoop() \ No newline at end of file + controller.showMainWindow() \ No newline at end of file From 61ecbe1e845b4a56db6f594f87ed07f6018c02ec Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 11 Oct 2023 13:08:59 +0900 Subject: [PATCH 257/355] =?UTF-8?q?[Refactor]=20createGUI=E3=82=92?= =?UTF-8?q?=E3=80=81createGUI=E3=81=A8showGUI=E3=81=AB=E5=88=86=E5=89=B2?= =?UTF-8?q?=E3=80=82createGUI=E3=81=A7=E3=81=AF=E7=94=BB=E9=9D=A2=E3=82=92?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=81=9B=E3=81=9A=E4=B8=AD=E8=BA=AB=E3=81=AE?= =?UTF-8?q?=E7=B5=84=E3=81=BF=E7=AB=8B=E3=81=A6=E3=82=84=E9=96=A2=E6=95=B0?= =?UTF-8?q?=E7=99=BB=E9=8C=B2=E3=80=82showGUI=E3=81=A7=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=80=82=20?= =?UTF-8?q?=E3=81=9D=E3=81=AE=E4=BB=96=E9=96=A2=E6=95=B0=E5=90=8D=E5=A4=89?= =?UTF-8?q?=E6=9B=B4(vrct=5Fgui.=20=E3=81=8B=E3=82=89=E5=91=BC=E3=81=B6?= =?UTF-8?q?=E9=96=A2=E6=95=B0=E3=81=AF=E3=81=99=E3=81=B9=E3=81=A6=E3=82=A2?= =?UTF-8?q?=E3=83=B3=E3=83=80=E3=83=BC=E3=83=90=E3=83=BC=E3=82=92=E3=81=A4?= =?UTF-8?q?=E3=81=91=E3=82=8B=E3=80=82view.py=E3=81=A7=E5=91=BC=E3=81=B0?= =?UTF-8?q?=E3=82=8C=E3=82=8B=E9=96=A2=E6=95=B0=E3=81=A8=E5=8C=BA=E5=88=A5?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=81=9F=E3=82=81=E3=80=82)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 3 +- main.py | 1 + view.py | 35 ++++++++------- vrct_gui/_CreateSelectableLanguagesWindow.py | 8 ++-- vrct_gui/config_window/ConfigWindow.py | 2 +- .../main_window/createMainWindowWidgets.py | 2 +- .../main_window/widgets/create_sidebar.py | 2 +- vrct_gui/vrct_gui.py | 44 +++++++++++-------- 8 files changed, 55 insertions(+), 42 deletions(-) diff --git a/controller.py b/controller.py index ab976748..ecbe1376 100644 --- a/controller.py +++ b/controller.py @@ -600,7 +600,7 @@ def callbackSetOscPort(value): print("callbackSetOscPort", int(value)) config.OSC_PORT = int(value) -def showMainWindow(): +def createMainWindow(): # create GUI view.createGUI() @@ -710,4 +710,5 @@ def showMainWindow(): }, ) +def showMainWindow(): view.startMainLoop() \ No newline at end of file diff --git a/main.py b/main.py index b498c824..103e09c3 100644 --- a/main.py +++ b/main.py @@ -1,4 +1,5 @@ import controller if __name__ == "__main__": + controller.createMainWindow() controller.showMainWindow() \ No newline at end of file diff --git a/view.py b/view.py index 903285e3..cc77c545 100644 --- a/view.py +++ b/view.py @@ -374,7 +374,7 @@ class View(): self.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) - vrct_gui.setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) + vrct_gui._setDefaultActiveLanguagePresetTab(tab_no=config.SELECTED_TAB_NO) self.view_variable.CALLBACK_OPEN_SELECTABLE_YOUR_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_YourLanguage self.view_variable.CALLBACK_OPEN_SELECTABLE_TARGET_LANGUAGE_WINDOW = self.openSelectableLanguagesWindow_TargetLanguage @@ -475,11 +475,11 @@ class View(): @staticmethod def setMainWindowAllWidgetsStatusToNormal(): - vrct_gui.changeMainWindowWidgetsStatus("normal", "All") + vrct_gui._changeMainWindowWidgetsStatus("normal", "All") @staticmethod def setMainWindowAllWidgetsStatusToDisabled(): - vrct_gui.changeMainWindowWidgetsStatus("disabled", "All") + vrct_gui._changeMainWindowWidgetsStatus("disabled", "All") @@ -503,19 +503,19 @@ class View(): def enableMainWindowSidebarCompactMode(self): self.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = True - vrct_gui.enableMainWindowSidebarCompactMode() + vrct_gui._enableMainWindowSidebarCompactMode() def disableMainWindowSidebarCompactMode(self): self.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = False - vrct_gui.disableMainWindowSidebarCompactMode() + vrct_gui._disableMainWindowSidebarCompactMode() def openSelectableLanguagesWindow_YourLanguage(self, _e): self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set(i18n.t("selectable_language_window.title_your_language")) - vrct_gui.openSelectableLanguagesWindow("your_language") + vrct_gui._openSelectableLanguagesWindow("your_language") def openSelectableLanguagesWindow_TargetLanguage(self, _e): self.view_variable.VAR_TITLE_LABEL_SELECTABLE_LANGUAGE.set(i18n.t("selectable_language_window.title_target_language")) - vrct_gui.openSelectableLanguagesWindow("target_language") + vrct_gui._openSelectableLanguagesWindow("target_language") def updateGuiVariableByPresetTabNo(self, tab_no:str): @@ -582,7 +582,7 @@ class View(): @staticmethod def _printToTextbox_Info(info_message): - vrct_gui.printToTextbox( + vrct_gui._printToTextbox( target_type="SYSTEM", original_message=info_message, # translated_message="", @@ -595,7 +595,7 @@ class View(): @staticmethod def _printToTextbox_Sent(original_message, translated_message): - vrct_gui.printToTextbox( + vrct_gui._printToTextbox( target_type="SENT", original_message=original_message, translated_message=translated_message, @@ -607,7 +607,7 @@ class View(): @staticmethod def _printToTextbox_Received(original_message, translated_message): - vrct_gui.printToTextbox( + vrct_gui._printToTextbox( target_type="RECEIVED", original_message=original_message, translated_message=translated_message, @@ -638,11 +638,16 @@ class View(): def createGUI(self): - vrct_gui.createGUI(settings=self.settings, view_variable=self.view_variable) + vrct_gui._createGUI(settings=self.settings, view_variable=self.view_variable) + + @staticmethod + def showGUI(): + vrct_gui._showGUI() @staticmethod def startMainLoop(): - vrct_gui.startMainLoop() + vrct_gui._showGUI() + vrct_gui._startMainLoop() # Config Window @@ -659,7 +664,7 @@ class View(): @staticmethod def setWidgetsStatus_ThresholdCheckButton_Disabled(): - vrct_gui.changeConfigWindowWidgetsStatus( + vrct_gui._changeConfigWindowWidgetsStatus( status="disabled", target_names=[ "mic_energy_threshold_check_button", @@ -669,7 +674,7 @@ class View(): @staticmethod def setWidgetsStatus_ThresholdCheckButton_Normal(): - vrct_gui.changeConfigWindowWidgetsStatus( + vrct_gui._changeConfigWindowWidgetsStatus( status="normal", target_names=[ "mic_energy_threshold_check_button", @@ -854,7 +859,7 @@ class View(): def _showErrorMessage(self, target_widget, message): self.view_variable.VAR_ERROR_MESSAGE.set(message) - vrct_gui.showErrorMessage(target_widget=target_widget) + vrct_gui._showErrorMessage(target_widget=target_widget) def clearErrorMessage(self): vrct_gui._clearErrorMessage() diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index 8ca5810c..f684ac3f 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -19,12 +19,12 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.vrct_gui = vrct_gui self.configure(fg_color="#ff7f50") - self.protocol("WM_DELETE_WINDOW", vrct_gui.closeSelectableLanguagesWindow) + self.protocol("WM_DELETE_WINDOW", vrct_gui._closeSelectableLanguagesWindow) self.settings = settings self._view_variable = view_variable - self.bind("", lambda e: vrct_gui.closeSelectableLanguagesWindow()) + self.bind("", lambda e: vrct_gui._closeSelectableLanguagesWindow()) @@ -63,7 +63,7 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): target_variable.set(value) callFunctionIfCallable(callback, value) - self.vrct_gui.closeSelectableLanguagesWindow() + self.vrct_gui._closeSelectableLanguagesWindow() @@ -98,7 +98,7 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): bindButtonPressColor([self.go_back_button_container, self.go_back_button_label], self.settings.ctm.GO_BACK_BUTTON_BG_CLICKED_COLOR, self.settings.ctm.GO_BACK_BUTTON_BG_COLOR) - bindButtonReleaseFunction([self.go_back_button_container, self.go_back_button_label], lambda _e: self.vrct_gui.closeSelectableLanguagesWindow()) + bindButtonReleaseFunction([self.go_back_button_container, self.go_back_button_label], lambda _e: self.vrct_gui._closeSelectableLanguagesWindow()) diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index c8a59555..918b9250 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -17,7 +17,7 @@ class ConfigWindow(CTkToplevel): self.configure(fg_color="#ff7f50") - self.protocol("WM_DELETE_WINDOW", vrct_gui.closeConfigWindow) + self.protocol("WM_DELETE_WINDOW", vrct_gui._closeConfigWindow) self.settings = settings self._view_variable = view_variable diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index c3707f93..25021800 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -6,7 +6,7 @@ from utils import callFunctionIfCallable from ..ui_utils import createButtonWithImage, getImagePath, bindButtonFunctionAndColor def createMainWindowWidgets(vrct_gui, settings, view_variable): - vrct_gui.protocol("WM_DELETE_WINDOW", vrct_gui.quitVRCT) + vrct_gui.protocol("WM_DELETE_WINDOW", vrct_gui._quitVRCT) vrct_gui.iconbitmap(getImagePath("vrct_logo_mark_black.ico")) diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 32aad519..09c1a58a 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -75,5 +75,5 @@ def createSidebar(settings, main_window, view_variable): enter_color=settings.ctm.CONFIG_BUTTON_HOVERED_BG_COLOR, leave_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, clicked_color=settings.ctm.CONFIG_BUTTON_CLICKED_BG_COLOR, - buttonReleasedFunction=main_window.openConfigWindow, + buttonReleasedFunction=main_window._openConfigWindow, ) \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index e4f4464f..ce96b23e 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -18,12 +18,18 @@ from utils import callFunctionIfCallable class VRCT_GUI(CTk): def __init__(self): super().__init__() + self.withdraw() self.adjusted_event=None self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID=None self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID=None - def createGUI(self, settings, view_variable): + def _showGUI(self): + self.deiconify() + self.update() + self.geometry("{}x{}".format(self.winfo_width(), self.winfo_height())) + + def _createGUI(self, settings, view_variable): self.settings = settings self._view_variable = view_variable @@ -86,31 +92,31 @@ class VRCT_GUI(CTk): ) - self.update() - self.geometry("{}x{}".format(self.winfo_width(), self.winfo_height())) + # self.update() + # self.geometry("{}x{}".format(self.winfo_width(), self.winfo_height())) - def startMainLoop(self): + def _startMainLoop(self): self.mainloop() - def quitVRCT(self): + def _quitVRCT(self): self.quit() self.destroy() - def openConfigWindow(self, _e): + def _openConfigWindow(self, _e): callFunctionIfCallable(self._view_variable.CALLBACK_OPEN_CONFIG_WINDOW) - self.adjustToMainWindowGeometry() + self._adjustToMainWindowGeometry() self.modal_window.deiconify() - self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID = self.bind("", self.adjustToMainWindowGeometry, "+") + self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID = self.bind("", self._adjustToMainWindowGeometry, "+") self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID = self.modal_window.bind("", lambda _e: self.config_window.lift(), "+") self.config_window.deiconify() self.config_window.focus_set() - def closeConfigWindow(self): + def _closeConfigWindow(self): callFunctionIfCallable(self._view_variable.CALLBACK_CLOSE_CONFIG_WINDOW) self.config_window.withdraw() @@ -122,7 +128,7 @@ class VRCT_GUI(CTk): - def openSelectableLanguagesWindow(self, selectable_language_window_type): + def _openSelectableLanguagesWindow(self, selectable_language_window_type): # print("___________________________________open____________________________________________________") # print("your", self._view_variable.IS_OPENED_SELECTABLE_YOUR_LANGUAGE_WINDOW) # print("target", self._view_variable.IS_OPENED_SELECTABLE_TARGET_LANGUAGE_WINDOW) @@ -151,7 +157,7 @@ class VRCT_GUI(CTk): self.selectable_languages_window.attributes("-topmost", True) - def closeSelectableLanguagesWindow(self): + def _closeSelectableLanguagesWindow(self): self.sls__arrow_img_your_language.configure(image=CTkImage(self.settings.main.image_file.ARROW_LEFT.rotate(180), size=self.settings.main.uism.SLS__BOX_OPTION_MENU_ARROW_IMAGE_SIZE)) self.sls__arrow_img_target_language.configure(image=CTkImage(self.settings.main.image_file.ARROW_LEFT.rotate(180), size=self.settings.main.uism.SLS__BOX_OPTION_MENU_ARROW_IMAGE_SIZE)) self.selectable_languages_window.withdraw() @@ -168,7 +174,7 @@ class VRCT_GUI(CTk): - def changeMainWindowWidgetsStatus(self, status, target_names): + def _changeMainWindowWidgetsStatus(self, status, target_names): _changeMainWindowWidgetsStatus( vrct_gui=self, settings=self.settings.main, @@ -177,7 +183,7 @@ class VRCT_GUI(CTk): target_names=target_names, ) - def changeConfigWindowWidgetsStatus(self, status, target_names): + def _changeConfigWindowWidgetsStatus(self, status, target_names): _changeConfigWindowWidgetsStatus( config_window=self.config_window, settings=self.settings.config_window, @@ -186,7 +192,7 @@ class VRCT_GUI(CTk): target_names=target_names, ) - def printToTextbox(self, target_type, original_message=None, translated_message=None): + def _printToTextbox(self, target_type, original_message=None, translated_message=None): _printToTextbox( vrct_gui=self, settings=self.settings.main, @@ -196,7 +202,7 @@ class VRCT_GUI(CTk): tags=target_type, ) - def setDefaultActiveLanguagePresetTab(self, tab_no:str): + def _setDefaultActiveLanguagePresetTab(self, tab_no:str): self.current_active_preset_tab = getattr(self, f"sls__presets_button_{tab_no}") _setDefaultActiveTab( active_tab_widget=self.current_active_preset_tab, @@ -204,20 +210,20 @@ class VRCT_GUI(CTk): active_text_color=self.settings.main.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR ) - def enableMainWindowSidebarCompactMode(self): + def _enableMainWindowSidebarCompactMode(self): self.sidebar_bg_container.grid_remove() self.sidebar_compact_mode_bg_container.grid() self.minimize_sidebar_button_container__for_closing.grid_remove() self.minimize_sidebar_button_container__for_opening.grid() - def disableMainWindowSidebarCompactMode(self): + def _disableMainWindowSidebarCompactMode(self): self.sidebar_compact_mode_bg_container.grid_remove() self.sidebar_bg_container.grid() self.minimize_sidebar_button_container__for_opening.grid_remove() self.minimize_sidebar_button_container__for_closing.grid() - def adjustToMainWindowGeometry(self, e=None): + def _adjustToMainWindowGeometry(self, e=None): self.update_idletasks() x_pos = self.winfo_rootx() y_pos = self.winfo_rooty() @@ -239,7 +245,7 @@ class VRCT_GUI(CTk): self.adjusted_event=str(e) - def showErrorMessage(self, target_widget): + def _showErrorMessage(self, target_widget): self.error_message_window.show(target_widget=target_widget) def _clearErrorMessage(self): From 379e9164281b0c5f4a4eddade77ed28c077bb7fd Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 11 Oct 2023 13:34:58 +0900 Subject: [PATCH 258/355] =?UTF-8?q?[Update]=20Add=20Splash=20Screen:=20?= =?UTF-8?q?=E8=B5=B7=E5=8B=95=E4=B8=AD=E3=81=AB=E4=BD=95=E3=81=8B=E3=81=97?= =?UTF-8?q?=E3=82=89=E3=81=AE=E8=A1=A8=E7=A4=BA=E3=82=92=E3=81=97=E3=81=A6?= =?UTF-8?q?=E3=80=81=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC=E3=81=B8=E3=81=AE?= =?UTF-8?q?=E3=83=95=E3=82=A3=E3=83=BC=E3=83=89=E3=83=90=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=80=82=E7=94=BB=E5=83=8F=E3=81=AF=E4=BB=AE=E7=BD=AE=E3=81=8D?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 5 +++ view.py | 11 ++++++ vrct_gui/splash_window/SplashWindow.py | 51 ++++++++++++++++++++++++++ vrct_gui/splash_window/__init__.py | 1 + 4 files changed, 68 insertions(+) create mode 100644 vrct_gui/splash_window/SplashWindow.py create mode 100644 vrct_gui/splash_window/__init__.py diff --git a/main.py b/main.py index 103e09c3..b3801625 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,10 @@ +from vrct_gui.splash_window import SplashWindow +splash = SplashWindow() +splash.showSplash() + import controller if __name__ == "__main__": controller.createMainWindow() + splash.destroySplash() controller.showMainWindow() \ No newline at end of file diff --git a/view.py b/view.py index cc77c545..7608a161 100644 --- a/view.py +++ b/view.py @@ -864,6 +864,17 @@ class View(): def clearErrorMessage(self): vrct_gui._clearErrorMessage() + + + + @staticmethod + def showSplash(): + vrct_gui.showSplash() + + @staticmethod + def destroySplash(): + vrct_gui.destroySplash() + # These conversations are generated by ChatGPT def _insertSampleConversationToTextbox(self): diff --git a/vrct_gui/splash_window/SplashWindow.py b/vrct_gui/splash_window/SplashWindow.py new file mode 100644 index 00000000..374323eb --- /dev/null +++ b/vrct_gui/splash_window/SplashWindow.py @@ -0,0 +1,51 @@ +from customtkinter import CTkImage, CTkLabel, CTkToplevel +from ..ui_utils import openImageKeepAspectRatio, getImageFileFromUiUtils +from time import sleep + +class SplashWindow(CTkToplevel): + def __init__(self): + super().__init__() + self.withdraw() + self.overrideredirect(True) + self.configure(fg_color="#292a2d") + self.title("SplashWindow") + + + sw=self.winfo_screenwidth() + sh=self.winfo_screenheight() + + pw=int(sw/4) + + self.grid_columnconfigure((0,2), weight=1) + self.grid_rowconfigure((0,2), weight=1) + (img, desired_width, height) = openImageKeepAspectRatio(getImageFileFromUiUtils("vrct_logo_for_dark_mode.png"), pw) + label = CTkLabel( + self, + text=None, + height=0, + fg_color="#292a2d", + image=CTkImage(img, size=(desired_width, height)) + ) + label.grid(row=1, column=1) + + geometry_width=desired_width+int(desired_width*0.2) + geometry_height=height+int(height*0.5) + + self.geometry(str(geometry_width)+"x"+str(geometry_height)+"+"+str((sw-geometry_width)//2)+"+"+str((sh-geometry_height)//2)) + + + + def showSplash(self): + self.deiconify() + + for i in range(0,91,20): + if not self.winfo_exists(): + break + self.attributes("-alpha", i/100) + self.update() + sleep(1/50) + self.attributes("-alpha", 1) + + + def destroySplash(self): + self.destroy() \ No newline at end of file diff --git a/vrct_gui/splash_window/__init__.py b/vrct_gui/splash_window/__init__.py new file mode 100644 index 00000000..3ce5e580 --- /dev/null +++ b/vrct_gui/splash_window/__init__.py @@ -0,0 +1 @@ +from .SplashWindow import * \ No newline at end of file From 8185050ea291f856581bef4ceb4f4b7b71a129d8 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 11 Oct 2023 15:28:54 +0900 Subject: [PATCH 259/355] =?UTF-8?q?[Update]=20Config=20Window:=20UI=20Size?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E5=AF=BE=E5=BF=9C(=E6=8C=87=E5=AE=9A?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=81=84=E3=81=9F=E3=82=82=E3=81=AE=E3=81=AF?= =?UTF-8?q?=E5=85=A8=E3=81=A6=E5=AF=BE=E5=BF=9C=E3=81=97=E3=81=9F=E3=81=AF?= =?UTF-8?q?=E3=81=9A)=E3=80=82=E3=82=B3=E3=83=B3=E3=83=91=E3=82=AF?= =?UTF-8?q?=E3=83=88=E3=83=A2=E3=83=BC=E3=83=89=E3=81=AE=E3=83=A9=E3=83=99?= =?UTF-8?q?=E3=83=AB=E6=97=A5=E6=9C=AC=E8=AA=9E=E5=AF=BE=E5=BF=9C=E3=80=82?= =?UTF-8?q?=20[Refactor]=20CTkScrollableFrame=E3=82=92=E4=BD=BF=E7=94=A8?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=81=84=E3=82=8Bwidget=E3=81=AEUI=20Size?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E5=AF=BE=E5=BF=9C=E3=82=84CTK=E3=81=AEheight?= =?UTF-8?q?=20bug=E4=BF=AE=E6=AD=A3=E3=82=92ui=5Futils.py=E3=81=B8?= =?UTF-8?q?=E9=96=A2=E6=95=B0=E5=88=87=E3=82=8A=E5=87=BA=E3=81=97=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 1 + locales/ja.yml | 1 + view.py | 1 + vrct_gui/_CreateDropdownMenuWindow.py | 13 ++++--- vrct_gui/_CreateSelectableLanguagesWindow.py | 12 +++--- .../_createSettingBoxCompactModeButton.py | 15 +++---- .../_createSettingBoxContainer.py | 2 +- .../createSideMenuAndSettingsBoxContainers.py | 8 +++- .../_SettingBoxGenerator.py | 18 +++++---- vrct_gui/ui_managers/ColorThemeManager.py | 2 + vrct_gui/ui_managers/UiScalingManager.py | 39 ++++++++++++++----- vrct_gui/ui_utils/ui_utils.py | 14 +++++-- 12 files changed, 85 insertions(+), 41 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index abfe232f..9778a3ae 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -26,6 +26,7 @@ selectable_language_window: config_window: config_title: Settings + compact_mode: Compact Mode side_menu_labels: appearance: Appearance translation: Translation diff --git a/locales/ja.yml b/locales/ja.yml index 4b5f373a..6fafbeae 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -27,6 +27,7 @@ selectable_language_window: config_window: config_title: 設定 + compact_mode: コンパクトモード side_menu_labels: appearance: デザイン translation: 翻訳 diff --git a/view.py b/view.py index 7608a161..73fe3910 100644 --- a/view.py +++ b/view.py @@ -137,6 +137,7 @@ class View(): VAR_ERROR_MESSAGE=StringVar(value=""), VAR_VERSION=StringVar(value=config.VERSION), VAR_CONFIG_WINDOW_TITLE=StringVar(value=i18n.t("config_window.config_title")), + VAR_CONFIG_WINDOW_COMPACT_MODE_LABEL=StringVar(value=i18n.t("config_window.compact_mode")), # Side Menu Labels diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index f5f79be7..a4f880b0 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -3,7 +3,7 @@ from types import SimpleNamespace from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont, CTkScrollableFrame from time import sleep -from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor, getLatestWidth, getLatestHeight +from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor, getLatestWidth, getLatestHeight, applyUiScalingAndFixTheBugScrollBar from functools import partial from utils import isEven, makeEven @@ -126,7 +126,6 @@ class _CreateDropdownMenuWindow(CTkToplevel): border_width=0, ) self.scroll_frame_container.grid(row=0, column=0, padx=BORDER_WIDTH, pady=BORDER_WIDTH, sticky="nsew") - self.scroll_frame_container._scrollbar.grid_configure(padx=self.scrollbar_ipadx) self.scroll_frame_container.grid_columnconfigure(0, weight=1) @@ -139,6 +138,12 @@ class _CreateDropdownMenuWindow(CTkToplevel): self._createDropdownMenuValues(dropdown_menu_widget_id, dropdown_menu_values, command) + applyUiScalingAndFixTheBugScrollBar( + scrollbar_widget=self.scroll_frame_container, + padx=self.scrollbar_ipadx, + width=self.scrollbar_width, + ) + geometry_width = int(self.new_width + self.scroll_frame_container._scrollbar.winfo_width() + (BORDER_WIDTH*2) + (self.scrollbar_ipadx[0] + self.scrollbar_ipadx[1])) geometry_height = int(self.new_height + (BORDER_WIDTH*2)) @@ -210,10 +215,6 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.new_width = makeEven(self.new_width) self.scroll_frame_container.configure(width=self.new_width, height=self.new_height) - # This is for CustomTkinter's spec change or bug fix. - self.scroll_frame_container._scrollbar.configure(height=0) - self.scroll_frame_container._scrollbar.configure(width=self.scrollbar_width) - row=0 diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index f684ac3f..51fdd1f9 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -1,6 +1,6 @@ from functools import partial -from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor +from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor, applyUiScalingAndFixTheBugScrollBar from utils import callFunctionIfCallable from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont, CTkScrollableFrame @@ -124,11 +124,11 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.scroll_frame_container = CTkScrollableFrame(self, corner_radius=0, fg_color=self.settings.ctm.MAIN_BG_COLOR, width=self.width_new, height=self.height_new) self.scroll_frame_container.grid(row=1, column=0, sticky="nsew") - self.scroll_frame_container._scrollbar.grid_configure(padx=self.settings.uism.SCROLLBAR_IPADX) - - # This is for CustomTkinter's spec change or bug fix. - self.scroll_frame_container._scrollbar.configure(height=0) - self.scroll_frame_container._scrollbar.configure(width=self.settings.uism.SCROLLBAR_WIDTH) + applyUiScalingAndFixTheBugScrollBar( + scrollbar_widget=self.scroll_frame_container, + padx=self.settings.uism.SCROLLBAR_IPADX, + width=self.settings.uism.SCROLLBAR_WIDTH, + ) self.container = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.settings.ctm.MAIN_BG_COLOR, width=0, height=0) diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py index 79ae86da..6cdcd504 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py @@ -13,7 +13,7 @@ def _createSettingBoxCompactModeButton(parent_widget, config_window, settings, v config_window.setting_box_compact_mode_button_container = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) - config_window.setting_box_compact_mode_button_container.grid(row=0, column=1, padx=(0, 20), sticky="nsw") + config_window.setting_box_compact_mode_button_container.grid(row=0, column=1, padx=settings.uism.COMPACT_MODE_PADX, sticky="nsw") @@ -26,12 +26,13 @@ def _createSettingBoxCompactModeButton(parent_widget, config_window, settings, v config_window.setting_box_compact_mode_label = CTkLabel( config_window.setting_box_compact_mode_button_container, height=0, - text="Compact Mode", + # text="Compact Mode", + textvariable=view_variable.VAR_CONFIG_WINDOW_COMPACT_MODE_LABEL, anchor="w", - font=CTkFont(family=settings.FONT_FAMILY, size=12, weight="normal"), + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.COMPACT_MODE_LABEL_FONT_SIZE, weight="normal"), text_color=settings.ctm.LABELS_TEXT_COLOR ) - config_window.setting_box_compact_mode_label.grid(row=0, column=0, padx=(0,10)) + config_window.setting_box_compact_mode_label.grid(row=0, column=0, padx=settings.uism.COMPACT_MODE_LABEL_PADX) @@ -50,14 +51,14 @@ def _createSettingBoxCompactModeButton(parent_widget, config_window, settings, v width=0, # corner_radius=0, border_width=0, - switch_width=40, - switch_height=16, + switch_width=settings.uism.COMPACT_MODE_SWITCH_WIDTH, + switch_height=settings.uism.COMPACT_MODE_SWITCH_HEIGHT, onvalue=True, offvalue=False, command=switchConfigWindowCompactMode, # fg_color="", # bg_color="red", - progress_color=settings.ctm.SB__SWITCH_BOX_ACTIVE_BG_COLOR, # SB__SWITCH_BOX_ACTIVE_BG_COLOR is for SB. change it later. + progress_color=settings.ctm.COMPACT_MODE_SWITCH_BOX_ACTIVE_BG_COLOR, ) config_window.setting_box_compact_mode_switch_box.grid(row=0, column=0) \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py index d54cea60..8efbbdf5 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py @@ -30,7 +30,7 @@ def _createSettingBoxContainer(config_window, settings, view_variable, setting_b setting_box_row=0 for i, setting_box_setting in enumerate(setting_box_container_settings["setting_boxes"]): # Top-Padding that can be container the section title - setting_box_top_padding = CTkFrame(setting_box_container_widget, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=60) + setting_box_top_padding = CTkFrame(setting_box_container_widget, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=settings.uism.SB__TOP_PADY) setting_box_top_padding.grid(row=setting_box_row, column=0, sticky="ew", padx=0, pady=0) setting_box_top_padding.grid_columnconfigure(0, weight=1) setting_box_row+=1 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index 6b8fa141..e5fb507d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -1,6 +1,6 @@ from customtkinter import CTkFrame, CTkScrollableFrame -from ....ui_utils import _setDefaultActiveTab +from ....ui_utils import _setDefaultActiveTab, applyUiScalingAndFixTheBugScrollBar from ._addConfigSideMenuItem import _addConfigSideMenuItem from ._createSettingBoxContainer import _createSettingBoxContainer @@ -41,6 +41,12 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl config_window.main_setting_box_scrollable_container = CTkScrollableFrame(config_window.main_bg_container, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR) config_window.main_setting_box_scrollable_container.grid(row=1, column=0, sticky="nsew") + applyUiScalingAndFixTheBugScrollBar( + scrollbar_widget=config_window.main_setting_box_scrollable_container, + padx=settings.uism.SCROLLBAR_IPADX, + width=settings.uism.SCROLLBAR_WIDTH, + ) + config_window.main_setting_box_bg_wrapper = CTkFrame(config_window.main_setting_box_scrollable_container, corner_radius=0, width=0, height=0, fg_color=settings.ctm.MAIN_BG_COLOR) config_window.main_setting_box_bg_wrapper.grid(row=0, column=0, pady=settings.uism.SB__BOTTOM_MARGIN, sticky="n") diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 5705031e..6b509aec 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -21,7 +21,7 @@ class _SettingBoxGenerator(): setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) # "pady=(0,1)" is for bottom padding. It can be removed(override) when you do like "self.attr_name.grid(row=row, pady=0)" - setting_box_frame.grid(column=0, padx=0, pady=(0,1), sticky="ew") + setting_box_frame.grid(column=0, padx=0, pady=self.settings.uism.SB__FAKE_BOTTOM_BORDER_SIZE, sticky="ew") setting_box_frame.grid_columnconfigure(0, weight=1) @@ -54,7 +54,7 @@ class _SettingBoxGenerator(): setting_box_labels_frame, textvariable=for_var_label_text, anchor="w", - # height=0, + height=0, font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__LABEL_FONT_SIZE, weight="normal"), text_color=self.settings.ctm.LABELS_TEXT_COLOR ) @@ -66,7 +66,7 @@ class _SettingBoxGenerator(): textvariable=for_var_desc_text, anchor="w", justify="left", - # height=0, + height=0, wraplength=int(self.settings.uism.MAIN_AREA_MIN_WIDTH / 2), font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__DESC_FONT_SIZE, weight="normal"), text_color=self.settings.ctm.LABELS_DESC_TEXT_COLOR @@ -88,9 +88,9 @@ class _SettingBoxGenerator(): optionmenu_bg_color=self.settings.ctm.SB__OPTIONMENU_BG_COLOR, optionmenu_hovered_bg_color=self.settings.ctm.SB__OPTIONMENU_HOVERED_BG_COLOR, optionmenu_clicked_bg_color=self.settings.ctm.SB__OPTIONMENU_CLICKED_BG_COLOR, - optionmenu_ipadx=(8,8), - optionmenu_ipady=2, - optionmenu_ipady_between_img=8, + optionmenu_ipadx=self.settings.uism.SB__OPTIONMENU_IPADX, + optionmenu_ipady=self.settings.uism.SB__OPTIONMENU_IPADY, + optionmenu_padx_between_img=self.settings.uism.SB__OPTIONMENU_IPADX_BETWEEN_IMG, optionmenu_min_height=self.settings.uism.SB__OPTIONMENU_MIN_HEIGHT, optionmenu_min_width=self.settings.uism.SB__OPTIONMENU_MIN_WIDTH, variable=variable, @@ -98,7 +98,7 @@ class _SettingBoxGenerator(): font_size=self.settings.uism.SB__OPTION_MENU_FONT_SIZE, text_color=self.settings.ctm.LABELS_TEXT_COLOR, image_file=self.settings.image_file.ARROW_LEFT.rotate(90), - image_size=(14,14), + image_size=self.settings.uism.SB__OPTIONMENU_IMG_SIZE, optionmenu_clicked_command=lambda _e: self.dropdown_menu_window.show( dropdown_menu_widget_id=optionmenu_attr_name, ), @@ -187,8 +187,12 @@ class _SettingBoxGenerator(): def createSettingBoxSlider(self, for_var_label_text, for_var_desc_text, slider_attr_name, slider_range, command, variable, slider_number_of_steps: Union[int, None] = None): (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) + # print(self.settings.uism.SB__SLIDER_WIDTH) + # print(self.settings.uism.SB__SLIDER_HEIGHT) slider_widget = CTkSlider( setting_box_item_frame, + width=self.settings.uism.SB__SLIDER_WIDTH, + height=self.settings.uism.SB__SLIDER_HEIGHT, from_=slider_range[0], to=slider_range[1], number_of_steps=slider_number_of_steps, diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index eff12ae5..e297b6a7 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -223,6 +223,8 @@ class ColorThemeManager(): # Top bar self.config_window.TOP_BAR_BG_COLOR = self.DARK_850_COLOR + # Compact Mode + self.config_window.COMPACT_MODE_SWITCH_BOX_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR # Main diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 24acbd9b..a49d1266 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -4,6 +4,7 @@ class UiScalingManager(): def __init__(self, scaling_percentage): scaling_float = int(scaling_percentage.replace("%", "")) / 100 self.SCALING_FLOAT = max(scaling_float, 0.4) + self.common = SimpleNamespace() self.main = SimpleNamespace() self.config_window = SimpleNamespace() self.selectable_language_window = SimpleNamespace() @@ -17,6 +18,9 @@ class UiScalingManager(): def _calculatedUiSizes(self): # Common # RESPONSIVE_UI_SIZE_INT_10 ... RESPONSIVE_UI_SIZE_INT_300 + self.common.SCROLLBAR_IPADX = (self._calculateUiSize(2), self._calculateUiSize(2)) + self.common.SCROLLBAR_WIDTH = self._calculateUiSize(16) + for i in range(10, 301, 10): setattr(self.main, f"RESPONSIVE_UI_SIZE_INT_{i}", self._calculateUiSize(i)) setattr(self.config_window, f"RESPONSIVE_UI_SIZE_INT_{i}", self._calculateUiSize(i)) @@ -121,8 +125,8 @@ class UiScalingManager(): # Selectable Language Window self.selectable_language_window.TOP_BAR_MIN_HEIGHT = self._calculateUiSize(50) - self.selectable_language_window.SCROLLBAR_IPADX = (self._calculateUiSize(2), self._calculateUiSize(2)) - self.selectable_language_window.SCROLLBAR_WIDTH = self._calculateUiSize(16) + self.selectable_language_window.SCROLLBAR_IPADX = self.common.SCROLLBAR_IPADX + self.selectable_language_window.SCROLLBAR_WIDTH = self.common.SCROLLBAR_WIDTH self.selectable_language_window.GO_BACK_BUTTON_LABEL_FONT_SIZE = self._calculateUiSize(14) self.selectable_language_window.GO_BACK_BUTTON_IPADX = self._calculateUiSize(10) @@ -151,25 +155,34 @@ class UiScalingManager(): self.config_window.TOP_BAR_SIDE__CONFIG_TITLE_LEFT_PADX = int(self.config_window.TOP_BAR_SIDE__CONFIG_TITLE_FONT_SIZE + self._calculateUiSize(16)) self.config_window.TOP_BAR_SIDE__TITLE_PADX= self._calculateUiSize(30) + # Compact Mode + self.config_window.COMPACT_MODE_PADX = (0, self._calculateUiSize(20)) + self.config_window.COMPACT_MODE_LABEL_FONT_SIZE = self._calculateUiSize(12) + self.config_window.COMPACT_MODE_LABEL_PADX = (0, self._calculateUiSize(10)) + self.config_window.COMPACT_MODE_SWITCH_WIDTH = self._calculateUiSize(40) + self.config_window.COMPACT_MODE_SWITCH_HEIGHT = self._calculateUiSize(16) + + # Side menu - self.config_window.SIDE_MENU_TOP_PADY= self._calculateUiSize(54) + self.config_window.SIDE_MENU_TOP_PADY = self._calculateUiSize(54) self.config_window.SIDE_MENU_LABELS_IPADX = self._calculateUiSize(20) - self.config_window.SIDE_MENU_LABELS_IPADY= self._calculateUiSize(8) - self.config_window.SIDE_MENU_LABELS_FONT_SIZE= self._calculateUiSize(18) + self.config_window.SIDE_MENU_LABELS_IPADY = self._calculateUiSize(8) + self.config_window.SIDE_MENU_LABELS_FONT_SIZE = self._calculateUiSize(18) # Top bar Main self.config_window.TOP_BAR_MAIN__TITLE_FONT_SIZE = self._calculateUiSize(22) + self.config_window.SCROLLBAR_IPADX = self.common.SCROLLBAR_IPADX + self.config_window.SCROLLBAR_WIDTH = self.common.SCROLLBAR_WIDTH # Setting Box self.config_window.MAIN_AREA_MIN_WIDTH = self._calculateUiSize(720) - self.config_window.SB__TOP_PADY_IF_WITH_SECTION_TITLE = (self._calculateUiSize(24)) - self.config_window.SB__TOP_PADY_IF_WITHOUT_SECTION_TITLE = (self._calculateUiSize(64)) - self.config_window.SB__BOTTOM_PADY = (self._calculateUiSize(40)) + self.config_window.SB__TOP_PADY = (self._calculateUiSize(60)) self.config_window.SB__IPADX = self._calculateUiSize(20) self.config_window.SB__IPADY = self._calculateUiSize(12) self.config_window.SB__BOTTOM_MARGIN = (0, self._calculateUiSize(60)) + self.config_window.SB__FAKE_BOTTOM_BORDER_SIZE = (0, self._calculateUiSize(1, is_allowed_odd=True)) self.config_window.SB__SECTION_TITLE_FONT_SIZE = self._calculateUiSize(20) self.config_window.SB__SECTION_TITLE_BOTTOM_PADY = (0, self._calculateUiSize(10)) @@ -193,12 +206,16 @@ class UiScalingManager(): self.config_window.SB__OPTION_MENU_FONT_SIZE = self.config_window.SB__SELECTOR_FONT_SIZE self.config_window.SB__OPTIONMENU_MIN_HEIGHT = self._calculateUiSize(30) self.config_window.SB__OPTIONMENU_MIN_WIDTH = self._calculateUiSize(200) + self.config_window.SB__OPTIONMENU_IPADX = (self._calculateUiSize(8), self._calculateUiSize(8)) + self.config_window.SB__OPTIONMENU_IPADY = self._calculateUiSize(2) + self.config_window.SB__OPTIONMENU_IPADX_BETWEEN_IMG = self._calculateUiSize(80) + self.config_window.SB__OPTIONMENU_IMG_SIZE = (self._calculateUiSize(14), self._calculateUiSize(14)) self.config_window.SB__DROPDOWN_MENU_WINDOW_ADDITIONAL_Y_POS = self._calculateUiSize(4) self.config_window.SB__DROPDOWN_MENU_WIDTH = self.config_window.SB__OPTIONMENU_MIN_WIDTH self.config_window.SB__DROPDOWN_MENU_WINDOW_BORDER_WIDTH = self._calculateUiSize(1, is_allowed_odd=True) - self.config_window.SB__DROPDOWN_MENU_SCROLLBAR_IPADX = (self._calculateUiSize(2), self._calculateUiSize(2)) - self.config_window.SB__DROPDOWN_MENU_SCROLLBAR_WIDTH = self._calculateUiSize(16) + self.config_window.SB__DROPDOWN_MENU_SCROLLBAR_IPADX = self.common.SCROLLBAR_IPADX + self.config_window.SB__DROPDOWN_MENU_SCROLLBAR_WIDTH = self.common.SCROLLBAR_WIDTH self.config_window.SB__DROPDOWN_MENU_VALUE_IPADX = (self._calculateUiSize(8), 0) self.config_window.SB__DROPDOWN_MENU_VALUE_IPADY = (self._calculateUiSize(6), self._calculateUiSize(6)) self.config_window.SB__DROPDOWN_MENU_VALUE_PADY = (0, self._calculateUiSize(1, is_allowed_odd=True)) @@ -217,6 +234,8 @@ class UiScalingManager(): self.config_window.SB__ENTRY_FONT_SIZE = self.config_window.SB__SELECTOR_FONT_SIZE self.config_window.SB__ENTRY_HEIGHT = self._calculateUiSize(30) + self.config_window.SB__SLIDER_WIDTH = self._calculateUiSize(200) + self.config_window.SB__SLIDER_HEIGHT = self._calculateUiSize(16) self.config_window.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH = self.config_window.RESPONSIVE_UI_SIZE_INT_50 self.config_window.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT = self.config_window.SB__ENTRY_HEIGHT diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index da1d1648..9b0b5671 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -142,7 +142,7 @@ def createButtonWithImage(parent_widget, button_fg_color, button_enter_color, bu return button_wrapper -def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_bg_color, optionmenu_clicked_bg_color, optionmenu_ipadx, optionmenu_ipady, variable, font_family, font_size, text_color, image_file, image_size, optionmenu_clicked_command, optionmenu_position=None, optionmenu_ipady_between_img=0, optionmenu_min_height=None, optionmenu_min_width=None, setattr_widget=None, image_widget_attr_name=None): +def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_bg_color, optionmenu_clicked_bg_color, optionmenu_ipadx, optionmenu_ipady, variable, font_family, font_size, text_color, image_file, image_size, optionmenu_clicked_command, optionmenu_position=None, optionmenu_padx_between_img=0, optionmenu_min_height=None, optionmenu_min_width=None, setattr_widget=None, image_widget_attr_name=None): option_menu_box = CTkFrame(parent_widget, corner_radius=6, fg_color=optionmenu_bg_color, cursor="hand2") @@ -167,7 +167,7 @@ def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_b font=CTkFont(family=font_family, size=font_size, weight="normal"), text_color=text_color ) - optionmenu_label_widget.grid(row=0, column=LABEL_COLUMN, padx=(0, optionmenu_ipady_between_img)) + optionmenu_label_widget.grid(row=0, column=LABEL_COLUMN, padx=(0, optionmenu_padx_between_img)) optionmenu_img_widget = CTkLabel( @@ -191,4 +191,12 @@ def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_b bindButtonReleaseFunction([optionmenu_label_wrapper, option_menu_box, optionmenu_label_widget, optionmenu_img_widget], optionmenu_clicked_command) - return option_menu_box \ No newline at end of file + return option_menu_box + + +def applyUiScalingAndFixTheBugScrollBar(scrollbar_widget, padx, width): + scrollbar_widget._scrollbar.grid_configure(padx=padx) + + # This is for CustomTkinter's spec change or bug fix. + scrollbar_widget._scrollbar.configure(height=0) + scrollbar_widget._scrollbar.configure(width=width) \ No newline at end of file From 3e6bce367c5de8fa3c01292ed6b1a64529a4aa00 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 11 Oct 2023 23:11:59 +0900 Subject: [PATCH 260/355] =?UTF-8?q?[Update]=20=E5=90=84Window=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E6=99=82=E3=81=AB=E7=94=BB=E9=9D=A2=E3=81=AE=E4=B8=AD?= =?UTF-8?q?=E5=A4=AE=E3=81=AB=E9=85=8D=E7=BD=AE=E3=81=99=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=80=82(=E3=81=9D=E3=82=8C=E3=81=AB?= =?UTF-8?q?=E3=82=88=E3=81=A3=E3=81=A6=E4=B8=80=E7=9E=AC=E5=86=8D=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=E3=81=8C=E8=A6=8B=E3=81=88=E3=82=8B=E3=81=AE=E3=81=A7?= =?UTF-8?q?=E3=80=81=E8=AA=A4=E9=AD=94=E5=8C=96=E3=81=97=E7=94=A8=E3=81=A7?= =?UTF-8?q?=E3=82=82=E3=81=82=E3=82=8B)=E3=83=95=E3=82=A7=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=82=A4=E3=83=B3=E3=82=A2=E3=83=8B=E3=83=A1=E3=83=BC?= =?UTF-8?q?=E3=82=B7=E3=83=A7=E3=83=B3=E8=BF=BD=E5=8A=A0=E3=81=A8=E3=81=9D?= =?UTF-8?q?=E3=82=8C=E3=82=89=E9=96=A2=E6=95=B0=E3=81=AE=E6=B1=8E=E7=94=A8?= =?UTF-8?q?=E5=8C=96=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateModalWindow.py | 2 +- vrct_gui/splash_window/SplashWindow.py | 24 ++++++--------------- vrct_gui/ui_utils/ui_utils.py | 29 +++++++++++++++++++++++++- vrct_gui/vrct_gui.py | 17 ++++++++++++--- 4 files changed, 49 insertions(+), 23 deletions(-) diff --git a/vrct_gui/_CreateModalWindow.py b/vrct_gui/_CreateModalWindow.py index 2e4ba26b..8cd837c2 100644 --- a/vrct_gui/_CreateModalWindow.py +++ b/vrct_gui/_CreateModalWindow.py @@ -9,7 +9,7 @@ class _CreateModalWindow(CTkToplevel): self.title("") self.overrideredirect(True) - self.wm_attributes("-alpha", 0.5) + # self.wm_attributes("-alpha", 0.5) self.wm_attributes("-toolwindow", True) self.attach_window = attach_window diff --git a/vrct_gui/splash_window/SplashWindow.py b/vrct_gui/splash_window/SplashWindow.py index 374323eb..d5e1d2e7 100644 --- a/vrct_gui/splash_window/SplashWindow.py +++ b/vrct_gui/splash_window/SplashWindow.py @@ -1,6 +1,5 @@ from customtkinter import CTkImage, CTkLabel, CTkToplevel -from ..ui_utils import openImageKeepAspectRatio, getImageFileFromUiUtils -from time import sleep +from ..ui_utils import openImageKeepAspectRatio, getImageFileFromUiUtils, setGeometryToCenterOfScreen, fadeInAnimation class SplashWindow(CTkToplevel): def __init__(self): @@ -12,7 +11,7 @@ class SplashWindow(CTkToplevel): sw=self.winfo_screenwidth() - sh=self.winfo_screenheight() + # sh=self.winfo_screenheight() pw=int(sw/4) @@ -26,25 +25,14 @@ class SplashWindow(CTkToplevel): fg_color="#292a2d", image=CTkImage(img, size=(desired_width, height)) ) - label.grid(row=1, column=1) - - geometry_width=desired_width+int(desired_width*0.2) - geometry_height=height+int(height*0.5) - - self.geometry(str(geometry_width)+"x"+str(geometry_height)+"+"+str((sw-geometry_width)//2)+"+"+str((sh-geometry_height)//2)) - + label.grid(row=1, column=1, padx=int(desired_width/7), pady=int(height/3)) def showSplash(self): + self.attributes("-alpha", 0) self.deiconify() - - for i in range(0,91,20): - if not self.winfo_exists(): - break - self.attributes("-alpha", i/100) - self.update() - sleep(1/50) - self.attributes("-alpha", 1) + setGeometryToCenterOfScreen(root_widget=self) + fadeInAnimation(self, steps=5, interval=0.02) def destroySplash(self): diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index 9b0b5671..5dff06b9 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -1,5 +1,6 @@ from os import path as os_path from PIL.Image import open as Image_open, LANCZOS +from time import sleep from customtkinter import CTkFrame, CTkLabel, CTkImage, CTkFont @@ -199,4 +200,30 @@ def applyUiScalingAndFixTheBugScrollBar(scrollbar_widget, padx, width): # This is for CustomTkinter's spec change or bug fix. scrollbar_widget._scrollbar.configure(height=0) - scrollbar_widget._scrollbar.configure(width=width) \ No newline at end of file + scrollbar_widget._scrollbar.configure(width=width) + + +def setGeometryToCenterOfScreen(root_widget): + root_widget.update() + sw=root_widget.winfo_screenwidth() + sh=root_widget.winfo_screenheight() + geometry_width = root_widget.winfo_width() + geometry_height = root_widget.winfo_height() + + root_widget.geometry(str(geometry_width)+"x"+str(geometry_height)+"+"+str((sw-geometry_width)//2)+"+"+str((sh-geometry_height)//2)) + + +def fadeInAnimation(root_widget, steps:int=10, interval:float=0.1, max_alpha:float=1): + alpha_steps = 100 + alpha_steps*=max_alpha + step_size = alpha_steps/steps + root_widget.attributes("-alpha", 0) + num = 0 + while num < alpha_steps: + if not root_widget.winfo_exists(): + break + root_widget.attributes("-alpha", num / 100) + root_widget.update() + sleep(interval) + num += step_size + root_widget.attributes("-alpha", max_alpha) \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index ce96b23e..844c029a 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -11,7 +11,7 @@ from ._printToTextbox import _printToTextbox from .main_window import createMainWindowWidgets from .config_window import ConfigWindow -from .ui_utils import _setDefaultActiveTab, getLatestHeight +from .ui_utils import _setDefaultActiveTab, getLatestHeight, setGeometryToCenterOfScreen, fadeInAnimation from utils import callFunctionIfCallable @@ -20,14 +20,16 @@ class VRCT_GUI(CTk): super().__init__() self.withdraw() self.adjusted_event=None + self.is_config_window_already_opened_once=False self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID=None self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID=None def _showGUI(self): + self.attributes("-alpha", 0) self.deiconify() - self.update() - self.geometry("{}x{}".format(self.winfo_width(), self.winfo_height())) + setGeometryToCenterOfScreen(root_widget=self) + fadeInAnimation(self, steps=5, interval=0.008) def _createGUI(self, settings, view_variable): self.settings = settings @@ -109,11 +111,20 @@ class VRCT_GUI(CTk): callFunctionIfCallable(self._view_variable.CALLBACK_OPEN_CONFIG_WINDOW) self._adjustToMainWindowGeometry() + self.modal_window.attributes("-alpha", 0) self.modal_window.deiconify() + fadeInAnimation(self.modal_window, steps=5, interval=0.005, max_alpha=0.5) + self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID = self.bind("", self._adjustToMainWindowGeometry, "+") self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID = self.modal_window.bind("", lambda _e: self.config_window.lift(), "+") + self.config_window.attributes("-alpha", 0) self.config_window.deiconify() + if self.is_config_window_already_opened_once is False: + setGeometryToCenterOfScreen(self.config_window) + self.is_config_window_already_opened_once = True + fadeInAnimation(self.config_window, steps=5, interval=0.005) + self.config_window.attributes("-alpha", 1) self.config_window.focus_set() def _closeConfigWindow(self): From 243492ac91a8532fe54bc8ec03b2f5fa927ef090 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 12 Oct 2023 06:37:25 +0900 Subject: [PATCH 261/355] =?UTF-8?q?[bugfix]=20Config=20Window:=20=E3=82=AA?= =?UTF-8?q?=E3=83=97=E3=82=B7=E3=83=A7=E3=83=B3=E3=83=A1=E3=83=8B=E3=83=A5?= =?UTF-8?q?=E3=83=BC=E7=B3=BB=E3=81=A7=E3=80=81=E7=9F=A2=E5=8D=B0=E7=94=BB?= =?UTF-8?q?=E5=83=8F=E3=81=A8=E3=83=A9=E3=83=99=E3=83=AB=E3=81=AE=E9=9A=99?= =?UTF-8?q?=E9=96=93px=E6=8C=87=E5=AE=9A=E3=81=8C=E3=83=86=E3=82=B9?= =?UTF-8?q?=E3=83=88=E7=94=A8=E3=81=AB=E5=A4=A7=E3=81=8D=E3=81=8F=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E3=81=97=E3=81=A6=E3=81=84=E3=81=9F=E3=81=BE=E3=81=BE?= =?UTF-8?q?=E3=81=A0=E3=81=A3=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/ui_managers/UiScalingManager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index a49d1266..c669cf69 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -208,7 +208,7 @@ class UiScalingManager(): self.config_window.SB__OPTIONMENU_MIN_WIDTH = self._calculateUiSize(200) self.config_window.SB__OPTIONMENU_IPADX = (self._calculateUiSize(8), self._calculateUiSize(8)) self.config_window.SB__OPTIONMENU_IPADY = self._calculateUiSize(2) - self.config_window.SB__OPTIONMENU_IPADX_BETWEEN_IMG = self._calculateUiSize(80) + self.config_window.SB__OPTIONMENU_IPADX_BETWEEN_IMG = self._calculateUiSize(8) self.config_window.SB__OPTIONMENU_IMG_SIZE = (self._calculateUiSize(14), self._calculateUiSize(14)) self.config_window.SB__DROPDOWN_MENU_WINDOW_ADDITIONAL_Y_POS = self._calculateUiSize(4) From a7644fce234e57160ca7bfee6d371c969d90bc8d Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 12 Oct 2023 09:40:01 +0900 Subject: [PATCH 262/355] =?UTF-8?q?[Update]=20Config=20Window:=20=E3=83=87?= =?UTF-8?q?=E3=83=90=E3=82=A4=E3=82=B9=E3=81=8C=E6=A4=9C=E5=87=BA=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=AA=E3=81=8B=E3=81=A3=E3=81=9F=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AEdisabled=E8=A1=A8=E7=A4=BA=E4=B8=80=E9=83=A8=E5=AE=9F?= =?UTF-8?q?=E8=A3=85(Mic=20Host,=20Mic=20Device,=20Speaker=20Device?= =?UTF-8?q?=E3=81=AE=E3=82=AA=E3=83=97=E3=82=B7=E3=83=A7=E3=83=B3=E3=83=A1?= =?UTF-8?q?=E3=83=8B=E3=83=A5=E3=83=BC=E3=81=AE=E3=81=BF)=20=E3=81=9D?= =?UTF-8?q?=E3=81=97=E3=81=A6=E3=81=9D=E3=81=AE=E9=96=A2=E6=95=B0=E3=81=AE?= =?UTF-8?q?=E6=B1=8E=E7=94=A8=E5=8C=96=E3=80=82=20[Chore]=20Appearance=20T?= =?UTF-8?q?heme=E3=82=92=E9=96=8B=E7=99=BA=E4=B8=AD=E3=81=A8=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=83=A9=E3=83=99=E3=83=AB=E7=B7=A8=E9=9B=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 2 +- locales/ja.yml | 2 +- view.py | 20 ++++++++++ vrct_gui/_changeConfigWindowWidgetsStatus.py | 26 +++++++++++++ vrct_gui/config_window/ConfigWindow.py | 2 + .../_SettingBoxGenerator.py | 38 +++++++++++++------ .../createSettingBox_Mic.py | 1 + .../createSettingBox_Speaker.py | 1 + .../createSidebarLanguagesSettings.py | 2 +- vrct_gui/ui_managers/ColorThemeManager.py | 2 + vrct_gui/ui_utils/ui_utils.py | 13 ++++++- 11 files changed, 94 insertions(+), 15 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 9778a3ae..ae3896d6 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -40,7 +40,7 @@ config_window: label: Transparency desc: Change the main window's transparency. appearance_theme: - label: Theme + label: Theme [Under development] desc: Change the color theme. If you selected "System", it will adjust based on your Windows theme. ui_size: label: UI Size diff --git a/locales/ja.yml b/locales/ja.yml index 6fafbeae..1a773f01 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -41,7 +41,7 @@ config_window: label: 透明度 desc: メイン画面の透明度を変更できます。 appearance_theme: - label: 外観テーマ + label: 外観テーマ [開発中] desc: カラーテーマを変更できます。「System」を選択した場合、Windowsのテーマに基づいて自動的に「Dark」か「Light」テーマを判断し、適用します。 ui_size: label: UIのサイズ diff --git a/view.py b/view.py index 73fe3910..dec8bf38 100644 --- a/view.py +++ b/view.py @@ -447,6 +447,26 @@ class View(): self.enableConfigWindowCompactMode() vrct_gui.config_window.setting_box_compact_mode_switch_box.select() + + + if config.CHOICE_MIC_HOST == "NoHost" or config.CHOICE_MIC_DEVICE == "NoDevice": + vrct_gui._changeConfigWindowWidgetsStatus( + status="disabled", + target_names=[ + "sb__optionmenu_mic_host", + "sb__optionmenu_mic_device", + ] + ) + + if config.CHOICE_SPEAKER_DEVICE == "NoDevice": + vrct_gui._changeConfigWindowWidgetsStatus( + status="disabled", + target_names=[ + "sb__optionmenu_speaker_device", + ] + ) + + # Insert sample conversation for testing. # self._insertSampleConversationToTextbox() diff --git a/vrct_gui/_changeConfigWindowWidgetsStatus.py b/vrct_gui/_changeConfigWindowWidgetsStatus.py index da2da440..0183c886 100644 --- a/vrct_gui/_changeConfigWindowWidgetsStatus.py +++ b/vrct_gui/_changeConfigWindowWidgetsStatus.py @@ -5,6 +5,16 @@ def _changeConfigWindowWidgetsStatus(config_window, settings, view_variable, sta target_names = ["mic_energy_threshold_check_button", "speaker_energy_threshold_check_button"] + def disableOptionmenuWidget(target_widget): + target_widget.label_widget.configure(text_color=settings.ctm.LABELS_TEXT_DISABLED_COLOR) + if target_widget.desc_widget is not None: + target_widget.desc_widget.configure(text_color=settings.ctm.LABELS_TEXT_DISABLED_COLOR) + target_widget.optionmenu_label_widget.configure(text_color=settings.ctm.LABELS_TEXT_DISABLED_COLOR) + target_widget.optionmenu_img_widget.configure(image=CTkImage(settings.image_file.ARROW_LEFT_DISABLED.rotate(90), size=settings.uism.SB__OPTIONMENU_IMG_SIZE)) + target_widget.optionmenu_box.unbindFunction() + target_widget.optionmenu_box.configure(cursor="") + + for target_name in target_names: match target_name: case "mic_energy_threshold_check_button": @@ -26,6 +36,22 @@ def _changeConfigWindowWidgetsStatus(config_window, settings, view_variable, sta config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.children["!ctklabel"].configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR) + case "sb__optionmenu_mic_host": + if status == "disabled": + target_widget = config_window.sb__widgets["sb__optionmenu_mic_host"] + disableOptionmenuWidget(target_widget) + + case "sb__optionmenu_mic_device": + if status == "disabled": + target_widget = config_window.sb__widgets["sb__optionmenu_mic_device"] + disableOptionmenuWidget(target_widget) + + case "sb__optionmenu_speaker_device": + if status == "disabled": + target_widget = config_window.sb__widgets["sb__optionmenu_speaker_device"] + disableOptionmenuWidget(target_widget) + + case _: raise ValueError(f"No matching case for target_name: {target_name}") diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 918b9250..463fb78a 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -26,6 +26,8 @@ class ConfigWindow(CTkToplevel): # When the configuration window's compact mode is turned on, it will call `grid_remove()` on each widget appended to this array. In the opposite case, `grid()` will be called. self.additional_widgets = [] + self.sb__widgets = {} + createConfigWindowTitle(config_window=self, settings=self.settings, view_variable=self._view_variable) createSettingBoxTopBar(config_window=self, settings=self.settings, view_variable=self._view_variable) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 6b509aec..6a92002a 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -1,4 +1,5 @@ from functools import partial +from types import SimpleNamespace from typing import Union from customtkinter import CTkOptionMenu, CTkFont, CTkFrame, CTkLabel, CTkRadioButton, CTkEntry, CTkSlider, CTkSwitch, CTkCheckBox, CTkProgressBar @@ -17,7 +18,9 @@ class _SettingBoxGenerator(): self.dropdown_menu_window = vrct_gui.vrct_gui.dropdown_menu_window - def _createSettingBoxFrame(self, for_var_label_text, for_var_desc_text): + def _createSettingBoxFrame(self, sb__attr_name, for_var_label_text, for_var_desc_text): + self.config_window.sb__widgets[sb__attr_name] = SimpleNamespace() + setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) # "pady=(0,1)" is for bottom padding. It can be removed(override) when you do like "self.attr_name.grid(row=row, pady=0)" @@ -36,7 +39,7 @@ class _SettingBoxGenerator(): setting_box_frame_wrapper_fix_border2 = CTkFrame(setting_box_frame, corner_radius=0, width=0, height=0) setting_box_frame_wrapper_fix_border2.grid(row=0, column=1, sticky="ns") - self._setSettingBoxLabels(setting_box_frame_wrapper, for_var_label_text, for_var_desc_text) + self._setSettingBoxLabels(sb__attr_name, setting_box_frame_wrapper, for_var_label_text, for_var_desc_text) setting_box_item_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) setting_box_item_frame.grid(row=0, column=2, padx=0, sticky="nsew") @@ -45,7 +48,7 @@ class _SettingBoxGenerator(): return (setting_box_frame, setting_box_item_frame) - def _setSettingBoxLabels(self, setting_box_frame_wrapper, for_var_label_text, for_var_desc_text=None): + def _setSettingBoxLabels(self, sb__attr_name, setting_box_frame_wrapper, for_var_label_text, for_var_desc_text=None): setting_box_labels_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) setting_box_labels_frame.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") @@ -59,6 +62,8 @@ class _SettingBoxGenerator(): text_color=self.settings.ctm.LABELS_TEXT_COLOR ) setting_box_label.grid(row=0, column=0, padx=0, pady=0, sticky="ew") + self.config_window.sb__widgets[sb__attr_name].label_widget = setting_box_label + if for_var_desc_text is not None: setting_box_desc = CTkLabel( @@ -73,17 +78,22 @@ class _SettingBoxGenerator(): ) setting_box_desc.grid(row=1, column=0, padx=0, pady=(self.settings.uism.SB__DESC_TOP_PADY,0), sticky="ew") self.config_window.additional_widgets.append(setting_box_desc) + self.config_window.sb__widgets[sb__attr_name].desc_widget=setting_box_desc + else: + self.config_window.sb__widgets[sb__attr_name].desc_widget=None + + def createSettingBoxDropdownMenu(self, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, dropdown_menu_width=None, variable=None, dropdown_menu_values=None): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(optionmenu_attr_name, for_var_label_text, for_var_desc_text) def adjustedCommand(value): variable.set(value) command(value) - option_menu_widget = createOptionMenuBox( + (option_menu_widget, optionmenu_label_widget, optionmenu_img_widget) = createOptionMenuBox( parent_widget=setting_box_item_frame, optionmenu_bg_color=self.settings.ctm.SB__OPTIONMENU_BG_COLOR, optionmenu_hovered_bg_color=self.settings.ctm.SB__OPTIONMENU_HOVERED_BG_COLOR, @@ -104,6 +114,12 @@ class _SettingBoxGenerator(): ), ) + + self.config_window.sb__widgets[optionmenu_attr_name].optionmenu_box = option_menu_widget + self.config_window.sb__widgets[optionmenu_attr_name].optionmenu_label_widget = optionmenu_label_widget + self.config_window.sb__widgets[optionmenu_attr_name].optionmenu_img_widget = optionmenu_img_widget + + option_menu_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") setattr(self.config_window, optionmenu_attr_name, option_menu_widget) @@ -122,7 +138,7 @@ class _SettingBoxGenerator(): def createSettingBoxSwitch(self, for_var_label_text, for_var_desc_text, switch_attr_name, is_checked, command): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(switch_attr_name, for_var_label_text, for_var_desc_text) switch_widget = CTkSwitch( setting_box_item_frame, @@ -151,7 +167,7 @@ class _SettingBoxGenerator(): def createSettingBoxCheckbox(self, for_var_label_text, for_var_desc_text, checkbox_attr_name, variable, command): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(checkbox_attr_name, for_var_label_text, for_var_desc_text) checkbox_widget = CTkCheckBox( setting_box_item_frame, @@ -185,7 +201,7 @@ class _SettingBoxGenerator(): def createSettingBoxSlider(self, for_var_label_text, for_var_desc_text, slider_attr_name, slider_range, command, variable, slider_number_of_steps: Union[int, None] = None): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(slider_attr_name, for_var_label_text, for_var_desc_text) # print(self.settings.uism.SB__SLIDER_WIDTH) # print(self.settings.uism.SB__SLIDER_HEIGHT) @@ -212,7 +228,7 @@ class _SettingBoxGenerator(): def createSettingBoxProgressbarXSlider( self, - for_var_label_text, for_var_desc_text, command, + for_var_label_text, for_var_desc_text, command, progressbar_x_slider_attr_name, entry_attr_name, entry_bind__FocusOut, slider_attr_name, slider_range, progressbar_attr_name, @@ -227,7 +243,7 @@ class _SettingBoxGenerator(): ): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(progressbar_x_slider_attr_name, for_var_label_text, for_var_desc_text) ENTRY_WIDTH = self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH BAR_WIDTH = self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BAR_WIDTH @@ -320,7 +336,7 @@ class _SettingBoxGenerator(): def createSettingBoxEntry(self, for_var_label_text, for_var_desc_text, entry_attr_name, entry_width, entry_bind__Any_KeyRelease, entry_textvariable, entry_bind__FocusOut=None): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(for_var_label_text, for_var_desc_text) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(entry_attr_name, for_var_label_text, for_var_desc_text) def adjusted_command__for_entry_bind__Any_KeyRelease(e): entry_bind__Any_KeyRelease(e.widget.get()) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 026c6539..6672cee7 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -70,6 +70,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_label_text=view_variable.VAR_LABEL_MIC_ENERGY_THRESHOLD, for_var_desc_text=view_variable.VAR_DESC_MIC_ENERGY_THRESHOLD, command=slider_input_mic_energy_threshold_callback, + progressbar_x_slider_attr_name="sb__mic_energy_threshold", entry_attr_name="sb__progressbar_x_slider__entry_mic_energy_threshold", entry_variable=view_variable.VAR_MIC_ENERGY_THRESHOLD__ENTRY, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 1e97eaee..01805127 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -53,6 +53,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ for_var_label_text=view_variable.VAR_LABEL_SPEAKER_ENERGY_THRESHOLD, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_ENERGY_THRESHOLD, command=slider_input_speaker_energy_threshold_callback, + progressbar_x_slider_attr_name="sb__speaker_energy_threshold", entry_variable=view_variable.VAR_SPEAKER_ENERGY_THRESHOLD__ENTRY, entry_attr_name="sb__progressbar_x_slider__entry_speaker_energy_threshold", diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 132cd86b..0363934e 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -80,7 +80,7 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): sls__box_optionmenu_wrapper.grid(row=1, column=0, sticky="ew") sls__box_optionmenu_wrapper.grid_columnconfigure(0, weight=1) - sls__selected_language_box = createOptionMenuBox( + (sls__selected_language_box, optionmenu_label_widget, optionmenu_img_widget) = createOptionMenuBox( parent_widget=sls__box_optionmenu_wrapper, optionmenu_bg_color=settings.ctm.SLS__OPTIONMENU_BG_COLOR, optionmenu_hovered_bg_color=settings.ctm.SLS__OPTIONMENU_HOVERED_BG_COLOR, diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index e297b6a7..541da271 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -220,6 +220,8 @@ class ColorThemeManager(): self.config_window.LABELS_TEXT_COLOR = self.config_window.BASIC_TEXT_COLOR self.config_window.LABELS_DESC_TEXT_COLOR = self.DARK_500_COLOR + self.config_window.LABELS_TEXT_DISABLED_COLOR = self.DARK_600_COLOR + # Top bar self.config_window.TOP_BAR_BG_COLOR = self.DARK_850_COLOR diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index 5dff06b9..ea957867 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -78,6 +78,11 @@ def bindButtonFunctionAndColor(target_widgets, enter_color, leave_color, clicked bindButtonPressColor(target_widgets, clicked_color, enter_color) bindButtonReleaseFunction(target_widgets, buttonReleasedFunction) +def unbindEnterLEaveButtonPressButtonReleaseFunction(target_widgets): + for target_widget in target_widgets: + for event_name in ["", "", "", ""]: + target_widget.unbind(event_name) + def unbindEventFromActiveTabWidget(active_tab_widget): for event_name in ["", "", "", ""]: active_tab_widget.unbind(event_name) @@ -192,7 +197,13 @@ def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_b bindButtonReleaseFunction([optionmenu_label_wrapper, option_menu_box, optionmenu_label_widget, optionmenu_img_widget], optionmenu_clicked_command) - return option_menu_box + def unbindEventFromWidgets(): + unbindEnterLEaveButtonPressButtonReleaseFunction([optionmenu_label_wrapper, option_menu_box, optionmenu_label_widget, optionmenu_img_widget]) + + option_menu_box.unbindFunction = unbindEventFromWidgets + + + return (option_menu_box, optionmenu_label_widget, optionmenu_img_widget) def applyUiScalingAndFixTheBugScrollBar(scrollbar_widget, padx, width): From fd433754fce6bcdd85d9290cad23349659225d50 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 12 Oct 2023 10:19:03 +0900 Subject: [PATCH 263/355] =?UTF-8?q?[Update]=20Config=20Window:=20=E3=83=87?= =?UTF-8?q?=E3=83=90=E3=82=A4=E3=82=B9=E3=81=8C=E3=81=AA=E3=81=8B=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E5=A0=B4=E5=90=88=E3=80=81=E3=82=AA=E3=83=97=E3=82=B7?= =?UTF-8?q?=E3=83=A7=E3=83=B3=E3=83=A1=E3=83=8B=E3=83=A5=E3=83=BC=E3=81=AB?= =?UTF-8?q?=E3=81=AFNo=20Mic=20Device=20Detected=E3=81=AA=E3=81=A9?= =?UTF-8?q?=E3=81=AE=E8=A1=A8=E7=A4=BA=E3=81=AB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/view.py b/view.py index dec8bf38..b2c4896e 100644 --- a/view.py +++ b/view.py @@ -449,6 +449,12 @@ class View(): + if config.CHOICE_MIC_HOST == "NoHost": + self.view_variable.VAR_MIC_HOST.set("No Mic Host Detected") + + if config.CHOICE_MIC_DEVICE == "NoDevice": + self.view_variable.VAR_MIC_DEVICE.set("No Mic Device Detected") + if config.CHOICE_MIC_HOST == "NoHost" or config.CHOICE_MIC_DEVICE == "NoDevice": vrct_gui._changeConfigWindowWidgetsStatus( status="disabled", @@ -458,6 +464,9 @@ class View(): ] ) + if config.CHOICE_SPEAKER_DEVICE == "NoDevice": + self.view_variable.VAR_SPEAKER_DEVICE.set("No Speaker Device Detected") + if config.CHOICE_SPEAKER_DEVICE == "NoDevice": vrct_gui._changeConfigWindowWidgetsStatus( status="disabled", From bbb5fcce307764a725a23e3b529b68b3edf685d2 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 12 Oct 2023 10:51:37 +0900 Subject: [PATCH 264/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20README=20:=20?= =?UTF-8?q?=E8=A1=A8=E7=8F=BE=E3=81=AE=E4=BF=AE=E6=AD=A3/=E7=94=BB?= =?UTF-8?q?=E5=83=8F=E3=81=AE=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 30 +++++++++++------------------- docs/main_window.png | Bin 39236 -> 76811 bytes docs/vrct_logo.png | Bin 152106 -> 158864 bytes 3 files changed, 11 insertions(+), 19 deletions(-) diff --git a/README.md b/README.md index 4dce0670..178866c0 100644 --- a/README.md +++ b/README.md @@ -12,51 +12,43 @@ # Download & Install 好きな場所からダウンロードしてください -- [releases page](https://github.com/misyaguziya/VRCT/releases/) +- [Github.com](https://github.com/misyaguziya/VRCT/releases/) - [BOOTH.pm](https://misyaguziya.booth.pm/) ダウンロードしてexeを起動するだけです。 # What is VRCT? VRCTは話す言語の異なる人同士が会話を行うためにチャットもしくは音声の翻訳を行うことで会話をサポートするソフトウェアです。 -これらの機能はVRChat内で使用するために設計されていますがその他の用途(映画鑑賞等)でも使用されています。 +これらの機能はVRChat内で使用するために設計されています。 +※サポート対象外ですがその他の用途として映画鑑賞等でも使用されています。 VRCTはあなたの会話を以下でサポートをします。 -- 💬チャット機能 +- 💬VRChatへのチャット送信機能 - 🌐翻訳機能 - 🎙マイクの文字起こし機能 - 🔈スピーカーの文字起こし機能 -その他の機能ついて詳しくは[Documents](#Documents)まで - # Documents -初期設定や基本機能、その他の機能についても記載してあります。 -- [Documents](https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246?pvs=4) +初期設定や基本機能、その他の機能についても記載してあります。 +- [Documents Link](https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246?pvs=4) # How to Use (YouTube) [![](https://img.youtube.com/vi/mI4DQaeaAPI/0.jpg)](https://www.youtube.com/watch?v=mI4DQaeaAPI) # If you want to run it in python -1. 以下のバージョンのpythonをインストールしてください。 - ``` - python 3.11.5 - ``` - -2. install packages +1. 以下のバージョンのpythonをインストールしてください。 + `python version 3.11.5` +2. packageのインストールとVRCTの起動 ```bash ./install.bat - ``` - -3. Usage - ```bash python main.py ``` ## Author - [みしゃ(misyaguzi)](https://github.com/misyaguziya) (メイン開発) -- [しいな(Shiina_12siy)](https://github.com/ShiinaSakamoto) (メイン開発, UIデザイン, 翻訳:英語/日本語) +- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) ((UI/UX, UI多言語対応)) - [レラ](https://github.com/soumt-r) (翻訳:韓国語) -- [done_san]() (ロゴデザイン) +- [どね]() (ロゴデザイン) --- diff --git a/docs/main_window.png b/docs/main_window.png index 17a87cbf9ba6221856c65883a6fc5f263da0ea19..eb2beb52b506b35a6d59f2ca2fad502b87257e9e 100644 GIT binary patch literal 76811 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 diff --git a/docs/vrct_logo.png b/docs/vrct_logo.png index badb9be80658728e8e6978dc0eb7b0321e3386cc..11bf16b9a9f1a151552b7cc71a248637d8f3ce7a 100644 GIT binary patch literal 158864 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 z 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 z

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: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

+ +![](docs/vrct_logo.png) + +|[English](./README.md)|**日本語**| + +

+VRCTは翻訳や文字起こしでVRChatの会話をサポートするソフトウェアです。 +

+ +![](docs/main_window.png) + + +
+ +# ダウンロード&インストール +好きな場所からダウンロードしてください。 +- [Github.com](https://github.com/misyaguziya/VRCT/releases/) +- [BOOTH.pm](https://misyaguziya.booth.pm/) + +ダウンロードしてexeを起動するだけです。 + +# VRCTってなに? +VRCTは話す言語の異なる人同士が会話を行うためにチャットもしくは音声の翻訳を行うことで会話をサポートするソフトウェアです。 +これらの機能はVRChat内で使用するために設計されています。 +※サポート対象外ですがその他の用途として映画鑑賞等でも使用されています。 + +VRCTはあなたの会話を以下でサポートをします。 +- 💬VRChatへのチャット送信機能 +- 🌐翻訳機能 +- 🎙マイクの文字起こし機能 +- 🔈スピーカーの文字起こし機能 + +# ドキュメント +初期設定や基本機能、その他の機能についても記載してあります。 +- [Documents Link](https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246?pvs=4) + +# 使い方(Youtube) +[![](https://img.youtube.com/vi/mI4DQaeaAPI/0.jpg)](https://www.youtube.com/watch?v=mI4DQaeaAPI) + +# pythonで実行したい場合 +1. 以下のバージョンのpythonをインストールしてください。 + `python version 3.11.5` +2. packageのインストールとmain.pyを起動してください。 + ```bash + ./install.bat + python main.py + ``` + +## Author +- [みしゃ(misyaguzi)](https://github.com/misyaguziya) (メイン開発) +- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) ((UI/UX, UI多言語対応)) +- [レラ](https://github.com/soumt-r) (翻訳:韓国語) +- [どね]() (ロゴデザイン) + +--- + +VRCT は VRChat によって承認されておらず、VRChat または VRChat の開発もしくは管理に公式に関与する者の見解や意見が反映されたものではありません。VRChat および関連するすべての財産は 米国VRChat, Incの商標または登録商標です。 \ No newline at end of file diff --git a/README.md b/README.md index 178866c0..247615f4 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,10 @@
![](docs/vrct_logo.png) +|**English**|[日本語](./README.jp.md)|

-翻訳や文字起こしでVRChatの会話をサポートするソフトウェア +VRCT is software that supports VRChat conversations with translation and transcription.

![](docs/main_window.png) @@ -11,45 +12,45 @@
# Download & Install -好きな場所からダウンロードしてください +Download from anywhere you like. - [Github.com](https://github.com/misyaguziya/VRCT/releases/) - [BOOTH.pm](https://misyaguziya.booth.pm/) -ダウンロードしてexeを起動するだけです。 +Just download and run the exe. # What is VRCT? -VRCTは話す言語の異なる人同士が会話を行うためにチャットもしくは音声の翻訳を行うことで会話をサポートするソフトウェアです。 -これらの機能はVRChat内で使用するために設計されています。 -※サポート対象外ですがその他の用途として映画鑑賞等でも使用されています。 +VRCT is software that supports conversations between people who speak different languages by providing chat or voice translation. +These features are designed for use within VRChat. +*Although not supported, it is also used for other purposes such as watching movies. -VRCTはあなたの会話を以下でサポートをします。 -- 💬VRChatへのチャット送信機能 -- 🌐翻訳機能 -- 🎙マイクの文字起こし機能 -- 🔈スピーカーの文字起こし機能 +VRCT supports your conversations with +- 💬Send chat to VRChat +- 🌐translation +- 🎙Transcription of audio from microphone +- 🔈Transcription of audio from Speaker # Documents -初期設定や基本機能、その他の機能についても記載してあります。 +Initial setup, basic functions, and other features are also described. - [Documents Link](https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246?pvs=4) # How to Use (YouTube) [![](https://img.youtube.com/vi/mI4DQaeaAPI/0.jpg)](https://www.youtube.com/watch?v=mI4DQaeaAPI) # If you want to run it in python -1. 以下のバージョンのpythonをインストールしてください。 +1. Install the following version of python. `python version 3.11.5` -2. packageのインストールとVRCTの起動 +2. Install package and run main.py. ```bash ./install.bat python main.py ``` ## Author -- [みしゃ(misyaguzi)](https://github.com/misyaguziya) (メイン開発) -- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) ((UI/UX, UI多言語対応)) -- [レラ](https://github.com/soumt-r) (翻訳:韓国語) -- [どね]() (ロゴデザイン) +- [みしゃ(misyaguzi)](https://github.com/misyaguziya) (Main Development) +- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) ((UI/UX, UI multilingual support)) +- [レラ](https://github.com/soumt-r) (translation:Korean) +- [どね]() (Logo Design) --- -※「VRChat」は、米国VRChat, Inc.の登録商標です。 \ No newline at end of file +VRCT is not endorsed by VRChat and does not reflect the views or opinions of VRChat or anyone officially involved in producing or managing VRChat properties. VRChat and all associated properties are trademarks or registered trademarks of VRChat Inc. VRChat © VRChat Inc. \ No newline at end of file From 318f55065e78574ac6eb2d331b5165296367f1ff Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 12 Oct 2023 11:46:58 +0900 Subject: [PATCH 266/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20README=20:=202.0?= =?UTF-8?q?.0=E3=81=B8=E5=90=91=E3=81=91=E3=81=A6=20booth=E3=81=AE?= =?UTF-8?q?=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1=E3=83=B3=E3=83=88=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.txt | 125 +++-------------------------------------------------- 1 file changed, 5 insertions(+), 120 deletions(-) diff --git a/README.txt b/README.txt index 10647b74..a769afc6 100644 --- a/README.txt +++ b/README.txt @@ -4,129 +4,14 @@ # 概要 VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツールになります。 翻訳エンジンを使用してメッセージとその翻訳部分を同時に送信することができます。 -(翻訳エンジンはDeepL,Google,Bingに対応) # 使用方法 - 初期設定時 - 0. VRChatのOSCを有効にする(重要) - - (任意) - 1. DeepLのAPIを使用するためにアカウント登録し、認証キーを取得する - 2. ギアアイコンのボタンでconfigウィンドウを開く - 3. ParameterタブのDeepL Auth Keyに認証キーを記載 - 4. configウィンドウを閉じる - - 通常使用時 - 1. メッセージボックスにメッセージを記入 - 2. Enterキーを押し、メッセージを送信する - -# その他の設定 - translation チェックボックス: 翻訳の有効無効 - voice2chatbox チェックボックス : マイクの音声を文字起こししてチャットボックスに送信する - speaker2log チェックボックス : スピーカーの音声から文字起こししてログに表示する - foreground チェックボックス: 最前面表示の有効無効 - - テキストボックス - logタブ - すべてのログを表示 - sendタブ - 送信したメッセージを表示 - receiveタブ - 受信したメッセージを表示 - systemタブ - 機能についてのメッセージを表示 - - configウィンドウ - UIタブ - Transparency: ウィンドウの透過度の調整 - Appearance Theme: ウィンドウテーマを選択 - UI Scaling: UIサイズを調整 - Font Family: 表示フォントを選択 - UI Language: UIの表示言語を選択 - Translationタブ - Select Translator: 翻訳エンジンの変更 - Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択 - Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択 - Transcriptionタブ - Input Mic Host: マイクのホストAPIを選択 - Input Mic Device: マイクを選択 - Input Mic Voice Language: 入力する音声の言語 - Input Mic Energy Threshold: 音声取得のしきい値 - Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化 - Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - Input Mic Record Timeout: 音声の区切りの無音時間 - Input Mic Phase Timeout: 文字起こしする音声時間の上限 - Input Mic Max Phrases: 保留する単語の上限 - Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC) - Input Speaker Device: スピーカーを選択 - Input Speaker Voice Language: 受信する音声の言語 - Input Speaker Energy Threshold: 音声取得のしきい値 - Check threshold point: Input Speaker Energy Thresholdのしきい値を視覚化 - Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - Input Speaker Record Timeout: 音声の区切りの無音時間 - Input Speaker Phase Timeout: 文字起こしする音声時間の上限 - Input Speaker Max Phrases: 保留する単語の上限 - Parameterタブ - OSC IP address: 変更不要 - OSC port: 変更不要 - DeepL Auth key: DeepLの認証キーの設定 - Message Format: 送信するメッセージのデコレーションの設定 - [message]がメッセージボックスに記入したメッセージに置換される - [translation]が翻訳されたメッセージに置換される - 初期フォーマット:"[message]([translation])" - Othersタブ - Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする - (New!) Notification XSOverlay: XSOverlayの通知機能を有効(VR only) - - 設定の初期化 - config.jsonを削除 + ドキュメント(JP):https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246?pvs=4 # お問い合わせ -要望などはTwitterまで -https://twitter.com/misya_ai + Googleフォーム:https://t.co/lSlo4brZwm + Twitter:https://twitter.com/misya_ai + Github:https://github.com/misyaguziya/VRCT # アップデート履歴 -[2023-05-29: v0.1b] v0.1b リリース -[2023-05-30: v0.2b] -- configボタンをギアアイコンに変更 -- 詳細情報のボタンを追加 -- 翻訳機能有効無効のチェックボックスを追加 -- 最前面表示の有効無効のチェックボックスを追加 -- いくつかのバグを修正 -[2023-06-03: v0.3b] -- 全体的にUIを刷新 -- 透過機能を追加 -- テーマのLight/Dark/Systemのモードの変更機能を追加 -- UIのスケール変更機能を追加 -- フォントの変更機能を追加 -[2023-06-06: v0.4b] -- 翻訳エンジンを追加 -- 入力と出力の翻訳言語を選択できるように変更 -[2023-06-20: v1.0] -- マイクからの音声の文字起こし機能を追加 -- スピーカーからの音声の文字起こし機能を追加 -[2023-06-28: v1.1] -- いくつかのバクを修正 -- 翻訳/文字起こし言語の表記を略称からわかりやすい文字に変更 -- 文字起こしの処理の軽量化 -[2023-07-05: v1.2] -- 文字起こし精度の向上 -[2023-07-21: v1.3] -- UIの表示言語を日本語/英語を選択できる機能を追加 -- Energy Thresholdの視覚化機能を追加 -- 文字起こしの誤認識対策のため、Word Filterを追加 -- WASAPI以外のHostAPIでもマイクとして使用できるようにHostAPIを選択できる機能を追加 -- メッセージ送信後に書き込んだメッセージを空にするか選択できる機能を追加 -- バグ対策のため、translation/voice2chatbox/speaker2log/foregroundは起動時はOFFになるように変更 -- バグ対策のため、スピーカーについて既定デバイス以外を選択した時にERRORが出るように変更 -- 半角入力時に一部の文字が書き込めないバグを修正 -[2023-07-22: v1.3.1] -- UIの表示言語選択に韓国語を追加 -[2023-07-30: v1.3.2] -- 試験的にXSOverlayへの通知機能を追加 -- checkbox ONの状態でもConfigを開けるように変更 -- 文字起こし言語の表示を修正 -- いくつかのバグを修正 - -# 注意事項 -再配布とかはやめてね \ No newline at end of file +[2023-10-21: 2.0.0] v2.0.0 リリース \ No newline at end of file From a9b137be0290673a94d8e21faceb03f4cb4f3352 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 12 Oct 2023 11:47:39 +0900 Subject: [PATCH 267/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20README=20:=20?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=81=AE=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.jp.md | 8 ++++++-- README.md | 8 ++++++-- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/README.jp.md b/README.jp.md index 138da5d8..b123170b 100644 --- a/README.jp.md +++ b/README.jp.md @@ -2,7 +2,7 @@ ![](docs/vrct_logo.png) -|[English](./README.md)|**日本語**| +| [English](./README.md) | **日本語** |

VRCTは翻訳や文字起こしでVRChatの会話をサポートするソフトウェアです。 @@ -36,8 +36,12 @@ VRCTはあなたの会話を以下でサポートをします。 - [Documents Link](https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246?pvs=4) # 使い方(Youtube) +
+ [![](https://img.youtube.com/vi/mI4DQaeaAPI/0.jpg)](https://www.youtube.com/watch?v=mI4DQaeaAPI) +
+ # pythonで実行したい場合 1. 以下のバージョンのpythonをインストールしてください。 `python version 3.11.5` @@ -49,7 +53,7 @@ VRCTはあなたの会話を以下でサポートをします。 ## Author - [みしゃ(misyaguzi)](https://github.com/misyaguziya) (メイン開発) -- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) ((UI/UX, UI多言語対応)) +- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) (UI/UX, UI多言語対応) - [レラ](https://github.com/soumt-r) (翻訳:韓国語) - [どね]() (ロゴデザイン) diff --git a/README.md b/README.md index 247615f4..35a427e2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@
![](docs/vrct_logo.png) -|**English**|[日本語](./README.jp.md)| +| **English** | [日本語](./README.jp.md) |

VRCT is software that supports VRChat conversations with translation and transcription. @@ -34,8 +34,12 @@ Initial setup, basic functions, and other features are also described. - [Documents Link](https://mzsoftware.notion.site/VRCT-Documents-be79b7a165f64442ad8f326d86c22246?pvs=4) # How to Use (YouTube) +
+ [![](https://img.youtube.com/vi/mI4DQaeaAPI/0.jpg)](https://www.youtube.com/watch?v=mI4DQaeaAPI) +
+ # If you want to run it in python 1. Install the following version of python. `python version 3.11.5` @@ -47,7 +51,7 @@ Initial setup, basic functions, and other features are also described. ## Author - [みしゃ(misyaguzi)](https://github.com/misyaguziya) (Main Development) -- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) ((UI/UX, UI multilingual support)) +- [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) (UI/UX, UI multilingual support) - [レラ](https://github.com/soumt-r) (translation:Korean) - [どね]() (Logo Design) From 17b33d57df974139fb9ebf3daea91bac96e15ca4 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 12 Oct 2023 11:50:39 +0900 Subject: [PATCH 268/355] =?UTF-8?q?[Update]=20Config=20Window:=20Mic/Speak?= =?UTF-8?q?er=20Dynamic=20Energy=20Threshold=E3=81=AE=E3=82=AA=E3=83=B3?= =?UTF-8?q?=E3=82=AA=E3=83=95=E3=81=A7=E3=80=81Mic/Speaker=20Energy=20Thre?= =?UTF-8?q?shold=E3=81=AEWidget=E3=82=92=E6=8A=98=E3=82=8A=E3=81=9F?= =?UTF-8?q?=E3=81=9F=E3=82=93=E3=81=A0=E3=82=8A=E9=96=8B=E3=81=84=E3=81=9F?= =?UTF-8?q?=E3=82=8A=E3=80=82=E5=88=9D=E6=9C=9F=E5=80=A4=E3=81=AB=E5=90=88?= =?UTF-8?q?=E3=82=8F=E3=81=9B=E3=81=9F=E5=8B=95=E4=BD=9C=E3=82=82=E5=90=AB?= =?UTF-8?q?=E3=82=81=E3=81=A6=E5=AE=9F=E8=A3=85=E3=80=82=20[bugfix]=20Spea?= =?UTF-8?q?ker=20Dynamic=20Energy=20Threshold=E3=81=ABMic=E3=81=AEctk=20va?= =?UTF-8?q?riable=E3=82=92=E8=A8=AD=E5=AE=9A=E3=81=97=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 9 +++++++ view.py | 25 +++++++++++++++++++ .../_SettingBoxGenerator.py | 5 ++-- .../createSettingBox_Mic.py | 22 ++++++++-------- .../createSettingBox_Speaker.py | 22 ++++++++-------- 5 files changed, 56 insertions(+), 27 deletions(-) diff --git a/controller.py b/controller.py index ecbe1376..d0384d8d 100644 --- a/controller.py +++ b/controller.py @@ -410,6 +410,10 @@ def callbackSetMicEnergyThreshold(value): def callbackSetMicDynamicEnergyThreshold(value): print("callbackSetMicDynamicEnergyThreshold", value) config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value + if config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD is True: + view.closeMicEnergyThresholdWidget() + else: + view.openMicEnergyThresholdWidget() def setProgressBarMicEnergy(energy): view.updateSetProgressBar_MicEnergy(energy) @@ -503,6 +507,11 @@ def callbackSetSpeakerEnergyThreshold(value): def callbackSetSpeakerDynamicEnergyThreshold(value): print("callbackSetSpeakerDynamicEnergyThreshold", value) config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value + if config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD is True: + view.closeSpeakerEnergyThresholdWidget() + else: + view.openSpeakerEnergyThresholdWidget() + def setProgressBarSpeakerEnergy(energy): view.updateSetProgressBar_SpeakerEnergy(energy) diff --git a/view.py b/view.py index b2c4896e..22f775ff 100644 --- a/view.py +++ b/view.py @@ -476,6 +476,13 @@ class View(): ) + if config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD is True: + self.closeMicEnergyThresholdWidget() + + if config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD is True: + self.closeSpeakerEnergyThresholdWidget() + + # Insert sample conversation for testing. # self._insertSampleConversationToTextbox() @@ -692,6 +699,24 @@ class View(): def setWidgetsStatus_ConfigWindowCompactModeSwitch_Normal(): vrct_gui.config_window.setting_box_compact_mode_switch_box.configure(state="normal") + @staticmethod + def openMicEnergyThresholdWidget(): + vrct_gui.config_window.sb__mic_dynamic_energy_threshold.grid(pady=0) + vrct_gui.config_window.sb__mic_energy_threshold.grid() + @staticmethod + def closeMicEnergyThresholdWidget(): + vrct_gui.config_window.sb__mic_dynamic_energy_threshold.grid(pady=(0,1)) + vrct_gui.config_window.sb__mic_energy_threshold.grid_remove() + + @staticmethod + def openSpeakerEnergyThresholdWidget(): + vrct_gui.config_window.sb__speaker_dynamic_energy_threshold.grid(pady=0) + vrct_gui.config_window.sb__speaker_energy_threshold.grid() + @staticmethod + def closeSpeakerEnergyThresholdWidget(): + vrct_gui.config_window.sb__speaker_dynamic_energy_threshold.grid(pady=(0,1)) + vrct_gui.config_window.sb__speaker_energy_threshold.grid_remove() + @staticmethod def setWidgetsStatus_ThresholdCheckButton_Disabled(): vrct_gui._changeConfigWindowWidgetsStatus( diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 6a92002a..a8fcf87d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -137,7 +137,7 @@ class _SettingBoxGenerator(): - def createSettingBoxSwitch(self, for_var_label_text, for_var_desc_text, switch_attr_name, is_checked, command): + def createSettingBoxSwitch(self, for_var_label_text, for_var_desc_text, switch_attr_name, variable, command): (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(switch_attr_name, for_var_label_text, for_var_desc_text) switch_widget = CTkSwitch( @@ -151,6 +151,7 @@ class _SettingBoxGenerator(): switch_width=self.settings.uism.SB__SWITCH_BOX_WIDTH, onvalue=True, offvalue=False, + variable=variable, command=command, fg_color=self.settings.ctm.SB__SWITCH_BOX_BG_COLOR, # bg_color="red", @@ -158,8 +159,6 @@ class _SettingBoxGenerator(): ) setattr(self.config_window, switch_attr_name, switch_widget) - switch_widget.select() if is_checked else switch_widget.deselect() - switch_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") return setting_box_frame diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 6672cee7..d4a071b2 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -5,7 +5,7 @@ from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_variable): sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu - createSettingBoxCheckbox = sbg.createSettingBoxCheckbox + createSettingBoxSwitch = sbg.createSettingBoxSwitch createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider createSettingBoxEntry = sbg.createSettingBoxEntry @@ -65,6 +65,15 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari config_window.sb__mic_device.grid(row=row) row+=1 + config_window.sb__mic_dynamic_energy_threshold = createSettingBoxSwitch( + for_var_label_text=view_variable.VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD, + for_var_desc_text=view_variable.VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD, + switch_attr_name="sb__checkbox_mic_dynamic_energy_threshold", + command=lambda: checkbox_input_mic_dynamic_energy_threshold_callback(config_window.sb__checkbox_mic_dynamic_energy_threshold), + variable=view_variable.VAR_MIC_DYNAMIC_ENERGY_THRESHOLD + ) + config_window.sb__mic_dynamic_energy_threshold.grid(row=row, pady=0) + row+=1 config_window.sb__mic_energy_threshold = createSettingBoxProgressbarXSlider( for_var_label_text=view_variable.VAR_LABEL_MIC_ENERGY_THRESHOLD, @@ -92,17 +101,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari config_window.sb__mic_energy_threshold.grid(row=row) row+=1 - # Mic Dynamic Energy Thresholdも上に引っ付ける予定 - config_window.sb__mic_dynamic_energy_threshold = createSettingBoxCheckbox( - for_var_label_text=view_variable.VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD, - for_var_desc_text=view_variable.VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD, - checkbox_attr_name="sb__checkbox_mic_dynamic_energy_threshold", - command=lambda: checkbox_input_mic_dynamic_energy_threshold_callback(config_window.sb__checkbox_mic_dynamic_energy_threshold), - variable=view_variable.VAR_MIC_DYNAMIC_ENERGY_THRESHOLD - ) - config_window.sb__mic_dynamic_energy_threshold.grid(row=row) - row+=1 - # 以下3つも一つの項目にまとめるかもしれない config_window.sb__mic_record_timeout = createSettingBoxEntry( diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 01805127..e557461e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -5,7 +5,7 @@ from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_variable): sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu - createSettingBoxCheckbox = sbg.createSettingBoxCheckbox + createSettingBoxSwitch = sbg.createSettingBoxSwitch createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider createSettingBoxEntry = sbg.createSettingBoxEntry @@ -48,6 +48,15 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ config_window.sb__speaker_device.grid(row=row) row+=1 + config_window.sb__speaker_dynamic_energy_threshold = createSettingBoxSwitch( + for_var_label_text=view_variable.VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, + for_var_desc_text=view_variable.VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, + switch_attr_name="sb__checkbox_speaker_dynamic_energy_threshold", + command=lambda: checkbox_input_speaker_dynamic_energy_threshold_callback(config_window.sb__checkbox_speaker_dynamic_energy_threshold), + variable=view_variable.VAR_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, + ) + config_window.sb__speaker_dynamic_energy_threshold.grid(row=row, pady=0) + row+=1 config_window.sb__speaker_energy_threshold = createSettingBoxProgressbarXSlider( for_var_label_text=view_variable.VAR_LABEL_SPEAKER_ENERGY_THRESHOLD, @@ -75,17 +84,6 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ config_window.sb__speaker_energy_threshold.grid(row=row) row+=1 - # Speaker Dynamic Energy Thresholdも上に引っ付ける予定 - config_window.sb__speaker_dynamic_energy_threshold = createSettingBoxCheckbox( - for_var_label_text=view_variable.VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, - for_var_desc_text=view_variable.VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, - checkbox_attr_name="sb__checkbox_speaker_dynamic_energy_threshold", - command=lambda: checkbox_input_speaker_dynamic_energy_threshold_callback(config_window.sb__checkbox_speaker_dynamic_energy_threshold), - variable=view_variable.VAR_MIC_DYNAMIC_ENERGY_THRESHOLD, - ) - config_window.sb__speaker_dynamic_energy_threshold.grid(row=row) - row+=1 - # 以下3つも一つの項目にまとめるかもしれない config_window.sb__speaker_record_timeout = createSettingBoxEntry( From 46e406bfc4247444ba004d607a2c92a7e0d32f13 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 12 Oct 2023 12:22:35 +0900 Subject: [PATCH 269/355] =?UTF-8?q?[Update]=20Config=20Window:=20=E3=82=B9?= =?UTF-8?q?=E3=83=AC=E3=83=83=E3=82=B7=E3=83=A7=E3=83=AB=E3=83=89=E3=83=81?= =?UTF-8?q?=E3=82=A7=E3=83=83=E3=82=AF=E5=91=A8=E3=82=8A=E3=81=AE=E6=96=87?= =?UTF-8?q?=E8=A8=80=E5=A4=89=E6=9B=B4=EF=BC=88=E9=96=93=E3=81=AB=E5=90=88?= =?UTF-8?q?=E3=82=8F=E3=81=9B=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 20 ++++++++++---------- locales/ja.yml | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index ae3896d6..b5f5a01c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -56,12 +56,12 @@ config_window: label: Mic Host/Driver mic_device: label: Mic Device - mic_energy_threshold: - label: Mic Energy Threshold - desc: Slider to modify the threshold for activating voice input. Press the microphone button to initiate input and speak, allowing you to adjust it while monitoring the actual volume. mic_dynamic_energy_threshold: - label: Mic Dynamic Energy Threshold - desc: When this feature is selected, it will automatically adjust in a way that works well, based on the set Mic Energy Threshold. + label: Mic Energy Threshold (Auto) + desc: Automatically determine mic input sensitivity. + mic_energy_threshold: + label: Mic Energy Threshold (Manual) + desc: Slider to modify the threshold for activating voice input. Press the microphone button to initiate input and speak, allowing you to adjust it while monitoring the actual volume. mic_record_timeout: label: Mic Record Timeout mic_phrase_timeout: @@ -75,12 +75,12 @@ config_window: speaker_device: label: Speaker Device - speaker_energy_threshold: - label: Speaker Energy Threshold - desc: Slider to modify the threshold for activating voice input. Press the headphones mark button to start input and speak something, so you can adjust it while monitoring the actual volume. speaker_dynamic_energy_threshold: - label: Speaker Dynamic Energy Threshold - desc: When this feature is selected, it will automatically adjust in a way that works well, based on the set Speaker Energy Threshold. + label: Speaker Energy Threshold (Auto) + desc: Automatically determine speaker input sensitivity. + speaker_energy_threshold: + label: Speaker Energy Threshold (Manual) + desc: Slider to modify the threshold for activating voice input. Press the headphones mark button to start input and speak something, so you can adjust it while monitoring the actual volume. speaker_record_timeout: label: Speaker Record Timeout speaker_phrase_timeout: diff --git a/locales/ja.yml b/locales/ja.yml index 1a773f01..ec547ebf 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -58,12 +58,12 @@ config_window: label: マイク(ホスト/ドライバー) mic_device: label: マイク (デバイス) - mic_energy_threshold: - label: 音声取得のしきい値 - desc: スライダーを調整してしきい値を決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。 mic_dynamic_energy_threshold: - label: 音声取得のしきい値の自動調整 - desc: 有効にすると、設定されたしきい値に応じて、ある程度自動的に調節されます。 + label: マイク入力感度の調整(自動) + desc: マイクの入力感度が自動的に調節されます。 + mic_energy_threshold: + label: マイク入力感度の調整(手動) + desc: スライダーを調整してしきい値を決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。 mic_record_timeout: label: マイク音声の区切りの無音時間 mic_phrase_timeout: @@ -77,12 +77,12 @@ config_window: speaker_device: label: スピーカー(デバイス) - speaker_energy_threshold: - label: 音声取得のしきい値 - desc: スライダーを調整してしきい値を決められます。スピーカーのアイコンを押すと、設定されたデバイスから音を聞き取り、音量を確認しながら調節できます。 speaker_dynamic_energy_threshold: - label: 音声取得のしきい値の自動調整 - desc: 有効にすると、設定されたしきい値に応じて、ある程度自動的に調節されます。 + label: スピーカー入力感度の調整(自動) + desc: スピーカーの入力感度が自動的に調節されます。 + speaker_energy_threshold: + label: スピーカー入力感度の調整(手動) + desc: スライダーを調整してしきい値を決められます。スピーカーのアイコンを押すと、設定されたデバイスから音を聞き取り、音量を確認しながら調節できます。 speaker_record_timeout: label: スピーカー音声の区切りの無音時間 speaker_phrase_timeout: From 01ade68d4e57a4ab807cda1b4112f89dde7f8b38 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 12 Oct 2023 13:41:14 +0900 Subject: [PATCH 270/355] =?UTF-8?q?[Update]=20Config=20Window:=20=E3=82=B9?= =?UTF-8?q?=E3=83=AC=E3=83=83=E3=82=B7=E3=83=A7=E3=83=AB=E3=83=89=E3=83=81?= =?UTF-8?q?=E3=82=A7=E3=83=83=E3=82=AF=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AE?= =?UTF-8?q?Disabled=E5=87=A6=E7=90=86=E8=BF=BD=E5=8A=A0=E3=82=84=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=80=82Disabled=E3=81=AA=E3=83=9C=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=81=9D=E3=81=AE=E3=82=82=E3=81=AE=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 21 ++++++++++++++++-- .../_SettingBoxGenerator.py | 21 ++++++++++++++++++ .../createSettingBox_Mic.py | 4 +++- .../createSettingBox_Speaker.py | 4 +++- vrct_gui/ui_utils/ui_utils.py | 22 ++++++++++--------- 5 files changed, 58 insertions(+), 14 deletions(-) diff --git a/view.py b/view.py index 22f775ff..7e904143 100644 --- a/view.py +++ b/view.py @@ -463,17 +463,18 @@ class View(): "sb__optionmenu_mic_device", ] ) + self.replaceMicThresholdCheckButton_Disabled() if config.CHOICE_SPEAKER_DEVICE == "NoDevice": self.view_variable.VAR_SPEAKER_DEVICE.set("No Speaker Device Detected") - - if config.CHOICE_SPEAKER_DEVICE == "NoDevice": vrct_gui._changeConfigWindowWidgetsStatus( status="disabled", target_names=[ "sb__optionmenu_speaker_device", ] ) + self.replaceSpeakerThresholdCheckButton_Disabled() + if config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD is True: @@ -740,11 +741,19 @@ class View(): @staticmethod def replaceMicThresholdCheckButton_Active(): vrct_gui.config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.grid_remove() + vrct_gui.config_window.sb__progressbar_x_slider__disabled_button_mic_energy_threshold.grid_remove() vrct_gui.config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold.grid() + @staticmethod + def replaceMicThresholdCheckButton_Disabled(): + vrct_gui.config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.grid_remove() + vrct_gui.config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold.grid_remove() + vrct_gui.config_window.sb__progressbar_x_slider__disabled_button_mic_energy_threshold.grid() + @staticmethod def replaceMicThresholdCheckButton_Passive(): vrct_gui.config_window.sb__progressbar_x_slider__active_button_mic_energy_threshold.grid_remove() + vrct_gui.config_window.sb__progressbar_x_slider__disabled_button_mic_energy_threshold.grid_remove() vrct_gui.config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.grid() @@ -752,11 +761,19 @@ class View(): @staticmethod def replaceSpeakerThresholdCheckButton_Active(): vrct_gui.config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.grid_remove() + vrct_gui.config_window.sb__progressbar_x_slider__disabled_button_speaker_energy_threshold.grid_remove() vrct_gui.config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold.grid() + @staticmethod + def replaceSpeakerThresholdCheckButton_Disabled(): + vrct_gui.config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.grid_remove() + vrct_gui.config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold.grid_remove() + vrct_gui.config_window.sb__progressbar_x_slider__disabled_button_speaker_energy_threshold.grid() + @staticmethod def replaceSpeakerThresholdCheckButton_Passive(): vrct_gui.config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold.grid_remove() + vrct_gui.config_window.sb__progressbar_x_slider__disabled_button_speaker_energy_threshold.grid_remove() vrct_gui.config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.grid() diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index a8fcf87d..29fbba04 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -233,6 +233,7 @@ class _SettingBoxGenerator(): progressbar_attr_name, passive_button_attr_name, passive_button_command, active_button_attr_name, active_button_command, + disabled_button_attr_name, disabled_button_image_file, button_image_file, entry_variable, @@ -316,16 +317,23 @@ class _SettingBoxGenerator(): passive_button_wrapper = self._createPassiveButtonForProgressbarXSlider(setting_box_item_frame, passive_button_command, button_image_file) setattr(self.config_window, passive_button_attr_name, passive_button_wrapper) + disabled_button_wrapper = self._createDisabledButtonForProgressbarXSlider(setting_box_item_frame, disabled_button_image_file) + setattr(self.config_window, disabled_button_attr_name, disabled_button_wrapper) + active_button_wrapper = self._createActiveButtonForProgressbarXSlider(setting_box_item_frame, active_button_command, button_image_file) setattr(self.config_window, active_button_attr_name, active_button_wrapper) passive_button_wrapper.grid(row=1, column=SETTING_BOX_COLUMN, padx=(0,BUTTON_PADDING), sticky="e") passive_button_wrapper.configure(corner_radius=int(getLatestWidth(passive_button_wrapper)/2)) + disabled_button_wrapper.grid(row=1, column=SETTING_BOX_COLUMN, padx=(0,BUTTON_PADDING), sticky="e") + disabled_button_wrapper.configure(corner_radius=int(getLatestWidth(passive_button_wrapper)/2)) + active_button_wrapper.grid(row=1, column=SETTING_BOX_COLUMN, padx=(0,BUTTON_PADDING), sticky="e") active_button_wrapper.configure(corner_radius=int(getLatestWidth(passive_button_wrapper)/2)) passive_button_wrapper.grid_remove() + disabled_button_wrapper.grid_remove() active_button_wrapper.grid_remove() passive_button_wrapper.grid() @@ -484,4 +492,17 @@ class _SettingBoxGenerator(): button_ipadxy=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY, button_command=button_command, ) + return button_wrapper + + + + def _createDisabledButtonForProgressbarXSlider(self, setting_box_progressbar_x_slider_frame, button_image_file): + button_wrapper = createButtonWithImage( + parent_widget=setting_box_progressbar_x_slider_frame, + button_fg_color=self.settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR, + button_image_file=button_image_file, + button_image_size=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE, + button_ipadxy=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY, + no_bind=True, + ) return button_wrapper \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index d4a071b2..9ad30f57 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -96,7 +96,9 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari passive_button_command=lambda _e: checkbox_input_mic_threshold_check_callback(True), active_button_attr_name="sb__progressbar_x_slider__active_button_mic_energy_threshold", active_button_command=lambda _e: checkbox_input_mic_threshold_check_callback(False), - button_image_file=settings.image_file.MIC_ICON + button_image_file=settings.image_file.MIC_ICON, + disabled_button_attr_name="sb__progressbar_x_slider__disabled_button_mic_energy_threshold", + disabled_button_image_file=settings.image_file.MIC_ICON_DISABLED, ) config_window.sb__mic_energy_threshold.grid(row=row) row+=1 diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index e557461e..9886ac57 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -79,7 +79,9 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ passive_button_command=lambda _e: checkbox_input_speaker_threshold_check_callback(True), active_button_attr_name="sb__progressbar_x_slider__active_button_speaker_energy_threshold", active_button_command=lambda _e: checkbox_input_speaker_threshold_check_callback(False), - button_image_file=settings.image_file.HEADPHONES_ICON + button_image_file=settings.image_file.HEADPHONES_ICON, + disabled_button_attr_name="sb__progressbar_x_slider__disabled_button_speaker_energy_threshold", + disabled_button_image_file=settings.image_file.HEADPHONES_ICON_DISABLED, ) config_window.sb__speaker_energy_threshold.grid(row=row) row+=1 diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index ea957867..8f562d32 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -126,17 +126,19 @@ def switchTabsColor(target_widget, tab_buttons, active_bg_color, active_text_col -def createButtonWithImage(parent_widget, button_fg_color, button_enter_color, button_clicked_color, button_image_file, button_image_size, button_ipadxy, button_command, corner_radius: int = 0): - button_wrapper = CTkFrame(parent_widget, corner_radius=corner_radius, fg_color=button_fg_color, height=0, width=0, cursor="hand2") +def createButtonWithImage(parent_widget, button_image_size, button_ipadxy, button_fg_color, button_enter_color=None, button_clicked_color=None, button_image_file=None, button_command=None, corner_radius:int=0, no_bind:bool=False): + button_wrapper = CTkFrame(parent_widget, corner_radius=corner_radius, fg_color=button_fg_color, height=0, width=0) - button_widget = CTkLabel( - button_wrapper, - text=None, - height=0, - image=CTkImage((button_image_file),size=(button_image_size,button_image_size)), - ) - button_widget.grid(row=0, column=0, padx=button_ipadxy, pady=button_ipadxy) + button_widget = CTkLabel( + button_wrapper, + text=None, + height=0, + image=CTkImage((button_image_file),size=(button_image_size,button_image_size)), + ) + button_widget.grid(row=0, column=0, padx=button_ipadxy, pady=button_ipadxy) + if no_bind is False: + button_wrapper.configure(cursor="hand2") bindButtonFunctionAndColor( target_widgets=[button_wrapper, button_widget], enter_color=button_enter_color, @@ -145,7 +147,7 @@ def createButtonWithImage(parent_widget, button_fg_color, button_enter_color, bu buttonReleasedFunction=button_command, ) - return button_wrapper + return button_wrapper def createOptionMenuBox(parent_widget, optionmenu_bg_color, optionmenu_hovered_bg_color, optionmenu_clicked_bg_color, optionmenu_ipadx, optionmenu_ipady, variable, font_family, font_size, text_color, image_file, image_size, optionmenu_clicked_command, optionmenu_position=None, optionmenu_padx_between_img=0, optionmenu_min_height=None, optionmenu_min_width=None, setattr_widget=None, image_widget_attr_name=None): From 2d43061f8ea3ca8db88cf611c99fffd87e784df4 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 12 Oct 2023 14:27:12 +0900 Subject: [PATCH 271/355] =?UTF-8?q?[bugfix]=20Config=20Window:=20=E3=82=B9?= =?UTF-8?q?=E3=83=AC=E3=83=83=E3=82=B7=E3=83=A7=E3=83=AB=E3=83=89=E3=83=81?= =?UTF-8?q?=E3=82=A7=E3=83=83=E3=82=AF=E3=83=9C=E3=82=BF=E3=83=B3Disabled?= =?UTF-8?q?=E5=87=A6=E7=90=86=E3=81=8C=E3=80=81=E3=82=B3=E3=83=B3=E3=83=91?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=83=A2=E3=83=BC=E3=83=89=E3=82=84=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E7=94=BB=E9=9D=A2=E9=96=89=E3=81=98=E3=82=8B=E9=9A=9B?= =?UTF-8?q?=E3=81=AB=E8=A7=A3=E9=99=A4=E3=81=95=E3=82=8C=E3=81=A6=E3=81=97?= =?UTF-8?q?=E3=81=BE=E3=81=86=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 20 ++++++++++---------- view.py | 13 +++++++++++++ 2 files changed, 23 insertions(+), 10 deletions(-) diff --git a/controller.py b/controller.py index d0384d8d..b17eaa84 100644 --- a/controller.py +++ b/controller.py @@ -300,9 +300,9 @@ def callbackOpenConfigWindow(): def callbackCloseConfigWindow(): model.stopCheckMicEnergy() model.stopCheckSpeakerEnergy() - view.replaceMicThresholdCheckButton_Passive() + view.initMicThresholdCheckButton() # view.initProgressBar_MicEnergy() # ProgressBarに0をセットしたい - view.replaceSpeakerThresholdCheckButton_Passive() + view.initSpeakerThresholdCheckButton() # view.initProgressBar_SpeakerEnergy() # ProgressBarに0をセットしたい if config.ENABLE_TRANSCRIPTION_SEND is True: @@ -319,18 +319,18 @@ def callbackCloseConfigWindow(): def callbackEnableConfigWindowCompactMode(): config.IS_CONFIG_WINDOW_COMPACT_MODE = True model.stopCheckMicEnergy() - view.replaceMicThresholdCheckButton_Passive() + view.initMicThresholdCheckButton() model.stopCheckSpeakerEnergy() - view.replaceSpeakerThresholdCheckButton_Passive() + view.initSpeakerThresholdCheckButton() view.enableConfigWindowCompactMode() def callbackDisableConfigWindowCompactMode(): config.IS_CONFIG_WINDOW_COMPACT_MODE = False model.stopCheckMicEnergy() - view.replaceMicThresholdCheckButton_Passive() + view.initMicThresholdCheckButton() model.stopCheckSpeakerEnergy() - view.replaceSpeakerThresholdCheckButton_Passive() + view.initSpeakerThresholdCheckButton() view.disableConfigWindowCompactMode() @@ -421,12 +421,12 @@ def setProgressBarMicEnergy(energy): def callbackCheckMicThreshold(is_turned_on): print("callbackCheckMicThreshold", is_turned_on) if is_turned_on is True: - view.setWidgetsStatus_ThresholdCheckButton_Disabled() + view.replaceMicThresholdCheckButton_Disabled() model.startCheckMicEnergy(setProgressBarMicEnergy) view.replaceMicThresholdCheckButton_Active() view.setWidgetsStatus_ThresholdCheckButton_Normal() else: - view.setWidgetsStatus_ThresholdCheckButton_Disabled() + view.replaceMicThresholdCheckButton_Disabled() model.stopCheckMicEnergy() view.replaceMicThresholdCheckButton_Passive() view.setWidgetsStatus_ThresholdCheckButton_Normal() @@ -519,12 +519,12 @@ def setProgressBarSpeakerEnergy(energy): def callbackCheckSpeakerThreshold(is_turned_on): print("callbackCheckSpeakerThreshold", is_turned_on) if is_turned_on is True: - view.setWidgetsStatus_ThresholdCheckButton_Disabled() + view.replaceSpeakerThresholdCheckButton_Disabled() model.startCheckSpeakerEnergy(setProgressBarSpeakerEnergy) view.replaceSpeakerThresholdCheckButton_Active() view.setWidgetsStatus_ThresholdCheckButton_Normal() else: - view.setWidgetsStatus_ThresholdCheckButton_Disabled() + view.replaceSpeakerThresholdCheckButton_Disabled() model.stopCheckSpeakerEnergy() view.replaceSpeakerThresholdCheckButton_Passive() view.setWidgetsStatus_ThresholdCheckButton_Normal() diff --git a/view.py b/view.py index 7e904143..a83b6d66 100644 --- a/view.py +++ b/view.py @@ -738,6 +738,13 @@ class View(): ] ) + + def initMicThresholdCheckButton(self): + if config.CHOICE_MIC_HOST == "NoHost" or config.CHOICE_MIC_DEVICE == "NoDevice": + self.replaceMicThresholdCheckButton_Disabled() + else: + self.replaceMicThresholdCheckButton_Passive() + @staticmethod def replaceMicThresholdCheckButton_Active(): vrct_gui.config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.grid_remove() @@ -758,6 +765,12 @@ class View(): + def initSpeakerThresholdCheckButton(self): + if config.CHOICE_SPEAKER_DEVICE == "NoDevice": + self.replaceSpeakerThresholdCheckButton_Disabled() + else: + self.replaceSpeakerThresholdCheckButton_Passive() + @staticmethod def replaceSpeakerThresholdCheckButton_Active(): vrct_gui.config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.grid_remove() From 07ffb89a5de6060cae6d0a0c0c628e48e8a6625b Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 12 Oct 2023 14:59:21 +0900 Subject: [PATCH 272/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20ener?= =?UTF-8?q?gy=E3=81=8C=E3=82=B9=E3=83=88=E3=83=83=E3=83=97=E3=81=97?= =?UTF-8?q?=E3=81=9F=E5=A0=B4=E5=90=88=E3=81=AB=E5=BF=85=E3=81=9A0?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E7=B5=82?= =?UTF-8?q?=E4=BA=86=E6=99=82=E9=96=A2=E6=95=B0=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 4 ++-- model.py | 13 ++++++++----- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/controller.py b/controller.py index ecbe1376..6cff32c1 100644 --- a/controller.py +++ b/controller.py @@ -418,7 +418,7 @@ def callbackCheckMicThreshold(is_turned_on): print("callbackCheckMicThreshold", is_turned_on) if is_turned_on is True: view.setWidgetsStatus_ThresholdCheckButton_Disabled() - model.startCheckMicEnergy(setProgressBarMicEnergy) + model.startCheckMicEnergy(setProgressBarMicEnergy, view.initProgressBar_MicEnergy) view.replaceMicThresholdCheckButton_Active() view.setWidgetsStatus_ThresholdCheckButton_Normal() else: @@ -511,7 +511,7 @@ def callbackCheckSpeakerThreshold(is_turned_on): print("callbackCheckSpeakerThreshold", is_turned_on) if is_turned_on is True: view.setWidgetsStatus_ThresholdCheckButton_Disabled() - model.startCheckSpeakerEnergy(setProgressBarSpeakerEnergy) + model.startCheckSpeakerEnergy(setProgressBarSpeakerEnergy, view.initProgressBar_SpeakerEnergy) view.replaceSpeakerThresholdCheckButton_Active() view.setWidgetsStatus_ThresholdCheckButton_Normal() else: diff --git a/model.py b/model.py index 292c387c..04ec62d3 100644 --- a/model.py +++ b/model.py @@ -24,9 +24,10 @@ from models.transcription.transcription_languages import transcription_lang from config import config class threadFnc(Thread): - def __init__(self, fnc, daemon=True, *args, **kwargs): + def __init__(self, fnc, end_fnc=None, daemon=True, *args, **kwargs): super(threadFnc, self).__init__(daemon=daemon, *args, **kwargs) self.fnc = fnc + self.end_fnc = end_fnc self._stop = Event() def stop(self): self._stop.set() @@ -35,6 +36,8 @@ class threadFnc(Thread): def run(self): while True: if self.stopped(): + if callable(self.end_fnc): + self.end_fnc() return self.fnc(*self._args, **self._kwargs) @@ -331,7 +334,7 @@ class Model: self.mic_audio_recorder.stop() self.mic_audio_recorder = None - def startCheckMicEnergy(self, fnc): + def startCheckMicEnergy(self, fnc, end_fnc): if config.CHOICE_MIC_HOST == "NoHost" or config.CHOICE_MIC_DEVICE == "NoDevice": return @@ -348,7 +351,7 @@ class Model: mic_device = [device for device in getInputDevices()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] self.mic_energy_recorder = SelectedMicEnergyRecorder(mic_device) self.mic_energy_recorder.recordIntoQueue(mic_energy_queue) - self.mic_energy_plot_progressbar = threadFnc(sendMicEnergy) + self.mic_energy_plot_progressbar = threadFnc(sendMicEnergy, end_fnc=end_fnc) self.mic_energy_plot_progressbar.daemon = True self.mic_energy_plot_progressbar.start() @@ -404,7 +407,7 @@ class Model: self.spk_audio_recorder.stop() self.spk_audio_recorder = None - def startCheckSpeakerEnergy(self, fnc): + def startCheckSpeakerEnergy(self, fnc, end_fnc): if config.CHOICE_SPEAKER_DEVICE == "NoDevice": return @@ -421,7 +424,7 @@ class Model: speaker_energy_queue = Queue() self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(speaker_device) self.speaker_energy_recorder.recordIntoQueue(speaker_energy_queue) - self.speaker_energy_plot_progressbar = threadFnc(sendSpeakerEnergy) + self.speaker_energy_plot_progressbar = threadFnc(sendSpeakerEnergy, end_fnc=end_fnc) self.speaker_energy_plot_progressbar.daemon = True self.speaker_energy_plot_progressbar.start() From 5ac4897fa9384fac1a238e3217801915f9a6ebcb Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 12 Oct 2023 15:26:11 +0900 Subject: [PATCH 273/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20README=20:=20?= =?UTF-8?q?=E6=96=87=E8=A8=80=E3=81=AE=E4=BF=AE=E6=AD=A3=E3=80=81shields?= =?UTF-8?q?=E3=81=AE=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.jp.md | 15 +++++++++------ README.md | 17 +++++++++++------ 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/README.jp.md b/README.jp.md index b123170b..b281eec6 100644 --- a/README.jp.md +++ b/README.jp.md @@ -1,6 +1,10 @@
![](docs/vrct_logo.png) +[![GitHub release](https://img.shields.io/github/v/release/misyaguziya/VRCT.svg)](https://github.com/misyaguziya/VRCT/releases) +[![Downloads](https://img.shields.io/github/downloads/misyaguziya/VRCT/total)](https://github.com/misyaguziya/VRCT/releases) +[![Licence](https://img.shields.io/github/license/misyaguziya/VRCT)](https://github.com/misyaguziya/VRCT/blob/master/LICENSE) +[![Booth](https://img.shields.io/badge/Store-Booth.pm-red)](https://misyaguziya.booth.pm/items/5155325) | [English](./README.md) | **日本語** | @@ -10,13 +14,12 @@ VRCTは翻訳や文字起こしでVRChatの会話をサポートするソフト ![](docs/main_window.png) -
# ダウンロード&インストール 好きな場所からダウンロードしてください。 - [Github.com](https://github.com/misyaguziya/VRCT/releases/) -- [BOOTH.pm](https://misyaguziya.booth.pm/) +- [BOOTH.pm](https://misyaguziya.booth.pm/items/5155325) ダウンロードしてexeを起動するだけです。 @@ -26,10 +29,10 @@ VRCTは話す言語の異なる人同士が会話を行うためにチャット ※サポート対象外ですがその他の用途として映画鑑賞等でも使用されています。 VRCTはあなたの会話を以下でサポートをします。 -- 💬VRChatへのチャット送信機能 -- 🌐翻訳機能 -- 🎙マイクの文字起こし機能 -- 🔈スピーカーの文字起こし機能 +- 💬 **VRChatへのチャット送信機能** +- 🌐 **翻訳機能** +- 🎙 **マイクの文字起こし機能** +- 🔈 **スピーカーの文字起こし機能** # ドキュメント 初期設定や基本機能、その他の機能についても記載してあります。 diff --git a/README.md b/README.md index 35a427e2..37d302cc 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@
![](docs/vrct_logo.png) +[![GitHub release](https://img.shields.io/github/v/release/misyaguziya/VRCT.svg)](https://github.com/misyaguziya/VRCT/releases) +[![Downloads](https://img.shields.io/github/downloads/misyaguziya/VRCT/total)](https://github.com/misyaguziya/VRCT/releases) +[![Licence](https://img.shields.io/github/license/misyaguziya/VRCT)](https://github.com/misyaguziya/VRCT/blob/master/LICENSE) +[![Booth](https://img.shields.io/badge/Store-Booth.pm-red)](https://misyaguziya.booth.pm/items/5155325) + | **English** | [日本語](./README.jp.md) |

@@ -14,7 +19,7 @@ VRCT is software that supports VRChat conversations with translation and transcr # Download & Install Download from anywhere you like. - [Github.com](https://github.com/misyaguziya/VRCT/releases/) -- [BOOTH.pm](https://misyaguziya.booth.pm/) +- [BOOTH.pm](https://misyaguziya.booth.pm/items/5155325) Just download and run the exe. @@ -24,10 +29,10 @@ These features are designed for use within VRChat. *Although not supported, it is also used for other purposes such as watching movies. VRCT supports your conversations with -- 💬Send chat to VRChat -- 🌐translation -- 🎙Transcription of audio from microphone -- 🔈Transcription of audio from Speaker +- 💬 **Send chat to VRChat** +- 🌐 **Translation** +- 🎙 **Transcription of audio from microphone** +- 🔈 **Transcription of audio from Speaker** # Documents Initial setup, basic functions, and other features are also described. @@ -52,7 +57,7 @@ Initial setup, basic functions, and other features are also described. ## Author - [みしゃ(misyaguzi)](https://github.com/misyaguziya) (Main Development) - [しいな(Shiina_12siy)](https://twitter.com/Shiina_12siy) (UI/UX, UI multilingual support) -- [レラ](https://github.com/soumt-r) (translation:Korean) +- [レラ](https://github.com/soumt-r) (Translation:Korean) - [どね]() (Logo Design) --- From 0bad71deccb8750ba2396a7fab24ebbd56105d66 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 12 Oct 2023 16:08:51 +0900 Subject: [PATCH 274/355] [Update] Config Window: combine Mic/Speaker Dynamic Energy Threshold and Mic/Speaker Energy Threshold. change the label and description for it. --- locales/en.yml | 14 ++----- locales/ja.yml | 10 +---- view.py | 24 ++++++------ .../_SettingBoxGenerator.py | 39 +++++++++---------- .../createSettingBox_Mic.py | 2 - .../createSettingBox_Speaker.py | 2 - vrct_gui/ui_managers/UiScalingManager.py | 6 +-- 7 files changed, 39 insertions(+), 58 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index b5f5a01c..1ee08a3c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -57,11 +57,8 @@ config_window: mic_device: label: Mic Device mic_dynamic_energy_threshold: - label: Mic Energy Threshold (Auto) - desc: Automatically determine mic input sensitivity. - mic_energy_threshold: - label: Mic Energy Threshold (Manual) - desc: Slider to modify the threshold for activating voice input. Press the microphone button to initiate input and speak, allowing you to adjust it while monitoring the actual volume. + label: Mic Energy Threshold (Automatic) + desc: Enabling this option will automatically adjust the microphone's input sensitivity. If you disable it, you can manually set the input sensitivity using the slider. Press the microphone icon to input your voice and adjust the sensitivity while monitoring the actual volume. mic_record_timeout: label: Mic Record Timeout mic_phrase_timeout: @@ -76,11 +73,8 @@ config_window: speaker_device: label: Speaker Device speaker_dynamic_energy_threshold: - label: Speaker Energy Threshold (Auto) - desc: Automatically determine speaker input sensitivity. - speaker_energy_threshold: - label: Speaker Energy Threshold (Manual) - desc: Slider to modify the threshold for activating voice input. Press the headphones mark button to start input and speak something, so you can adjust it while monitoring the actual volume. + label: Speaker Energy Threshold (Automatic) + desc: Enabling this option will automatically adjust the speaker's input sensitivity. If you disable it, you can manually set the input sensitivity using the slider. Press the headphones icon to listen to the audio and adjust the sensitivity while monitoring the volume. speaker_record_timeout: label: Speaker Record Timeout speaker_phrase_timeout: diff --git a/locales/ja.yml b/locales/ja.yml index ec547ebf..4fe7887b 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -60,10 +60,7 @@ config_window: label: マイク (デバイス) mic_dynamic_energy_threshold: label: マイク入力感度の調整(自動) - desc: マイクの入力感度が自動的に調節されます。 - mic_energy_threshold: - label: マイク入力感度の調整(手動) - desc: スライダーを調整してしきい値を決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。 + desc: 有効にするとマイクの入力感度が自動的に調節されます。無効の場合は、スライダーを調整して入力感度を手動で決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。 mic_record_timeout: label: マイク音声の区切りの無音時間 mic_phrase_timeout: @@ -79,10 +76,7 @@ config_window: label: スピーカー(デバイス) speaker_dynamic_energy_threshold: label: スピーカー入力感度の調整(自動) - desc: スピーカーの入力感度が自動的に調節されます。 - speaker_energy_threshold: - label: スピーカー入力感度の調整(手動) - desc: スライダーを調整してしきい値を決められます。スピーカーのアイコンを押すと、設定されたデバイスから音を聞き取り、音量を確認しながら調節できます。 + desc: 有効にするとスピーカーの入力感度が自動的に調節されます。無効の場合は、スライダーを調整して入力感度を手動で決められます。ヘッドフォンのアイコンを押すと、実際に音声を聞き取り、音量を確認しながら調節できます。 speaker_record_timeout: label: スピーカー音声の区切りの無音時間 speaker_phrase_timeout: diff --git a/view.py b/view.py index a83b6d66..bc8974a1 100644 --- a/view.py +++ b/view.py @@ -203,18 +203,18 @@ class View(): CALLBACK_SET_MIC_DEVICE=None, VAR_MIC_DEVICE=StringVar(value=config.CHOICE_MIC_DEVICE), - VAR_LABEL_MIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_energy_threshold.label")), - VAR_DESC_MIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_energy_threshold.desc")), + + VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_dynamic_energy_threshold.label")), + VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_dynamic_energy_threshold.desc")), + CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD=None, + VAR_MIC_DYNAMIC_ENERGY_THRESHOLD=BooleanVar(value=config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD), + SLIDER_RANGE_MIC_ENERGY_THRESHOLD=(0, config.MAX_MIC_ENERGY_THRESHOLD), CALLBACK_CHECK_MIC_THRESHOLD=None, VAR_MIC_ENERGY_THRESHOLD__SLIDER=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), VAR_MIC_ENERGY_THRESHOLD__ENTRY=StringVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), CALLBACK_FOCUS_OUT_MIC_ENERGY_THRESHOLD=self.setLatestConfigVariable_MicEnergyThreshold, - VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_dynamic_energy_threshold.label")), - VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_dynamic_energy_threshold.desc")), - CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD=None, - VAR_MIC_DYNAMIC_ENERGY_THRESHOLD=BooleanVar(value=config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD), VAR_LABEL_MIC_RECORD_TIMEOUT=StringVar(value=i18n.t("config_window.mic_record_timeout.label")), VAR_DESC_MIC_RECORD_TIMEOUT=None, @@ -247,18 +247,18 @@ class View(): CALLBACK_SET_SPEAKER_DEVICE=None, VAR_SPEAKER_DEVICE=StringVar(value=config.CHOICE_SPEAKER_DEVICE), - VAR_LABEL_SPEAKER_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_energy_threshold.label")), - VAR_DESC_SPEAKER_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_energy_threshold.desc")), + + VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_dynamic_energy_threshold.label")), + VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_dynamic_energy_threshold.desc")), + CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=None, + VAR_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=BooleanVar(value=config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD), + SLIDER_RANGE_SPEAKER_ENERGY_THRESHOLD=(0, config.MAX_SPEAKER_ENERGY_THRESHOLD), CALLBACK_CHECK_SPEAKER_THRESHOLD=None, VAR_SPEAKER_ENERGY_THRESHOLD__SLIDER=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), VAR_SPEAKER_ENERGY_THRESHOLD__ENTRY=StringVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), CALLBACK_FOCUS_OUT_SPEAKER_ENERGY_THRESHOLD=self.setLatestConfigVariable_SpeakerEnergyThreshold, - VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_dynamic_energy_threshold.label")), - VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_dynamic_energy_threshold.desc")), - CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=None, - VAR_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=BooleanVar(value=config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD), VAR_LABEL_SPEAKER_RECORD_TIMEOUT=StringVar(value=i18n.t("config_window.speaker_record_timeout.label")), VAR_DESC_SPEAKER_RECORD_TIMEOUT=None, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 29fbba04..8a5ad552 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -18,7 +18,7 @@ class _SettingBoxGenerator(): self.dropdown_menu_window = vrct_gui.vrct_gui.dropdown_menu_window - def _createSettingBoxFrame(self, sb__attr_name, for_var_label_text, for_var_desc_text): + def _createSettingBoxFrame(self, sb__attr_name, for_var_label_text=None, for_var_desc_text=None): self.config_window.sb__widgets[sb__attr_name] = SimpleNamespace() setting_box_frame = CTkFrame(self.parent_widget, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) @@ -39,10 +39,15 @@ class _SettingBoxGenerator(): setting_box_frame_wrapper_fix_border2 = CTkFrame(setting_box_frame, corner_radius=0, width=0, height=0) setting_box_frame_wrapper_fix_border2.grid(row=0, column=1, sticky="ns") - self._setSettingBoxLabels(sb__attr_name, setting_box_frame_wrapper, for_var_label_text, for_var_desc_text) + if for_var_label_text is not None: + self._setSettingBoxLabels(sb__attr_name, setting_box_frame_wrapper, for_var_label_text, for_var_desc_text) + # setting_box_item_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color="red") setting_box_item_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, width=0, height=0, fg_color=self.settings.ctm.SB__BG_COLOR) - setting_box_item_frame.grid(row=0, column=2, padx=0, sticky="nsew") + if for_var_label_text is not None: + setting_box_item_frame.grid(row=0, column=2, padx=0, sticky="nsew") + else: + setting_box_item_frame.grid(row=0, columnspan=3, padx=0, sticky="nsew") setting_box_item_frame.grid_rowconfigure((0,2), weight=1) setting_box_item_frame.grid_columnconfigure(0, weight=1) @@ -227,7 +232,7 @@ class _SettingBoxGenerator(): def createSettingBoxProgressbarXSlider( self, - for_var_label_text, for_var_desc_text, command, progressbar_x_slider_attr_name, + command, progressbar_x_slider_attr_name, entry_attr_name, entry_bind__FocusOut, slider_attr_name, slider_range, progressbar_attr_name, @@ -243,22 +248,18 @@ class _SettingBoxGenerator(): ): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(progressbar_x_slider_attr_name, for_var_label_text, for_var_desc_text) - - ENTRY_WIDTH = self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH - BAR_WIDTH = self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BAR_WIDTH - - BAR_PADDING = int(ENTRY_WIDTH + self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BAR_RIGHT_PADX) - BUTTON_PADDING = int(BAR_WIDTH + BAR_PADDING + self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BUTTON_RIGHT_PADX) + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(progressbar_x_slider_attr_name) def adjusted_command__for_entry_bind__Any_KeyRelease(e): command(e.widget.get()) def adjusted_command__for_slider(value): command(value) + setting_box_item_frame.grid_columnconfigure((0,2), weight=0) + setting_box_item_frame.grid_columnconfigure(1, weight=1) entry_widget = CTkEntry( setting_box_item_frame, - width=ENTRY_WIDTH, + width=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH, height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT, textvariable=entry_variable, font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__ENTRY_FONT_SIZE, weight="normal"), @@ -268,7 +269,7 @@ class _SettingBoxGenerator(): if entry_bind__FocusOut is not None: entry_widget.bind("", entry_bind__FocusOut, "+") - entry_widget.grid(row=1, column=SETTING_BOX_COLUMN, padx=0, pady=0, sticky="e") + entry_widget.grid(row=1, column=2, padx=0, pady=0, sticky="e") setattr(self.config_window, entry_attr_name, entry_widget) @@ -284,7 +285,6 @@ class _SettingBoxGenerator(): command=adjusted_command__for_slider, variable=slider_variable, height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__SLIDER_HEIGHT, - width=BAR_WIDTH, border_width=0, button_length=SLIDER_BORDER_WIDTH, button_corner_radius=SLIDER_BUTTON_LENGTH, @@ -295,7 +295,7 @@ class _SettingBoxGenerator(): progress_color=self.settings.ctm.SB__BG_COLOR, border_color=self.settings.ctm.SB__BG_COLOR, ) - slider_widget.grid(row=1, column=SETTING_BOX_COLUMN, padx=(0, BAR_PADDING), sticky="e") + slider_widget.grid(row=1, column=1, padx=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BAR_PADX, sticky="ew") setattr(self.config_window, slider_attr_name, slider_widget) @@ -303,12 +303,11 @@ class _SettingBoxGenerator(): progressbar_widget = CTkProgressBar( setting_box_item_frame, - width=BAR_WIDTH, height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__PROGRESSBAR_HEIGHT, corner_radius=0, ) setattr(self.config_window, progressbar_attr_name, progressbar_widget) - progressbar_widget.grid(row=1, column=SETTING_BOX_COLUMN, padx=(0, BAR_PADDING), sticky="e") + progressbar_widget.grid(row=1, column=1, padx=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BAR_PADX, sticky="ew") progressbar_widget.set(0) @@ -323,13 +322,13 @@ class _SettingBoxGenerator(): active_button_wrapper = self._createActiveButtonForProgressbarXSlider(setting_box_item_frame, active_button_command, button_image_file) setattr(self.config_window, active_button_attr_name, active_button_wrapper) - passive_button_wrapper.grid(row=1, column=SETTING_BOX_COLUMN, padx=(0,BUTTON_PADDING), sticky="e") + passive_button_wrapper.grid(row=1, column=0, padx=0, sticky="w") passive_button_wrapper.configure(corner_radius=int(getLatestWidth(passive_button_wrapper)/2)) - disabled_button_wrapper.grid(row=1, column=SETTING_BOX_COLUMN, padx=(0,BUTTON_PADDING), sticky="e") + disabled_button_wrapper.grid(row=1, column=0, padx=0, sticky="w") disabled_button_wrapper.configure(corner_radius=int(getLatestWidth(passive_button_wrapper)/2)) - active_button_wrapper.grid(row=1, column=SETTING_BOX_COLUMN, padx=(0,BUTTON_PADDING), sticky="e") + active_button_wrapper.grid(row=1, column=0, padx=0, sticky="w") active_button_wrapper.configure(corner_radius=int(getLatestWidth(passive_button_wrapper)/2)) passive_button_wrapper.grid_remove() diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 9ad30f57..8b192421 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -76,8 +76,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari row+=1 config_window.sb__mic_energy_threshold = createSettingBoxProgressbarXSlider( - for_var_label_text=view_variable.VAR_LABEL_MIC_ENERGY_THRESHOLD, - for_var_desc_text=view_variable.VAR_DESC_MIC_ENERGY_THRESHOLD, command=slider_input_mic_energy_threshold_callback, progressbar_x_slider_attr_name="sb__mic_energy_threshold", diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 9886ac57..62e8dd12 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -59,8 +59,6 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ row+=1 config_window.sb__speaker_energy_threshold = createSettingBoxProgressbarXSlider( - for_var_label_text=view_variable.VAR_LABEL_SPEAKER_ENERGY_THRESHOLD, - for_var_desc_text=view_variable.VAR_DESC_SPEAKER_ENERGY_THRESHOLD, command=slider_input_speaker_energy_threshold_callback, progressbar_x_slider_attr_name="sb__speaker_energy_threshold", diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index c669cf69..e5c7a41c 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -240,12 +240,10 @@ class UiScalingManager(): self.config_window.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH = self.config_window.RESPONSIVE_UI_SIZE_INT_50 self.config_window.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT = self.config_window.SB__ENTRY_HEIGHT self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_HEIGHT = self._calculateUiSize(40) - self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_LENGTH = self._calculateUiSize(2) - self.config_window.SB__PROGRESSBAR_X_SLIDER__BAR_WIDTH = self._calculateUiSize(200) self.config_window.SB__PROGRESSBAR_X_SLIDER__PROGRESSBAR_HEIGHT = self._calculateUiSize(8) - self.config_window.SB__PROGRESSBAR_X_SLIDER__BAR_RIGHT_PADX = self._calculateUiSize(20) + self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_LENGTH = self._calculateUiSize(2) + self.config_window.SB__PROGRESSBAR_X_SLIDER__BAR_PADX = (self._calculateUiSize(30), self._calculateUiSize(30)) - self.config_window.SB__PROGRESSBAR_X_SLIDER__BUTTON_RIGHT_PADX = self._calculateUiSize(20) self.config_window.SB__PROGRESSBAR_X_SLIDER__BUTTON_IPADXY = self._calculateUiSize(10) self.config_window.SB__PROGRESSBAR_X_SLIDER__BUTTON_ICON_SIZE = self._calculateUiSize(20) From f185b2f475c58a79afddde74e063116b172adc36 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 12 Oct 2023 17:03:26 +0900 Subject: [PATCH 275/355] [Refactor] Remove the code that is no longer in use. --- controller.py | 4 ---- view.py | 19 ---------------- vrct_gui/_changeConfigWindowWidgetsStatus.py | 23 ++------------------ 3 files changed, 2 insertions(+), 44 deletions(-) diff --git a/controller.py b/controller.py index 80ce9dc8..05f1829f 100644 --- a/controller.py +++ b/controller.py @@ -424,12 +424,10 @@ def callbackCheckMicThreshold(is_turned_on): view.replaceMicThresholdCheckButton_Disabled() model.startCheckMicEnergy(setProgressBarMicEnergy, view.initProgressBar_MicEnergy) view.replaceMicThresholdCheckButton_Active() - view.setWidgetsStatus_ThresholdCheckButton_Normal() else: view.replaceMicThresholdCheckButton_Disabled() model.stopCheckMicEnergy() view.replaceMicThresholdCheckButton_Passive() - view.setWidgetsStatus_ThresholdCheckButton_Normal() def callbackSetMicRecordTimeout(value): print("callbackSetMicRecordTimeout", value) @@ -522,12 +520,10 @@ def callbackCheckSpeakerThreshold(is_turned_on): view.replaceSpeakerThresholdCheckButton_Disabled() model.startCheckSpeakerEnergy(setProgressBarSpeakerEnergy, view.initProgressBar_SpeakerEnergy) view.replaceSpeakerThresholdCheckButton_Active() - view.setWidgetsStatus_ThresholdCheckButton_Normal() else: view.replaceSpeakerThresholdCheckButton_Disabled() model.stopCheckSpeakerEnergy() view.replaceSpeakerThresholdCheckButton_Passive() - view.setWidgetsStatus_ThresholdCheckButton_Normal() def callbackSetSpeakerRecordTimeout(value): print("callbackSetSpeakerRecordTimeout", value) diff --git a/view.py b/view.py index bc8974a1..3a0187e4 100644 --- a/view.py +++ b/view.py @@ -718,25 +718,6 @@ class View(): vrct_gui.config_window.sb__speaker_dynamic_energy_threshold.grid(pady=(0,1)) vrct_gui.config_window.sb__speaker_energy_threshold.grid_remove() - @staticmethod - def setWidgetsStatus_ThresholdCheckButton_Disabled(): - vrct_gui._changeConfigWindowWidgetsStatus( - status="disabled", - target_names=[ - "mic_energy_threshold_check_button", - "speaker_energy_threshold_check_button", - ] - ) - - @staticmethod - def setWidgetsStatus_ThresholdCheckButton_Normal(): - vrct_gui._changeConfigWindowWidgetsStatus( - status="normal", - target_names=[ - "mic_energy_threshold_check_button", - "speaker_energy_threshold_check_button", - ] - ) def initMicThresholdCheckButton(self): diff --git a/vrct_gui/_changeConfigWindowWidgetsStatus.py b/vrct_gui/_changeConfigWindowWidgetsStatus.py index 0183c886..39ca924b 100644 --- a/vrct_gui/_changeConfigWindowWidgetsStatus.py +++ b/vrct_gui/_changeConfigWindowWidgetsStatus.py @@ -1,8 +1,8 @@ from customtkinter import CTkImage def _changeConfigWindowWidgetsStatus(config_window, settings, view_variable, status, target_names): - if target_names == "All": - target_names = ["mic_energy_threshold_check_button", "speaker_energy_threshold_check_button"] + # if target_names == "All": + # target_names = [] def disableOptionmenuWidget(target_widget): @@ -17,25 +17,6 @@ def _changeConfigWindowWidgetsStatus(config_window, settings, view_variable, sta for target_name in target_names: match target_name: - case "mic_energy_threshold_check_button": - if status == "disabled": - config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.children["!ctklabel"].configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - - elif status == "normal": - config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR) - config_window.sb__progressbar_x_slider__passive_button_mic_energy_threshold.children["!ctklabel"].configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR) - - case "speaker_energy_threshold_check_button": - if status == "disabled": - config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.children["!ctklabel"].configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR) - - elif status == "normal": - config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR) - config_window.sb__progressbar_x_slider__passive_button_speaker_energy_threshold.children["!ctklabel"].configure(fg_color=settings.ctm.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR) - - case "sb__optionmenu_mic_host": if status == "disabled": target_widget = config_window.sb__widgets["sb__optionmenu_mic_host"] From 717a56a580e578d91eb427a8d106c736d76d6cb8 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 05:59:14 +0900 Subject: [PATCH 276/355] =?UTF-8?q?[bugfix]=20fix=201px=20bugs.=20(?= =?UTF-8?q?=E3=81=BE=E3=81=A0=E7=99=BA=E7=94=9F=E3=81=99=E3=82=8B)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateSelectableLanguagesWindow.py | 6 +++--- vrct_gui/config_window/ConfigWindow.py | 9 ++++++--- .../_createSettingBoxCompactModeButton.py | 4 ++-- .../_createSettingBoxTitle.py | 8 ++++++-- .../createSettingBoxTopBar.py | 19 +++++++++++++++++-- .../_addConfigSideMenuItem.py | 3 --- .../createSideMenuAndSettingsBoxContainers.py | 4 ++-- .../createSidebarLanguagesSettings.py | 5 ++++- vrct_gui/vrct_gui.py | 6 +++--- 9 files changed, 43 insertions(+), 21 deletions(-) diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index 51fdd1f9..0ba102b4 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -1,7 +1,7 @@ from functools import partial from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor, applyUiScalingAndFixTheBugScrollBar -from utils import callFunctionIfCallable +from utils import callFunctionIfCallable, makeEven from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont, CTkScrollableFrame @@ -39,8 +39,8 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.attach.update_idletasks() self.x_pos = self.attach.winfo_rootx() self.y_pos = self.attach.winfo_rooty() - self.width_new = self.attach.winfo_width() - self.height_new = self.attach.winfo_height() + self.width_new = makeEven(self.attach.winfo_width()) + self.height_new = makeEven(self.attach.winfo_height()) self.geometry("{}x{}+{}+{}".format(self.width_new, self.height_new, self.x_pos, self.y_pos)) diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 463fb78a..6bf10df8 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -1,9 +1,9 @@ from .widgets import createConfigWindowTitle, createSideMenuAndSettingsBoxContainers, createSettingBoxTopBar -from customtkinter import CTkToplevel +from customtkinter import CTkToplevel, CTkFrame -from ..ui_utils import getImagePath +from ..ui_utils import getImagePath, getLatestWidth, getLatestHeight class ConfigWindow(CTkToplevel): def __init__(self, vrct_gui, settings, view_variable): @@ -32,8 +32,11 @@ class ConfigWindow(CTkToplevel): createSettingBoxTopBar(config_window=self, settings=self.settings, view_variable=self._view_variable) - createSideMenuAndSettingsBoxContainers(config_window=self, settings=self.settings, view_variable=self._view_variable) + # for fixing 1px bug + sls__box_optionmenu_wrapper_fix_1px_bug = CTkFrame(self.side_menu_bg_container, corner_radius=0, width=0, height=0) + sls__box_optionmenu_wrapper_fix_1px_bug.grid(row=1, column=0, sticky="ew") + self.bind_all("", lambda event: event.widget.focus_set(), "+") \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py index 6cdcd504..792ecc12 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py @@ -1,6 +1,6 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkSwitch -def _createSettingBoxCompactModeButton(parent_widget, config_window, settings, view_variable): +def _createSettingBoxCompactModeButton(parent_widget, config_window, settings, view_variable, column_num): def switchConfigWindowCompactMode(): if config_window.setting_box_compact_mode_switch_box.get() is True: @@ -13,7 +13,7 @@ def _createSettingBoxCompactModeButton(parent_widget, config_window, settings, v config_window.setting_box_compact_mode_button_container = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) - config_window.setting_box_compact_mode_button_container.grid(row=0, column=1, padx=settings.uism.COMPACT_MODE_PADX, sticky="nsw") + config_window.setting_box_compact_mode_button_container.grid(row=0, column=column_num, padx=settings.uism.COMPACT_MODE_PADX, sticky="nsw") diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxTitle.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxTitle.py index 824da444..7c0ec386 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxTitle.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxTitle.py @@ -1,10 +1,10 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel -def _createSettingBoxTitle(parent_widget, config_window, settings, view_variable): +def _createSettingBoxTitle(parent_widget, config_window, settings, view_variable, column_num): parent_widget.grid_columnconfigure(0, weight=1) config_window.main_current_active_config_title_container = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) - config_window.main_current_active_config_title_container.grid(row=0, column=0, sticky="nsew") + config_window.main_current_active_config_title_container.grid(row=0, column=column_num, sticky="nsew") config_window.main_current_active_config_title_container.grid_rowconfigure(0, weight=1) @@ -18,3 +18,7 @@ def _createSettingBoxTitle(parent_widget, config_window, settings, view_variable ) config_window.main_current_active_config_title.grid(row=0, column=0, padx=0, pady=settings.uism.TOP_BAR__IPADY) + + # for fixing 1px bug + sls__box_optionmenu_wrapper_fix_1px_bug = CTkFrame(config_window.main_current_active_config_title, corner_radius=0, width=0, height=0) + sls__box_optionmenu_wrapper_fix_1px_bug.grid(row=0, column=column_num, sticky="ns") \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py index d663d17d..8e0581e5 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py @@ -3,6 +3,9 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel from ._createSettingBoxTitle import _createSettingBoxTitle from ._createSettingBoxCompactModeButton import _createSettingBoxCompactModeButton +from ....ui_utils import getLatestHeight, getLatestWidth +from utils import isEven + def createSettingBoxTopBar(config_window, settings, view_variable): config_window.grid_columnconfigure(1, weight=1) @@ -10,6 +13,18 @@ def createSettingBoxTopBar(config_window, settings, view_variable): config_window.setting_box_top_bar.grid(row=0, column=1, sticky="nsew") - _createSettingBoxTitle(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings, view_variable=view_variable) + column_num=0 + _createSettingBoxTitle(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings, view_variable=view_variable, column_num=column_num) + column_num+=1 - _createSettingBoxCompactModeButton(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings, view_variable=view_variable) \ No newline at end of file + _createSettingBoxCompactModeButton(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings, view_variable=view_variable, column_num=column_num) + column_num+=1 + + + l_height = getLatestHeight(config_window.side_menu_config_window_title_logo_frame) + if isEven(l_height) is False: + config_window.grid_rowconfigure(0, weight=0, minsize=l_height+1) + + # for fixing 1px bug + setting_box_top_bar_fix_1px_bug = CTkFrame(config_window.setting_box_top_bar, corner_radius=0, width=0, height=0) + setting_box_top_bar_fix_1px_bug.grid(row=0, column=column_num, sticky="ns") \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py index 4e5ec347..39d83009 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_addConfigSideMenuItem.py @@ -96,9 +96,6 @@ def _addConfigSideMenuItem(config_window, settings, view_variable, side_menu_set setattr(config_window, selected_mark_attr_name, selected_mark_widget) - - - # Arrange selected_mark_widget.place(relx=-1, rely=0.5, relheight=1, anchor="w") label_widget.grid(row=0, column=0, padx=settings.uism.SIDE_MENU_LABELS_IPADX, pady=settings.uism.SIDE_MENU_LABELS_IPADY, sticky="ew") diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index e5fb507d..6cfc489e 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -30,9 +30,9 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl config_window.side_menu_bg_container = CTkFrame(config_window, corner_radius=0, fg_color=settings.ctm.SIDE_MENU_BG_COLOR, width=0, height=0) config_window.side_menu_bg_container.grid(row=1, column=0, sticky="nsew") - + config_window.side_menu_bg_container.grid_rowconfigure(0, weight=1) config_window.side_menu_container = CTkFrame(config_window.side_menu_bg_container, corner_radius=0, fg_color=settings.ctm.SIDE_MENU_LABELS_BG_FOR_FAKE_BORDER_COLOR, width=0, height=0) - config_window.side_menu_container.grid(row=0, column=0, padx=settings.uism.TOP_BAR_SIDE__TITLE_PADX, pady=(settings.uism.SIDE_MENU_TOP_PADY, 0)) + config_window.side_menu_container.grid(row=0, column=0, padx=settings.uism.TOP_BAR_SIDE__TITLE_PADX, pady=(settings.uism.SIDE_MENU_TOP_PADY, 0), sticky="nsew") diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 0363934e..98fda4ae 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -1,6 +1,6 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkImage -from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor, createOptionMenuBox +from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor, createOptionMenuBox, getLatestWidth from utils import callFunctionIfCallable @@ -101,6 +101,9 @@ def createSidebarLanguagesSettings(settings, main_window, view_variable): ) sls__selected_language_box.grid(row=0, column=0, sticky="ew") + sls__box_optionmenu_wrapper_fix_1px_bug = CTkFrame(optionmenu_label_widget, corner_radius=0, width=0, height=0) + sls__box_optionmenu_wrapper_fix_1px_bug.grid(row=0, column=1, sticky="ns") + return sls__box diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 844c029a..390d076b 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -13,7 +13,7 @@ from .main_window import createMainWindowWidgets from .config_window import ConfigWindow from .ui_utils import _setDefaultActiveTab, getLatestHeight, setGeometryToCenterOfScreen, fadeInAnimation -from utils import callFunctionIfCallable +from utils import callFunctionIfCallable, makeEven class VRCT_GUI(CTk): def __init__(self): @@ -238,8 +238,8 @@ class VRCT_GUI(CTk): self.update_idletasks() x_pos = self.winfo_rootx() y_pos = self.winfo_rooty() - width_new = self.winfo_width() - height_new = self.winfo_height() + width_new = makeEven(self.winfo_width()) + height_new = makeEven(self.winfo_height()) self.modal_window.geometry("{}x{}+{}+{}".format(width_new, height_new, x_pos, y_pos)) self.modal_window.lift() From 94c3b72a5276e1a0b9e318ce57175324d40a3a70 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 13 Oct 2023 07:02:48 +0900 Subject: [PATCH 277/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20Auto?= =?UTF-8?q?Update=E5=A4=B1=E6=95=97=E6=99=82=E3=81=ABbooth=E3=82=92?= =?UTF-8?q?=E9=96=8B=E3=81=8F=E3=82=88=E3=81=86=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-) diff --git a/model.py b/model.py index 04ec62d3..7b148552 100644 --- a/model.py +++ b/model.py @@ -1,8 +1,8 @@ import sys from zipfile import ZipFile from subprocess import Popen -from os import makedirs -from os import path as os_path, rename as os_rename, mkdir as os_mkdir +from os import makedirs as os_makedirs +from os import path as os_path, rename as os_rename from shutil import rmtree from datetime import datetime from logging import getLogger, FileHandler, Formatter, INFO @@ -10,6 +10,7 @@ from time import sleep from queue import Queue from threading import Thread, Event from requests import get as requests_get +import webbrowser from flashtext import KeywordProcessor from models.translation.translation_translator import Translator @@ -97,7 +98,7 @@ class Model: return result def startLogger(self): - makedirs(os_path.join(os_path.dirname(sys.argv[0]), "logs"), exist_ok=True) + os_makedirs(os_path.join(os_path.dirname(sys.argv[0]), "logs"), exist_ok=True) logger = getLogger() logger.setLevel(INFO) file_name = os_path.join(os_path.dirname(sys.argv[0]), "logs", f"{datetime.now().strftime('%Y-%m-%d_%H-%M-%S')}.log") @@ -245,19 +246,22 @@ class Model: current_directory = os_path.dirname(sys.argv[0]) program_directory = os_path.dirname(__file__) - os_mkdir(os_path.join(current_directory, tmp_directory_name)) - res = requests_get(config.GITHUB_URL) - url = res.json()['assets'][0]['browser_download_url'] - res = requests_get(url, stream=True) - with open(os_path.join(current_directory, tmp_directory_name, filename), 'wb') as file: - for chunk in res.iter_content(chunk_size=1024): - file.write(chunk) - with ZipFile(os_path.join(current_directory, tmp_directory_name, filename)) as zf: - zf.extract(program_name, os_path.join(current_directory, tmp_directory_name)) - os_rename(os_path.join(current_directory, tmp_directory_name, program_name), os_path.join(current_directory, temporary_name)) - rmtree(os_path.join(current_directory, tmp_directory_name)) - command = [os_path.join(program_directory, "batch", batch_name), program_name, temporary_name, str(restart)] - Popen(command) + try: + res = requests_get(config.GITHUB_URL) + url = res.json()['assets'][0]['browser_download_url'] + res = requests_get(url, stream=True) + os_makedirs(os_path.join(current_directory, tmp_directory_name), exist_ok=True) + with open(os_path.join(current_directory, tmp_directory_name, filename), 'wb') as file: + for chunk in res.iter_content(chunk_size=1024): + file.write(chunk) + with ZipFile(os_path.join(current_directory, tmp_directory_name, filename)) as zf: + zf.extract(program_name, os_path.join(current_directory, tmp_directory_name)) + os_rename(os_path.join(current_directory, tmp_directory_name, program_name), os_path.join(current_directory, temporary_name)) + rmtree(os_path.join(current_directory, tmp_directory_name)) + command = [os_path.join(program_directory, "batch", batch_name), program_name, temporary_name, str(restart)] + Popen(command) + except: + webbrowser.open(config.BOOTH_URL, new=2, autoraise=True) @staticmethod def reStartSoftware(): From 36d750501e5b879520c075c39c07e4c0afb69053 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 09:04:26 +0900 Subject: [PATCH 278/355] [bugfix] Config Window: fix 1px bug. --- vrct_gui/config_window/ConfigWindow.py | 12 +++++++++++- .../_createSettingBoxCompactModeButton.py | 2 +- .../createSettingBoxTopBar/createSettingBoxTopBar.py | 7 ++++++- .../createSideMenuAndSettingsBoxContainers.py | 2 +- 4 files changed, 19 insertions(+), 4 deletions(-) diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 6bf10df8..d7be1492 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -4,6 +4,7 @@ from .widgets import createConfigWindowTitle, createSideMenuAndSettingsBoxContai from customtkinter import CTkToplevel, CTkFrame from ..ui_utils import getImagePath, getLatestWidth, getLatestHeight +from utils import isEven class ConfigWindow(CTkToplevel): def __init__(self, vrct_gui, settings, view_variable): @@ -35,8 +36,17 @@ class ConfigWindow(CTkToplevel): createSideMenuAndSettingsBoxContainers(config_window=self, settings=self.settings, view_variable=self._view_variable) # for fixing 1px bug + l_width = getLatestWidth(self.side_menu_bg_container) + if isEven(l_width) is False: + self.side_menu_bg_container.grid_columnconfigure(0, weight=0, minsize=l_width+1) + + # for fixing 1px bug + self.side_menu_bg_container.grid_rowconfigure(2, weight=1) sls__box_optionmenu_wrapper_fix_1px_bug = CTkFrame(self.side_menu_bg_container, corner_radius=0, width=0, height=0) - sls__box_optionmenu_wrapper_fix_1px_bug.grid(row=1, column=0, sticky="ew") + sls__box_optionmenu_wrapper_fix_1px_bug.grid(row=3, column=0, sticky="sew") + + # for fixing 1px bug + l_width = getLatestWidth(self.side_menu_bg_container) self.bind_all("", lambda event: event.widget.focus_set(), "+") \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py index 792ecc12..7f56af52 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py @@ -13,7 +13,7 @@ def _createSettingBoxCompactModeButton(parent_widget, config_window, settings, v config_window.setting_box_compact_mode_button_container = CTkFrame(parent_widget, corner_radius=0, fg_color=settings.ctm.TOP_BAR_BG_COLOR, width=0, height=0) - config_window.setting_box_compact_mode_button_container.grid(row=0, column=column_num, padx=settings.uism.COMPACT_MODE_PADX, sticky="nsw") + config_window.setting_box_compact_mode_button_container.grid(row=0, column=column_num, padx=settings.uism.COMPACT_MODE_PADX, sticky="nse") diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py index 8e0581e5..cac83acb 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py @@ -13,10 +13,15 @@ def createSettingBoxTopBar(config_window, settings, view_variable): config_window.setting_box_top_bar.grid(row=0, column=1, sticky="nsew") + config_window.setting_box_top_bar.grid_rowconfigure(0, weight=1) + column_num=0 _createSettingBoxTitle(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings, view_variable=view_variable, column_num=column_num) column_num+=1 + config_window.setting_box_top_bar.grid_columnconfigure(column_num, weight=1) + column_num+=1 + _createSettingBoxCompactModeButton(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings, view_variable=view_variable, column_num=column_num) column_num+=1 @@ -27,4 +32,4 @@ def createSettingBoxTopBar(config_window, settings, view_variable): # for fixing 1px bug setting_box_top_bar_fix_1px_bug = CTkFrame(config_window.setting_box_top_bar, corner_radius=0, width=0, height=0) - setting_box_top_bar_fix_1px_bug.grid(row=0, column=column_num, sticky="ns") \ No newline at end of file + setting_box_top_bar_fix_1px_bug.grid(row=0, column=column_num, sticky="nse") \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index 6cfc489e..2fac3c15 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -29,8 +29,8 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl config_window.grid_rowconfigure(1, weight=1) config_window.side_menu_bg_container = CTkFrame(config_window, corner_radius=0, fg_color=settings.ctm.SIDE_MENU_BG_COLOR, width=0, height=0) config_window.side_menu_bg_container.grid(row=1, column=0, sticky="nsew") + config_window.side_menu_bg_container.grid_columnconfigure(0, weight=1) - config_window.side_menu_bg_container.grid_rowconfigure(0, weight=1) config_window.side_menu_container = CTkFrame(config_window.side_menu_bg_container, corner_radius=0, fg_color=settings.ctm.SIDE_MENU_LABELS_BG_FOR_FAKE_BORDER_COLOR, width=0, height=0) config_window.side_menu_container.grid(row=0, column=0, padx=settings.uism.TOP_BAR_SIDE__TITLE_PADX, pady=(settings.uism.SIDE_MENU_TOP_PADY, 0), sticky="nsew") From 77500adb8d6231580f10ec551a13af183389cf6f Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 12:24:53 +0900 Subject: [PATCH 279/355] =?UTF-8?q?[bugfix]=20Main=20Window:=20=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E7=94=BB=E9=9D=A2=E3=82=92=E9=96=8B=E3=81=84=E3=81=9F?= =?UTF-8?q?=E6=99=82=E3=81=AB=E3=80=81=E3=83=A1=E3=82=A4=E3=83=B3=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E3=81=AB=E8=A2=AB=E3=81=9B=E3=82=8B=E3=83=A2=E3=83=BC?= =?UTF-8?q?=E3=83=80=E3=83=AB=E5=9E=8B=E3=82=A6=E3=82=A3=E3=83=B3=E3=83=89?= =?UTF-8?q?=E3=82=A6=E3=81=8C=E3=80=81=E3=83=A1=E3=82=A4=E3=83=B3=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E6=9C=80=E5=B0=8F=E5=8C=96=E6=99=82=E3=81=AF=E3=81=9D?= =?UTF-8?q?=E3=82=8C=E3=81=AB=E5=BE=93=E3=81=A3=E3=81=A6=E6=B6=88=E3=81=97?= =?UTF-8?q?=E3=80=81=E3=83=A1=E3=82=A4=E3=83=B3=E7=94=BB=E9=9D=A2=E3=82=92?= =?UTF-8?q?=E3=81=BE=E3=81=9F=E9=96=8B=E3=81=84=E3=81=9F=E6=99=82=E3=81=AB?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=80=82=20[Refactor]=20=E3=81=9D=E3=81=AE?= =?UTF-8?q?=E3=83=A2=E3=83=BC=E3=83=80=E3=83=AB=E5=9E=8B=E3=82=A6=E3=82=A3?= =?UTF-8?q?=E3=83=B3=E3=83=89=E3=82=A6=E3=81=AE=E8=A1=A8=E7=A4=BA=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E3=82=92=E7=A7=BB=E5=8B=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateModalWindow.py | 10 +++++++++- vrct_gui/vrct_gui.py | 27 ++++++++++++++++++++++++--- 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/vrct_gui/_CreateModalWindow.py b/vrct_gui/_CreateModalWindow.py index 8cd837c2..f7a656b1 100644 --- a/vrct_gui/_CreateModalWindow.py +++ b/vrct_gui/_CreateModalWindow.py @@ -1,5 +1,7 @@ from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont +from .ui_utils import fadeInAnimation + class _CreateModalWindow(CTkToplevel): def __init__(self, attach_window, settings, view_variable): super().__init__() @@ -46,4 +48,10 @@ class _CreateModalWindow(CTkToplevel): anchor="w", text_color=self.settings.ctm.TEXT_COLOR, ) - self.modal_container_label_wrapper.place(relx=0.5, rely=0.5, anchor="center") \ No newline at end of file + self.modal_container_label_wrapper.place(relx=0.5, rely=0.5, anchor="center") + + + def show(self): + self.attributes("-alpha", 0) + self.deiconify() + fadeInAnimation(self, steps=5, interval=0.005, max_alpha=0.5) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 390d076b..3b0ce31f 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -23,6 +23,23 @@ class VRCT_GUI(CTk): self.is_config_window_already_opened_once=False self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID=None self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID=None + self.BIND_UNMAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID = None + self.BIND_MAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID = None + + self.window_state = None + + def detectMainWindowState(self, _e=None): + self.new_window_state = self.wm_state() + if self.window_state == self.new_window_state: + return + else: + self.window_state = self.new_window_state + + if self.window_state == "iconic": + self.modal_window.withdraw() + elif self.window_state == "normal": + self.modal_window.show() + def _showGUI(self): @@ -111,11 +128,13 @@ class VRCT_GUI(CTk): callFunctionIfCallable(self._view_variable.CALLBACK_OPEN_CONFIG_WINDOW) self._adjustToMainWindowGeometry() - self.modal_window.attributes("-alpha", 0) - self.modal_window.deiconify() - fadeInAnimation(self.modal_window, steps=5, interval=0.005, max_alpha=0.5) + self.modal_window.show() self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID = self.bind("", self._adjustToMainWindowGeometry, "+") + + self.BIND_UNMAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID = self.bind("", self.detectMainWindowState, "+") + self.BIND_MAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID = self.bind("", self.detectMainWindowState, "+") + self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID = self.modal_window.bind("", lambda _e: self.config_window.lift(), "+") self.config_window.attributes("-alpha", 0) @@ -134,6 +153,8 @@ class VRCT_GUI(CTk): self.modal_window.withdraw() self.unbind("", self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID) + self.unbind("", self.BIND_UNMAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID) + self.unbind("", self.BIND_MAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID) self.modal_window.unbind("", self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID) self.adjusted_event=None From 508cca8215fa7af20ef1b3b49e81ca2047be9bb0 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 13:41:10 +0900 Subject: [PATCH 280/355] =?UTF-8?q?[Update]=20=E8=A8=AD=E5=AE=9A=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E3=81=8B=E3=82=89=E9=80=8F=E6=98=8E=E5=BA=A6=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E6=99=82=E3=81=AB=E3=80=81=E3=83=A1=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E7=94=BB=E9=9D=A2=E3=81=AE=E3=82=AB=E3=83=90=E3=83=BC=E3=82=92?= =?UTF-8?q?=E4=B8=80=E6=99=82=E7=9A=84=E3=81=AB=E5=A4=96=E3=81=99=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E3=80=82=20=E3=82=B9=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=83=80=E3=83=BC=E3=82=AF=E3=83=AA=E3=83=83=E3=82=AF=E6=99=82?= =?UTF-8?q?=EF=BC=88=E3=83=89=E3=83=A9=E3=83=83=E3=82=B0=E6=99=82=EF=BC=89?= =?UTF-8?q?=E3=81=AB=E3=82=AB=E3=83=90=E3=83=BC=E3=82=92=E5=A4=96=E3=81=97?= =?UTF-8?q?=E3=80=81=E3=83=AA=E3=83=AA=E3=83=BC=E3=82=B9=E6=99=82=E3=81=AB?= =?UTF-8?q?=E6=88=BB=E3=81=99=E3=80=82=20=E3=83=AA=E3=83=AA=E3=83=BC?= =?UTF-8?q?=E3=82=B9=E3=81=AFCTkSlider=E5=81=B4=E3=81=A7=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E3=81=AE?= =?UTF-8?q?=E3=81=A7=E3=81=86=E3=81=BE=E3=81=8F=E3=81=84=E3=81=8F=E3=81=91?= =?UTF-8?q?=E3=81=A9=E3=80=81=20CTkSlider=E3=81=AFbind=20ButtonPress(?= =?UTF-8?q?=E3=82=AF=E3=83=AA=E3=83=83=E3=82=AF)=E3=81=AF=E3=82=AA?= =?UTF-8?q?=E3=83=BC=E3=83=90=E3=83=BC=E3=83=A9=E3=82=A4=E3=83=89=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=82=8B=E3=81=AE=E3=81=A7=E5=BC=95=E6=95=B0command(?= =?UTF-8?q?=E3=82=AF=E3=83=AA=E3=83=83=E3=82=AF=E3=82=A4=E3=83=99=E3=83=B3?= =?UTF-8?q?=E3=83=88)=E3=81=AB=E7=84=A1=E7=90=86=E3=82=84=E3=82=8A?= =?UTF-8?q?=E6=8C=9F=E3=81=BF=E8=BE=BC=E3=81=BF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 11 +++++++++++ .../_SettingBoxGenerator.py | 16 +++++++++++++--- .../createSettingBox_Appearance.py | 2 ++ 3 files changed, 26 insertions(+), 3 deletions(-) diff --git a/view.py b/view.py index 3a0187e4..1d74c48c 100644 --- a/view.py +++ b/view.py @@ -156,6 +156,8 @@ class View(): SLIDER_RANGE_TRANSPARENCY=(50, 100), CALLBACK_SET_TRANSPARENCY=None, VAR_TRANSPARENCY=IntVar(value=config.TRANSPARENCY), + CALLBACK_BUTTON_PRESS_TRANSPARENCY=self._closeTheCoverOfMainWindow, + CALLBACK_BUTTON_RELEASE_TRANSPARENCY=self._openTheCoverOfMainWindow, VAR_LABEL_APPEARANCE_THEME=StringVar(value=i18n.t("config_window.appearance_theme.label")), VAR_DESC_APPEARANCE_THEME=StringVar(value=i18n.t("config_window.appearance_theme.desc")), @@ -539,6 +541,15 @@ class View(): vrct_gui.attributes("-topmost", False) + @staticmethod + def _openTheCoverOfMainWindow(): + vrct_gui.modal_window.show() + vrct_gui.config_window.lift() + + @staticmethod + def _closeTheCoverOfMainWindow(): + vrct_gui.modal_window.withdraw() + def enableMainWindowSidebarCompactMode(self): self.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = True vrct_gui._enableMainWindowSidebarCompactMode() diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 8a5ad552..692fb046 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -204,11 +204,10 @@ class _SettingBoxGenerator(): - def createSettingBoxSlider(self, for_var_label_text, for_var_desc_text, slider_attr_name, slider_range, command, variable, slider_number_of_steps: Union[int, None] = None): + def createSettingBoxSlider(self, for_var_label_text, for_var_desc_text, slider_attr_name, slider_range, command, variable, slider_number_of_steps: Union[int, None] = None, slider_bind__ButtonPress=None, slider_bind__ButtonRelease=None): (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(slider_attr_name, for_var_label_text, for_var_desc_text) - # print(self.settings.uism.SB__SLIDER_WIDTH) - # print(self.settings.uism.SB__SLIDER_HEIGHT) + slider_widget = CTkSlider( setting_box_item_frame, width=self.settings.uism.SB__SLIDER_WIDTH, @@ -225,6 +224,17 @@ class _SettingBoxGenerator(): slider_widget.grid(row=1, column=SETTING_BOX_COLUMN, sticky="e") + if slider_bind__ButtonPress is not None: + def adjusted_slider_bind__ButtonPress(_e): + command(_e) + slider_bind__ButtonPress() + slider_widget.configure(command=adjusted_slider_bind__ButtonPress) + + if slider_bind__ButtonRelease is not None: + def adjusted_slider_bind__ButtonRelease(_e): + slider_bind__ButtonRelease() + slider_widget.bind("", adjusted_slider_bind__ButtonRelease, "+") + return setting_box_frame diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index e745bc3f..b2058340 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -33,6 +33,8 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi slider_range=view_variable.SLIDER_RANGE_TRANSPARENCY, command=lambda value: slider_transparency_callback(value), variable=view_variable.VAR_TRANSPARENCY, + slider_bind__ButtonPress=view_variable.CALLBACK_BUTTON_PRESS_TRANSPARENCY, + slider_bind__ButtonRelease=view_variable.CALLBACK_BUTTON_RELEASE_TRANSPARENCY, ) config_window.sb__transparency.grid(row=row) row+=1 From bd851ac8d3dd037bfe481be707cd7352b3a32a46 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 14:22:44 +0900 Subject: [PATCH 281/355] [Update] comment out and disable the feature that startup osc enabled check --- config.py | 21 ++++++++------- controller.py | 15 ++++++----- view.py | 11 ++++---- .../createSettingBox_Others.py | 26 ++++++++++--------- 4 files changed, 39 insertions(+), 34 deletions(-) diff --git a/config.py b/config.py index 5dac3f77..4ccad224 100644 --- a/config.py +++ b/config.py @@ -486,16 +486,17 @@ class Config: self._ENABLE_SEND_MESSAGE_TO_VRC = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property - @json_serializable('STARTUP_OSC_ENABLED_CHECK') - def STARTUP_OSC_ENABLED_CHECK(self): - return self._STARTUP_OSC_ENABLED_CHECK + # [deprecated] + # @property + # @json_serializable('STARTUP_OSC_ENABLED_CHECK') + # def STARTUP_OSC_ENABLED_CHECK(self): + # return self._STARTUP_OSC_ENABLED_CHECK - @STARTUP_OSC_ENABLED_CHECK.setter - def STARTUP_OSC_ENABLED_CHECK(self, value): - if type(value) is bool: - self._STARTUP_OSC_ENABLED_CHECK = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + # @STARTUP_OSC_ENABLED_CHECK.setter + # def STARTUP_OSC_ENABLED_CHECK(self, value): + # if type(value) is bool: + # self._STARTUP_OSC_ENABLED_CHECK = value + # saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property @json_serializable('ENABLE_LOGGER') @@ -587,7 +588,7 @@ class Config: self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = True self._ENABLE_NOTICE_XSOVERLAY = False self._ENABLE_SEND_MESSAGE_TO_VRC = True - self._STARTUP_OSC_ENABLED_CHECK = True + # self._STARTUP_OSC_ENABLED_CHECK = True # [deprecated] self._ENABLE_LOGGER = False self._IS_CONFIG_WINDOW_COMPACT_MODE = False diff --git a/controller.py b/controller.py index 05f1829f..a3b77adb 100644 --- a/controller.py +++ b/controller.py @@ -592,9 +592,10 @@ def callbackSetEnableSendMessageToVrc(value): print("callbackSetEnableSendMessageToVrc", value) config.ENABLE_SEND_MESSAGE_TO_VRC = value -def callbackSetStartupOscEnabledCheck(value): - print("callbackSetStartupOscEnabledCheck", value) - config.STARTUP_OSC_ENABLED_CHECK = value +# [deprecated] +# def callbackSetStartupOscEnabledCheck(value): +# print("callbackSetStartupOscEnabledCheck", value) +# config.STARTUP_OSC_ENABLED_CHECK = value # Advanced Settings Tab def callbackSetOscIpAddress(value): @@ -621,9 +622,9 @@ def createMainWindow(): # set word filter model.addKeywords() - # check OSC started - if config.STARTUP_OSC_ENABLED_CHECK is True and config.ENABLE_SEND_MESSAGE_TO_VRC is True: - model.checkOSCStarted(view.printToTextbox_OSCError) + # check OSC started [deprecated] + # if config.STARTUP_OSC_ENABLED_CHECK is True and config.ENABLE_SEND_MESSAGE_TO_VRC is True: + # model.checkOSCStarted(view.printToTextbox_OSCError) # check Software Updated if model.checkSoftwareUpdated() is True: @@ -707,7 +708,7 @@ def createMainWindow(): "callback_set_enable_auto_export_message_logs": callbackSetEnableAutoExportMessageLogs, "callback_set_message_format": callbackSetMessageFormat, "callback_set_enable_send_message_to_vrc": callbackSetEnableSendMessageToVrc, - "callback_set_startup_osc_enabled_check": callbackSetStartupOscEnabledCheck, + # "callback_set_startup_osc_enabled_check": callbackSetStartupOscEnabledCheck, # [deprecated] # Advanced Settings Tab "callback_set_osc_ip_address": callbackSetOscIpAddress, diff --git a/view.py b/view.py index 1d74c48c..76d79047 100644 --- a/view.py +++ b/view.py @@ -309,10 +309,11 @@ class View(): CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC=None, VAR_ENABLE_SEND_MESSAGE_TO_VRC=BooleanVar(value=config.ENABLE_SEND_MESSAGE_TO_VRC), - VAR_LABEL_STARTUP_OSC_ENABLED_CHECK=StringVar(value=i18n.t("config_window.startup_osc_enabled_check.label")), - VAR_DESC_STARTUP_OSC_ENABLED_CHECK=StringVar(value=i18n.t("config_window.startup_osc_enabled_check.desc")), - CALLBACK_SET_STARTUP_OSC_ENABLED_CHECK=None, - VAR_STARTUP_OSC_ENABLED_CHECK=BooleanVar(value=config.STARTUP_OSC_ENABLED_CHECK), + # [deprecated] + # VAR_LABEL_STARTUP_OSC_ENABLED_CHECK=StringVar(value=i18n.t("config_window.startup_osc_enabled_check.label")), + # VAR_DESC_STARTUP_OSC_ENABLED_CHECK=StringVar(value=i18n.t("config_window.startup_osc_enabled_check.desc")), + # CALLBACK_SET_STARTUP_OSC_ENABLED_CHECK=None, + # VAR_STARTUP_OSC_ENABLED_CHECK=BooleanVar(value=config.STARTUP_OSC_ENABLED_CHECK), @@ -438,7 +439,7 @@ class View(): self.view_variable.CALLBACK_SET_MESSAGE_FORMAT = config_window_registers.get("callback_set_message_format", None) self.view_variable.CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC = config_window_registers.get("callback_set_enable_send_message_to_vrc", None) - self.view_variable.CALLBACK_SET_STARTUP_OSC_ENABLED_CHECK = config_window_registers.get("callback_set_startup_osc_enabled_check", None) + # self.view_variable.CALLBACK_SET_STARTUP_OSC_ENABLED_CHECK = config_window_registers.get("callback_set_startup_osc_enabled_check", None) #[deprecated] # Advanced Settings Tab self.view_variable.CALLBACK_SET_OSC_IP_ADDRESS = config_window_registers.get("callback_set_osc_ip_address", None) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py index 4fb6aeed..e4f46fb6 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_others/createSettingBox_Others.py @@ -21,8 +21,9 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v def checkbox_enable_send_message_to_vrc_callback(checkbox_box_widget): callFunctionIfCallable(view_variable.CALLBACK_SET_ENABLE_SEND_MESSAGE_TO_VRC, checkbox_box_widget.get()) - def checkbox_startup_osc_enabled_check_callback(checkbox_box_widget): - callFunctionIfCallable(view_variable.CALLBACK_SET_STARTUP_OSC_ENABLED_CHECK, checkbox_box_widget.get()) + # [deprecated] + # def checkbox_startup_osc_enabled_check_callback(checkbox_box_widget): + # callFunctionIfCallable(view_variable.CALLBACK_SET_STARTUP_OSC_ENABLED_CHECK, checkbox_box_widget.get()) def entry_message_format_callback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_MESSAGE_FORMAT, value) @@ -81,16 +82,17 @@ def createSettingBox_Others(setting_box_wrapper, config_window, settings, view_v command=lambda: checkbox_enable_send_message_to_vrc_callback(config_window.sb__checkbox_enable_send_message_to_vrc), variable=view_variable.VAR_ENABLE_SEND_MESSAGE_TO_VRC, ) - config_window.sb__enable_send_message_to_vrc.grid(row=row) + config_window.sb__enable_send_message_to_vrc.grid(row=row, pady=0) row+=1 - config_window.sb__startup_osc_enabled_check = createSettingBoxCheckbox( - for_var_label_text=view_variable.VAR_LABEL_STARTUP_OSC_ENABLED_CHECK, - for_var_desc_text=view_variable.VAR_DESC_STARTUP_OSC_ENABLED_CHECK, - checkbox_attr_name="sb__checkbox_startup_osc_enabled_check", - command=lambda: checkbox_startup_osc_enabled_check_callback(config_window.sb__checkbox_startup_osc_enabled_check), - variable=view_variable.VAR_STARTUP_OSC_ENABLED_CHECK, - ) - config_window.sb__startup_osc_enabled_check.grid(row=row, pady=0) - row+=1 + # [deprecated] + # config_window.sb__startup_osc_enabled_check = createSettingBoxCheckbox( + # for_var_label_text=view_variable.VAR_LABEL_STARTUP_OSC_ENABLED_CHECK, + # for_var_desc_text=view_variable.VAR_DESC_STARTUP_OSC_ENABLED_CHECK, + # checkbox_attr_name="sb__checkbox_startup_osc_enabled_check", + # command=lambda: checkbox_startup_osc_enabled_check_callback(config_window.sb__checkbox_startup_osc_enabled_check), + # variable=view_variable.VAR_STARTUP_OSC_ENABLED_CHECK, + # ) + # config_window.sb__startup_osc_enabled_check.grid(row=row, pady=0) + # row+=1 From 1380d66da1a25785082c26009a2b36375a7bc797 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 14:44:20 +0900 Subject: [PATCH 282/355] =?UTF-8?q?[Refactor]=20Main=20Window:=20=E8=B5=B7?= =?UTF-8?q?=E5=8B=95=E6=99=82=E3=81=AF=E3=82=B5=E3=82=A4=E3=83=89=E3=83=90?= =?UTF-8?q?=E3=83=BC=E3=81=8C=E9=96=8B=E3=81=84=E3=81=9F=E7=8A=B6=E6=85=8B?= =?UTF-8?q?=E3=81=A7=E3=80=81=E3=82=82=E3=81=97config.IS=5FMAIN=5FWINDOW?= =?UTF-8?q?=5FSIDEBAR=5FCOMPACT=5FMODE=E3=81=8CTrue=E3=81=AA=E3=82=89?= =?UTF-8?q?=E9=96=89=E3=81=98=E3=82=8B=E3=81=A8=E3=81=84=E3=81=86=E5=8B=95?= =?UTF-8?q?=E4=BD=9C=E3=81=AB=E3=80=82(=E3=83=A6=E3=83=BC=E3=82=B6?= =?UTF-8?q?=E3=83=BC=E3=81=AB=E8=A6=8B=E3=81=88=E3=82=8B=E3=81=BE=E3=81=A7?= =?UTF-8?q?=E3=81=AB=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B=E5=87=A6=E7=90=86?= =?UTF-8?q?=E3=81=AA=E3=81=AE=E3=81=A7=E8=A6=8B=E3=81=9F=E7=9B=AE=E3=81=AF?= =?UTF-8?q?=E5=A4=89=E3=82=8F=E3=82=89=E3=81=AA=E3=81=84)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 5 +++++ .../main_window/widgets/create_minimize_sidebar_button.py | 7 +------ vrct_gui/main_window/widgets/create_sidebar.py | 5 ----- 3 files changed, 6 insertions(+), 11 deletions(-) diff --git a/view.py b/view.py index 76d79047..49f05428 100644 --- a/view.py +++ b/view.py @@ -446,6 +446,11 @@ class View(): self.view_variable.CALLBACK_SET_OSC_PORT = config_window_registers.get("callback_set_osc_port", None) # The initial processing after registration. + + if config.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: + vrct_gui._enableMainWindowSidebarCompactMode() + + if config.IS_CONFIG_WINDOW_COMPACT_MODE is True: self.enableConfigWindowCompactMode() vrct_gui.config_window.setting_box_compact_mode_switch_box.select() diff --git a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py index de15cc89..83585e98 100644 --- a/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py +++ b/vrct_gui/main_window/widgets/create_minimize_sidebar_button.py @@ -67,9 +67,4 @@ def createMinimizeSidebarButton(settings, main_window, view_variable): main_window.minimize_sidebar_button_container__for_opening.grid(row=0, column=0, sticky="nsw") main_window.minimize_sidebar_button_container__for_closing.grid(row=0, column=0, sticky="nsw") - main_window.minimize_sidebar_button_container__for_opening.grid_remove() - main_window.minimize_sidebar_button_container__for_closing.grid_remove() - if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - main_window.minimize_sidebar_button_container__for_opening.grid() - else: - main_window.minimize_sidebar_button_container__for_closing.grid() \ No newline at end of file + main_window.minimize_sidebar_button_container__for_opening.grid_remove() \ No newline at end of file diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 09c1a58a..4605bef4 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -26,12 +26,7 @@ def createSidebar(settings, main_window, view_variable): main_window.sidebar_bg_container.grid(row=0, column=0, sticky="nsew") main_window.sidebar_compact_mode_bg_container.grid(row=0, column=0, sticky="nsew") - main_window.sidebar_bg_container.grid_remove() main_window.sidebar_compact_mode_bg_container.grid_remove() - if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE: - main_window.sidebar_compact_mode_bg_container.grid() - else: - main_window.sidebar_bg_container.grid() # Config Button From 1b5515cc95d66ac368aae19f2747911ec6fcb4f9 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 14:56:23 +0900 Subject: [PATCH 283/355] =?UTF-8?q?[bugfix]=20Main=20Window:=20=E3=82=B5?= =?UTF-8?q?=E3=82=A4=E3=83=89=E3=83=90=E3=83=BC=E3=82=B3=E3=83=B3=E3=83=91?= =?UTF-8?q?=E3=82=AF=E3=83=88=E3=83=A2=E3=83=BC=E3=83=89=E3=81=AB=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=80=81VRCT=E3=82=92=E7=B5=82=E4=BA=86=E3=81=97?= =?UTF-8?q?=E3=80=81=E6=AC=A1=E5=9B=9E=E8=B5=B7=E5=8B=95=E6=99=82=E3=81=AB?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=91=E3=82=AF=E3=83=88=E3=83=A2=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=81=A7=E3=81=AEroot=20geometry=E8=A8=88=E7=AE=97?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=82=8B=E3=81=9F=E3=82=81Window=E3=81=8C?= =?UTF-8?q?=E5=B0=8F=E3=81=95=E3=81=8F=E3=81=AA=E3=82=8B=E3=81=AE=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 5 ----- vrct_gui/vrct_gui.py | 2 ++ 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/view.py b/view.py index 49f05428..76d79047 100644 --- a/view.py +++ b/view.py @@ -446,11 +446,6 @@ class View(): self.view_variable.CALLBACK_SET_OSC_PORT = config_window_registers.get("callback_set_osc_port", None) # The initial processing after registration. - - if config.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: - vrct_gui._enableMainWindowSidebarCompactMode() - - if config.IS_CONFIG_WINDOW_COMPACT_MODE is True: self.enableConfigWindowCompactMode() vrct_gui.config_window.setting_box_compact_mode_switch_box.select() diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 3b0ce31f..38335495 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -46,6 +46,8 @@ class VRCT_GUI(CTk): self.attributes("-alpha", 0) self.deiconify() setGeometryToCenterOfScreen(root_widget=self) + if self._view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: + self._enableMainWindowSidebarCompactMode() fadeInAnimation(self, steps=5, interval=0.008) def _createGUI(self, settings, view_variable): From a49f382b161db79be4a05da616420776375f8c81 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 15:49:07 +0900 Subject: [PATCH 284/355] [Update] remove the items that is about startup_osc_enabled_check in localize --- locales/en.yml | 3 --- locales/ja.yml | 3 --- 2 files changed, 6 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 1ee08a3c..88aa52fe 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -97,9 +97,6 @@ config_window: send_message_to_vrc: label: Send Message To VRChat desc: There is a way to use it without sending messages to VRChat. That is not covered by support, though. - startup_osc_enabled_check: - label: Check If OSC Is Enabled At Startup - desc: Every time VRCT is started up, your character in VRChat moves forward ever so slightly. If your character is seated, they might even stand up. Unfortunately, this is the only method we've found to check if OSC is enabled at startup... Sorry about that. (Remember to turn on OSC yourself when you want to send messages to VRChat.) osc_ip_address: label: OSC IP Address diff --git a/locales/ja.yml b/locales/ja.yml index 4fe7887b..ed424fd0 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -99,9 +99,6 @@ config_window: send_message_to_vrc: label: VRChatにメッセージを送信する desc: VRChatにメッセージを送信せずに使う方法があります。サポート対象外ですが。 - startup_osc_enabled_check: - label: 起動時にOSCが有効になっているか確認する - desc: 起動時に毎回、キャラクターがほんの少し前進します。もしsit判定などある場所に座っている場合、立ってしまうかもしれません。残念ながら今のところ私達はOSCがVRChat側で有効になっているか確認する方法がこれしか見つけられていません。ごめんね。(このチェック機能をオフにする場合、自分でOSCをオンにすることを忘れないでね。) osc_ip_address: label: OSC IP Address From 0ea376df10f68027308f55a5cc5e25b456aadbb9 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 16:08:01 +0900 Subject: [PATCH 285/355] =?UTF-8?q?[bugfix]=20controller.py:=20=E5=90=84?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E9=A0=85=E7=9B=AE=E3=80=81Entry=E7=B3=BB?= =?UTF-8?q?=E3=81=A7=E7=A9=BA=E6=96=87=E5=AD=97=E3=82=92=E5=8F=97=E3=81=91?= =?UTF-8?q?=E5=8F=96=E3=81=A3=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AF=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E3=81=A7=E3=81=AF=E3=81=AA=E3=81=8F=E5=87=A6?= =?UTF-8?q?=E7=90=86=E3=82=92=E3=82=B9=E3=83=AB=E3=83=BC(return)=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=80=82=20=E3=81=93?= =?UTF-8?q?=E3=82=8C=E3=81=AB=E3=82=88=E3=82=8AUI=E3=81=A8=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=81=AF=E3=80=81=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC?= =?UTF-8?q?=E3=81=8Cdelete=20key=E3=82=92=E6=8A=BC=E3=81=97=E3=81=A6?= =?UTF-8?q?=E4=B8=AD=E8=BA=AB=E3=82=92=E7=A9=BA=E3=81=AB=E3=81=97=E3=81=9F?= =?UTF-8?q?=E6=99=82=E3=81=AE=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=A1=E3=83=83?= =?UTF-8?q?=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E5=87=BA=E3=81=95=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/controller.py b/controller.py index a3b77adb..3164b0f7 100644 --- a/controller.py +++ b/controller.py @@ -396,6 +396,7 @@ def callbackSetMicDevice(value): def callbackSetMicEnergyThreshold(value): print("callbackSetMicEnergyThreshold", value) + if value == "": return try: value = int(value) if 0 <= value and value <= config.MAX_MIC_ENERGY_THRESHOLD: @@ -431,6 +432,7 @@ def callbackCheckMicThreshold(is_turned_on): def callbackSetMicRecordTimeout(value): print("callbackSetMicRecordTimeout", value) + if value == "": return try: value = int(value) if 0 <= value and value <= config.INPUT_MIC_PHRASE_TIMEOUT: @@ -444,6 +446,7 @@ def callbackSetMicRecordTimeout(value): def callbackSetMicPhraseTimeout(value): print("callbackSetMicPhraseTimeout", value) + if value == "": return try: value = int(value) if 0 <= value and value >= config.INPUT_MIC_RECORD_TIMEOUT: @@ -457,6 +460,7 @@ def callbackSetMicPhraseTimeout(value): def callbackSetMicMaxPhrases(value): print("callbackSetMicMaxPhrases", value) + if value == "": return try: value = int(value) if 0 <= value: @@ -491,6 +495,7 @@ def callbackSetSpeakerDevice(value): def callbackSetSpeakerEnergyThreshold(value): print("callbackSetSpeakerEnergyThreshold", value) + if value == "": return try: value = int(value) if 0 <= value and value <= config.MAX_SPEAKER_ENERGY_THRESHOLD: @@ -527,6 +532,7 @@ def callbackCheckSpeakerThreshold(is_turned_on): def callbackSetSpeakerRecordTimeout(value): print("callbackSetSpeakerRecordTimeout", value) + if value == "": return try: value = int(value) if 0 <= value and value <= config.INPUT_SPEAKER_PHRASE_TIMEOUT: @@ -540,6 +546,7 @@ def callbackSetSpeakerRecordTimeout(value): def callbackSetSpeakerPhraseTimeout(value): print("callbackSetSpeakerPhraseTimeout", value) + if value == "": return try: value = int(value) if 0 <= value and value >= config.INPUT_SPEAKER_RECORD_TIMEOUT: @@ -553,6 +560,7 @@ def callbackSetSpeakerPhraseTimeout(value): def callbackSetSpeakerMaxPhrases(value): print("callbackSetSpeakerMaxPhrases", value) + if value == "": return try: value = int(value) if 0 <= value: @@ -599,10 +607,12 @@ def callbackSetEnableSendMessageToVrc(value): # Advanced Settings Tab def callbackSetOscIpAddress(value): + if value == "": return print("callbackSetOscIpAddress", str(value)) config.OSC_IP_ADDRESS = str(value) def callbackSetOscPort(value): + if value == "": return print("callbackSetOscPort", int(value)) config.OSC_PORT = int(value) From 376f5e7b5ac9b37e6667b6973bfc52a24d274623 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 16:56:49 +0900 Subject: [PATCH 286/355] =?UTF-8?q?[Update]=20=E3=82=BD=E3=83=95=E3=83=88?= =?UTF-8?q?=E3=81=AE=E3=83=90=E3=83=BC=E3=82=B8=E3=83=A7=E3=83=B3=E6=83=85?= =?UTF-8?q?=E5=A0=B1=E3=82=92=E8=A8=AD=E5=AE=9A=E7=94=BB=E9=9D=A2=E3=81=AB?= =?UTF-8?q?=E7=A7=BB=E5=8B=95=E3=80=81UI=20Size=E5=A4=89=E6=9B=B4=E3=80=81?= =?UTF-8?q?=E8=89=B2=E3=81=AE=E5=A4=89=E6=95=B0=E5=8C=96=E3=80=81=E8=A1=A8?= =?UTF-8?q?=E8=A8=98=E3=82=92=E8=8B=B1=E8=AA=9E=E3=81=A8=E6=97=A5=E6=9C=AC?= =?UTF-8?q?=E8=AA=9E=E3=81=AB=E5=AF=BE=E5=BF=9C=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 1 + locales/ja.yml | 1 + view.py | 2 +- vrct_gui/config_window/ConfigWindow.py | 16 +++++++++++++++- vrct_gui/main_window/createMainWindowWidgets.py | 16 ---------------- vrct_gui/ui_managers/ColorThemeManager.py | 1 + vrct_gui/ui_managers/UiScalingManager.py | 1 + 7 files changed, 20 insertions(+), 18 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 88aa52fe..126618bc 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -27,6 +27,7 @@ selectable_language_window: config_window: config_title: Settings compact_mode: Compact Mode + version: version %{version} side_menu_labels: appearance: Appearance translation: Translation diff --git a/locales/ja.yml b/locales/ja.yml index ed424fd0..ef2b2e3d 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -28,6 +28,7 @@ selectable_language_window: config_window: config_title: 設定 compact_mode: コンパクトモード + version: バージョン %{version} side_menu_labels: appearance: デザイン translation: 翻訳 diff --git a/view.py b/view.py index 76d79047..66ec769e 100644 --- a/view.py +++ b/view.py @@ -135,7 +135,7 @@ class View(): ACTIVE_SETTING_BOX_TAB_ATTR_NAME="side_menu_tab_appearance", CALLBACK_SELECTED_SETTING_BOX_TAB=None, VAR_ERROR_MESSAGE=StringVar(value=""), - VAR_VERSION=StringVar(value=config.VERSION), + VAR_VERSION=StringVar(value=i18n.t("config_window.version", version=config.VERSION)), VAR_CONFIG_WINDOW_TITLE=StringVar(value=i18n.t("config_window.config_title")), VAR_CONFIG_WINDOW_COMPACT_MODE_LABEL=StringVar(value=i18n.t("config_window.compact_mode")), diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index d7be1492..ee88da4b 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -1,7 +1,7 @@ from .widgets import createConfigWindowTitle, createSideMenuAndSettingsBoxContainers, createSettingBoxTopBar -from customtkinter import CTkToplevel, CTkFrame +from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont from ..ui_utils import getImagePath, getLatestWidth, getLatestHeight from utils import isEven @@ -49,4 +49,18 @@ class ConfigWindow(CTkToplevel): l_width = getLatestWidth(self.side_menu_bg_container) + + # VRCT Now Version Label(Tmp) + version_label = CTkLabel( + self.side_menu_bg_container, + textvariable=self._view_variable.VAR_VERSION, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.NOW_VERSION_FONT_SIZE, weight="normal"), + anchor="w", + text_color=self.settings.ctm.NOW_VERSION_TEXT_COLOR, + ) + version_label.place(relx=0.05, rely=0.99, anchor="sw") + + self.bind_all("", lambda event: event.widget.focus_set(), "+") \ No newline at end of file diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 25021800..14b0eda3 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -43,22 +43,6 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): # Main Top Bar Container - Right Side # start from 3 main_topbar_column=3 - - # VRCT Now Version Label(Tmp) - vrct_gui.version_label = CTkLabel( - vrct_gui.main_topbar_container, - textvariable=view_variable.VAR_VERSION, - height=0, - corner_radius=0, - font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.UPDATE_AVAILABLE_BUTTON_FONT_SIZE, weight="normal"), - anchor="e", - text_color="white", - ) - vrct_gui.version_label.grid(row=0, column=main_topbar_column, padx=(0,8)) - main_topbar_column+=1 - - - # Restart Button(Tmp) vrct_gui.restart_button_container = createButtonWithImage( parent_widget=vrct_gui.main_topbar_container, diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 541da271..0f46aa14 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -284,6 +284,7 @@ class ColorThemeManager(): self.config_window.SIDE_MENU_SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR + self.config_window.NOW_VERSION_TEXT_COLOR = self.DARK_300_COLOR # Error Message Window for Config Window # The color code [#bb4448] is a mixture of [#a9555c] and [#cc3333] (for a redder shade). diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index e5c7a41c..1d73764f 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -169,6 +169,7 @@ class UiScalingManager(): self.config_window.SIDE_MENU_LABELS_IPADY = self._calculateUiSize(8) self.config_window.SIDE_MENU_LABELS_FONT_SIZE = self._calculateUiSize(18) + self.config_window.NOW_VERSION_FONT_SIZE = self._calculateUiSize(12) # Top bar Main self.config_window.TOP_BAR_MAIN__TITLE_FONT_SIZE = self._calculateUiSize(22) From 708bcea533cb9c3ff44ae86309922e74be9d6999 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 17:32:23 +0900 Subject: [PATCH 287/355] [bugfix] Config Window: Setting Box Labels. fix the position to the center that was a bit above before. --- .../setting_box_containers/_SettingBoxGenerator.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index 692fb046..db613ad7 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -58,6 +58,7 @@ class _SettingBoxGenerator(): setting_box_labels_frame = CTkFrame(setting_box_frame_wrapper, corner_radius=0, fg_color=self.settings.ctm.SB__BG_COLOR, width=0, height=0) setting_box_labels_frame.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") + setting_box_labels_frame.grid_rowconfigure((0,3), weight=1) setting_box_label = CTkLabel( setting_box_labels_frame, textvariable=for_var_label_text, @@ -66,7 +67,7 @@ class _SettingBoxGenerator(): font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__LABEL_FONT_SIZE, weight="normal"), text_color=self.settings.ctm.LABELS_TEXT_COLOR ) - setting_box_label.grid(row=0, column=0, padx=0, pady=0, sticky="ew") + setting_box_label.grid(row=1, column=0, padx=0, pady=0, sticky="ew") self.config_window.sb__widgets[sb__attr_name].label_widget = setting_box_label @@ -81,7 +82,7 @@ class _SettingBoxGenerator(): font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.SB__DESC_FONT_SIZE, weight="normal"), text_color=self.settings.ctm.LABELS_DESC_TEXT_COLOR ) - setting_box_desc.grid(row=1, column=0, padx=0, pady=(self.settings.uism.SB__DESC_TOP_PADY,0), sticky="ew") + setting_box_desc.grid(row=2, column=0, padx=0, pady=(self.settings.uism.SB__DESC_TOP_PADY,0), sticky="ew") self.config_window.additional_widgets.append(setting_box_desc) self.config_window.sb__widgets[sb__attr_name].desc_widget=setting_box_desc else: From ca692ce9bfea655b9176efddbbbb2096dc5fac4d Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 13 Oct 2023 17:53:17 +0900 Subject: [PATCH 288/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Remove=20Unused?= =?UTF-8?q?=20Files?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 13 - test_model/config.py | 511 ---------- test_model/img/app.ico | Bin 67646 -> 0 bytes test_model/img/config-icon-white.png | Bin 5490 -> 0 bytes test_model/img/info-icon-white.png | Bin 4945 -> 0 bytes test_model/img/xsoverlay.png | Bin 1209 -> 0 bytes test_model/languages.py | 6 - test_model/locales.yml | 193 ---- test_model/main.py | 503 ---------- test_model/utils.py | 106 -- test_model/window_config.py | 1348 -------------------------- test_model/window_information.py | 161 --- window_help_and_info.py | 154 --- 13 files changed, 2995 deletions(-) delete mode 100644 test_model/config.py delete mode 100644 test_model/img/app.ico delete mode 100644 test_model/img/config-icon-white.png delete mode 100644 test_model/img/info-icon-white.png delete mode 100644 test_model/img/xsoverlay.png delete mode 100644 test_model/languages.py delete mode 100644 test_model/locales.yml delete mode 100644 test_model/main.py delete mode 100644 test_model/utils.py delete mode 100644 test_model/window_config.py delete mode 100644 test_model/window_information.py delete mode 100644 window_help_and_info.py diff --git a/.gitignore b/.gitignore index 7ef8f9f7..678a4536 100644 --- a/.gitignore +++ b/.gitignore @@ -1,20 +1,7 @@ build/ dist/ config.json -VRC_ChatBox_translator.spec memo.txt -app/ -booth/ -pyinstaller/ -VRCT.build/ -VRCT.dist/ -VRCT.onefile-build/ -VRCT.exe VRCT.spec -deepl-translate/ -translators/ -test/ *.pyc -.vscode/ -lib/ logs/ \ No newline at end of file diff --git a/test_model/config.py b/test_model/config.py deleted file mode 100644 index cea811a5..00000000 --- a/test_model/config.py +++ /dev/null @@ -1,511 +0,0 @@ -import os -import sys -sys.path.append(os.path.join(os.path.dirname(__file__), '..')) - -from json import load, dump -import inspect -from os import path as os_path -from json import load as json_load -from json import dump as json_dump -import tkinter as tk -from tkinter import font -from languages import selectable_languages -from models.translation.translation_languages import translatorEngine, translation_lang -from models.transcription.transcription_languages import transcription_lang -from models.transcription.transcription_utils import getInputDevices, getOutputDevices, getDefaultInputDevice, getDefaultOutputDevice - -def saveJson(path, key, value): - with open(path, "r") as fp: - json_data = load(fp) - json_data[key] = value - with open(path, "w") as fp: - dump(json_data, fp, indent=4) - -class Config: - _instance = None - - def __new__(cls): - if cls._instance is None: - cls._instance = super(Config, cls).__new__(cls) - cls._instance.init_config() - cls._instance.load_config() - return cls._instance - - @property - def VERSION(self): - return self._VERSION - - @property - def PATH_CONFIG(self): - return self._PATH_CONFIG - - @property - def ENABLE_TRANSLATION(self): - return self._ENABLE_TRANSLATION - - @ENABLE_TRANSLATION.setter - def ENABLE_TRANSLATION(self, value): - if type(value) is bool: - self._ENABLE_TRANSLATION = value - - @property - def ENABLE_TRANSCRIPTION_SEND(self): - return self._ENABLE_TRANSCRIPTION_SEND - - @ENABLE_TRANSCRIPTION_SEND.setter - def ENABLE_TRANSCRIPTION_SEND(self, value): - if type(value) is bool: - self._ENABLE_TRANSCRIPTION_SEND = value - - @property - def ENABLE_TRANSCRIPTION_RECEIVE(self): - return self._ENABLE_TRANSCRIPTION_RECEIVE - - @ENABLE_TRANSCRIPTION_RECEIVE.setter - def ENABLE_TRANSCRIPTION_RECEIVE(self, value): - if type(value) is bool: - self._ENABLE_TRANSCRIPTION_RECEIVE = value - - @property - def ENABLE_FOREGROUND(self): - return self._ENABLE_FOREGROUND - - @ENABLE_FOREGROUND.setter - def ENABLE_FOREGROUND(self, value): - if type(value) is bool: - self._ENABLE_FOREGROUND = value - - @property - def TRANSPARENCY(self): - return self._TRANSPARENCY - - @TRANSPARENCY.setter - def TRANSPARENCY(self, value): - if type(value) is int and 0 <= value <= 100: - self._TRANSPARENCY = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def APPEARANCE_THEME(self): - return self._APPEARANCE_THEME - - @APPEARANCE_THEME.setter - def APPEARANCE_THEME(self, value): - if value in ["Light", "Dark", "System"]: - self._APPEARANCE_THEME = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def UI_SCALING(self): - return self._UI_SCALING - - @UI_SCALING.setter - def UI_SCALING(self, value): - if value in ["80%", "90%", "100%", "110%", "120%"]: - self._UI_SCALING = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def FONT_FAMILY(self): - return self._FONT_FAMILY - - @FONT_FAMILY.setter - def FONT_FAMILY(self, value): - root = tk.Tk() - root.withdraw() - if value in list(font.families()): - self._FONT_FAMILY = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - root.destroy() - - @property - def UI_LANGUAGE(self): - return self._UI_LANGUAGE - - @UI_LANGUAGE.setter - def UI_LANGUAGE(self, value): - if value in list(selectable_languages.keys()): - self._UI_LANGUAGE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def CHOICE_TRANSLATOR(self): - return self._CHOICE_TRANSLATOR - - @CHOICE_TRANSLATOR.setter - def CHOICE_TRANSLATOR(self, value): - if value in translatorEngine: - self._CHOICE_TRANSLATOR = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_SOURCE_LANG(self): - return self._INPUT_SOURCE_LANG - - @INPUT_SOURCE_LANG.setter - def INPUT_SOURCE_LANG(self, value): - if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): - self._INPUT_SOURCE_LANG = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_TARGET_LANG(self): - return self._INPUT_TARGET_LANG - - @INPUT_TARGET_LANG.setter - def INPUT_TARGET_LANG(self, value): - if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): - self._INPUT_TARGET_LANG = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def OUTPUT_SOURCE_LANG(self): - return self._OUTPUT_SOURCE_LANG - - @OUTPUT_SOURCE_LANG.setter - def OUTPUT_SOURCE_LANG(self, value): - if value in list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys()): - self._OUTPUT_SOURCE_LANG = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def OUTPUT_TARGET_LANG(self): - return self._OUTPUT_TARGET_LANG - - @OUTPUT_TARGET_LANG.setter - def OUTPUT_TARGET_LANG(self, value): - if value in list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys()): - self._OUTPUT_TARGET_LANG = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def CHOICE_MIC_HOST(self): - return self._CHOICE_MIC_HOST - - @CHOICE_MIC_HOST.setter - def CHOICE_MIC_HOST(self, value): - if value in [host for host in getInputDevices().keys()]: - self._CHOICE_MIC_HOST = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def CHOICE_MIC_DEVICE(self): - return self._CHOICE_MIC_DEVICE - - @CHOICE_MIC_DEVICE.setter - def CHOICE_MIC_DEVICE(self, value): - if value in [device["name"] for device in getInputDevices()[self.CHOICE_MIC_HOST]]: - self._CHOICE_MIC_DEVICE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_MIC_VOICE_LANGUAGE(self): - return self._INPUT_MIC_VOICE_LANGUAGE - - @INPUT_MIC_VOICE_LANGUAGE.setter - def INPUT_MIC_VOICE_LANGUAGE(self, value): - if value in list(transcription_lang.keys()): - self._INPUT_MIC_VOICE_LANGUAGE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_MIC_ENERGY_THRESHOLD(self): - return self._INPUT_MIC_ENERGY_THRESHOLD - - @INPUT_MIC_ENERGY_THRESHOLD.setter - def INPUT_MIC_ENERGY_THRESHOLD(self, value): - if type(value) is int: - self._INPUT_MIC_ENERGY_THRESHOLD = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self): - return self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD - - @INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD.setter - def INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD(self, value): - if type(value) is bool: - self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_MIC_RECORD_TIMEOUT(self): - return self._INPUT_MIC_RECORD_TIMEOUT - - @INPUT_MIC_RECORD_TIMEOUT.setter - def INPUT_MIC_RECORD_TIMEOUT(self, value): - if type(value) is int: - self._INPUT_MIC_RECORD_TIMEOUT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_MIC_PHRASE_TIMEOUT(self): - return self._INPUT_MIC_PHRASE_TIMEOUT - - @INPUT_MIC_PHRASE_TIMEOUT.setter - def INPUT_MIC_PHRASE_TIMEOUT(self, value): - if type(value) is int: - self._INPUT_MIC_PHRASE_TIMEOUT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_MIC_MAX_PHRASES(self): - return self._INPUT_MIC_MAX_PHRASES - - @INPUT_MIC_MAX_PHRASES.setter - def INPUT_MIC_MAX_PHRASES(self, value): - if type(value) is int: - self._INPUT_MIC_MAX_PHRASES = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_MIC_WORD_FILTER(self): - return self._INPUT_MIC_WORD_FILTER - - @INPUT_MIC_WORD_FILTER.setter - def INPUT_MIC_WORD_FILTER(self, value): - if type(value) is list: - self._INPUT_MIC_WORD_FILTER = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def CHOICE_SPEAKER_DEVICE(self): - return self._CHOICE_SPEAKER_DEVICE - - @CHOICE_SPEAKER_DEVICE.setter - def CHOICE_SPEAKER_DEVICE(self, value): - if value in [device["name"] for device in getOutputDevices()]: - speaker_device = [device for device in getOutputDevices() if device["name"] == value][0] - if getDefaultOutputDevice()["index"] == speaker_device["index"]: - self._CHOICE_SPEAKER_DEVICE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_SPEAKER_VOICE_LANGUAGE(self): - return self._INPUT_SPEAKER_VOICE_LANGUAGE - - @INPUT_SPEAKER_VOICE_LANGUAGE.setter - def INPUT_SPEAKER_VOICE_LANGUAGE(self, value): - if value in list(transcription_lang.keys()): - self._INPUT_SPEAKER_VOICE_LANGUAGE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_SPEAKER_ENERGY_THRESHOLD(self): - return self._INPUT_SPEAKER_ENERGY_THRESHOLD - - @INPUT_SPEAKER_ENERGY_THRESHOLD.setter - def INPUT_SPEAKER_ENERGY_THRESHOLD(self, value): - if type(value) is int: - self._INPUT_SPEAKER_ENERGY_THRESHOLD = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self): - return self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD - - @INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD.setter - def INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD(self, value): - if type(value) is bool: - self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_SPEAKER_RECORD_TIMEOUT(self): - return self._INPUT_SPEAKER_RECORD_TIMEOUT - - @INPUT_SPEAKER_RECORD_TIMEOUT.setter - def INPUT_SPEAKER_RECORD_TIMEOUT(self, value): - if type(value) is int: - self._INPUT_SPEAKER_RECORD_TIMEOUT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_SPEAKER_PHRASE_TIMEOUT(self): - return self._INPUT_SPEAKER_PHRASE_TIMEOUT - - @INPUT_SPEAKER_PHRASE_TIMEOUT.setter - def INPUT_SPEAKER_PHRASE_TIMEOUT(self, value): - if type(value) is int: - self._INPUT_SPEAKER_PHRASE_TIMEOUT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def INPUT_SPEAKER_MAX_PHRASES(self): - return self._INPUT_SPEAKER_MAX_PHRASES - - @INPUT_SPEAKER_MAX_PHRASES.setter - def INPUT_SPEAKER_MAX_PHRASES(self, value): - if type(value) is int: - self._INPUT_SPEAKER_MAX_PHRASES = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def OSC_IP_ADDRESS(self): - return self._OSC_IP_ADDRESS - - @OSC_IP_ADDRESS.setter - def OSC_IP_ADDRESS(self, value): - if type(value) is str: - self._OSC_IP_ADDRESS = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def OSC_PORT(self): - return self._OSC_PORT - - @OSC_PORT.setter - def OSC_PORT(self, value): - if type(value) is int: - self._OSC_PORT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def AUTH_KEYS(self): - return self._AUTH_KEYS - - @AUTH_KEYS.setter - def AUTH_KEYS(self, value): - if type(value) is dict and set(value.keys()) == set(self.AUTH_KEYS.keys()): - for key, value in value.items(): - if type(value) is str: - self._AUTH_KEYS[key] = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.AUTH_KEYS) - - @property - def MESSAGE_FORMAT(self): - return self._MESSAGE_FORMAT - - @MESSAGE_FORMAT.setter - def MESSAGE_FORMAT(self, value): - if type(value) is str: - self._MESSAGE_FORMAT = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def ENABLE_AUTO_CLEAR_CHATBOX(self): - return self._ENABLE_AUTO_CLEAR_CHATBOX - - @ENABLE_AUTO_CLEAR_CHATBOX.setter - def ENABLE_AUTO_CLEAR_CHATBOX(self, value): - if type(value) is bool: - self._ENABLE_AUTO_CLEAR_CHATBOX = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def ENABLE_NOTICE_XSOVERLAY(self): - return self._ENABLE_NOTICE_XSOVERLAY - - @ENABLE_NOTICE_XSOVERLAY.setter - def ENABLE_NOTICE_XSOVERLAY(self, value): - if type(value) is bool: - self._ENABLE_NOTICE_XSOVERLAY = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - - @property - def ENABLE_OSC(self): - return self._ENABLE_OSC - - @ENABLE_OSC.setter - def ENABLE_OSC(self, value): - if type(value) is bool: - self._ENABLE_OSC = value - - @property - def UPDATE_FLAG(self): - return self._UPDATE_FLAG - - @UPDATE_FLAG.setter - def UPDATE_FLAG(self, value): - if type(value) is bool: - self._UPDATE_FLAG = value - - @property - def GITHUB_URL(self): - return self._GITHUB_URL - - @property - def BREAK_KEYSYM_LIST(self): - return self._BREAK_KEYSYM_LIST - - @property - def MAX_MIC_ENERGY_THRESHOLD(self): - return self._MAX_MIC_ENERGY_THRESHOLD - - @property - def MAX_SPEAKER_ENERGY_THRESHOLD(self): - return self._MAX_SPEAKER_ENERGY_THRESHOLD - - def init_config(self): - self._VERSION = "1.3.2" - self._PATH_CONFIG = os.path.join(os.path.dirname(__file__), 'config.json') - self._ENABLE_TRANSLATION = False - self._ENABLE_TRANSCRIPTION_SEND = False - self._ENABLE_TRANSCRIPTION_RECEIVE = False - self._ENABLE_FOREGROUND = False - self._TRANSPARENCY = 100 - self._APPEARANCE_THEME = "System" - self._UI_SCALING = "100%" - self._FONT_FAMILY = "Yu Gothic UI" - self._UI_LANGUAGE = "en" - self._CHOICE_TRANSLATOR = translatorEngine[0] - self._INPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[0] - self._INPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[1] - self._OUTPUT_SOURCE_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["source"].keys())[1] - self._OUTPUT_TARGET_LANG = list(translation_lang[self.CHOICE_TRANSLATOR]["target"].keys())[0] - self._CHOICE_MIC_HOST = getDefaultInputDevice()["host"]["name"] - self._CHOICE_MIC_DEVICE = getDefaultInputDevice()["device"]["name"] - self._INPUT_MIC_VOICE_LANGUAGE = list(transcription_lang.keys())[0] - self._INPUT_MIC_ENERGY_THRESHOLD = 300 - self._INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = True - self._INPUT_MIC_RECORD_TIMEOUT = 3 - self._INPUT_MIC_PHRASE_TIMEOUT = 3 - self._INPUT_MIC_MAX_PHRASES = 10 - self._INPUT_MIC_WORD_FILTER = [] - self._CHOICE_SPEAKER_DEVICE = getDefaultOutputDevice()["name"] - self._INPUT_SPEAKER_VOICE_LANGUAGE = list(transcription_lang.keys())[1] - self._INPUT_SPEAKER_ENERGY_THRESHOLD = 300 - self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = True - self._INPUT_SPEAKER_RECORD_TIMEOUT = 3 - self._INPUT_SPEAKER_PHRASE_TIMEOUT = 3 - self._INPUT_SPEAKER_MAX_PHRASES = 10 - self._OSC_IP_ADDRESS = "127.0.0.1" - self._OSC_PORT = 9000 - self._AUTH_KEYS = { - "DeepL(web)": None, - "DeepL(auth)": None, - "Bing(web)": None, - "Google(web)": None, - } - self._MESSAGE_FORMAT = "[message]([translation])" - self._ENABLE_AUTO_CLEAR_CHATBOX = False - self._ENABLE_NOTICE_XSOVERLAY = False - self._ENABLE_OSC = False - self._UPDATE_FLAG = False - self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" - self._BREAK_KEYSYM_LIST = [ - "Delete", "Select", "Up", "Down", "Next", "End", "Print", - "Prior","Insert","Home", "Left", "Clear", "Right", "Linefeed" - ] - self._MAX_MIC_ENERGY_THRESHOLD = 2000 - self._MAX_SPEAKER_ENERGY_THRESHOLD = 4000 - - def load_config(self): - if os_path.isfile(self.PATH_CONFIG) is not False: - with open(self.PATH_CONFIG, 'r') as fp: - config = json_load(fp) - - for key in config.keys(): - setattr(self, key, config[key]) - - with open(self.PATH_CONFIG, 'w') as fp: - setter_methods = [ - name for name, obj in vars(type(self)).items() - if isinstance(obj, property) and obj.fset is not None - ] - config = {} - for method in setter_methods: - config[method] = getattr(self, method) - json_dump(config, fp, indent=4) - -config = Config() \ No newline at end of file diff --git a/test_model/img/app.ico b/test_model/img/app.ico deleted file mode 100644 index eca32ce73e86bb5d9cf41456997c2c74c77c7c1a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 67646 zcmeI5d5{!kn#K#7OHhz;VgL_d9Y^p8yf*?xK-7(JW@2OJA4MGuG`v<(R}CFqT}0P$ zb?^v7aRzn7*wyt6Zw*gqKw}1Inx>)8YN`+Vf^M+8>ASnC_V-|yk(E?eWmTniX8U~| zPgj0B|`F>aCn}NUx{#L9A@PB4t_P7y&Ujzby*??Wzd2qir4@SP*F#nANhFk(8 zMxdO5z(_C-j8?>ipOgf~Qrkt~kKlT6Di{My_-ROBI@SCNM8E;?30MNAVjB(`2ewTZ zo)QqhzXV%>_>T_!*6{mRumGrCFk$#g;0UVr-=GVK{}>d{1NVYo0re3~7=99v4LAqn zgBbVL-~H@Yo3I%C445_nAq=x{IQ3Z$>^2~#2mKsVo3IM}GcaueLKtS@Z0fTK#5hm% zEB)94ZUr+y4lrSONI*8=KF|RYaUa{Kexllhx4>_KX%i5_FbQW-kBuP4b+TXSK?Qge zsQ+Nv1cc!8FrMnJn|G^+608)@i3F}eF8N98{;yYU+F;|_zRd1CIAz> zD*@SnrJxC9!@K=jjT5xytholY3C0#61doPeDc3t7+kSvu2Nc&l^7}w-!q0&T-jjgF z@RxxGkch)<_erlpKx>P0!B}8|wRWHUb?qH@-0}6X#~xcfYSgIiRM#XpF8iT2;m_bSVAd89!Vm}b(SHXjK{6faUAS=J zj8l%x@BpeR+Vh_3{5H_IK!oM~n?CRRF zV@KI7x7@Pr=%bHrKo8RGA9|{;gYY9g)i`()xD8AJPQpo?^fw?q^FuuomJv?FXPJJ`(Y^x*4z$rGgyREXvIGAP z>}%heU%3RRk2fs1kJSSk(DBei4^?0T4%j(Q5RbGI-g)Pp(4s|)f?WG8=?u3vA&)wp z2dv@eM6?Dr-1J?#+&^p9tWbV_{!aL|js+6X|NX8m<`3!~d+f1_*|TR?VMlz~2d3_c z?q&6LHIFe7#Dc~M>gy*u-?~rwq3bmU^9#DMfG+ddQ-Ocd+xcX5qlMa z%T)dMI?nkuP`j^rp8o*J^rjo`i?E?3ot>S%Jv}|)?(S}1fqOX&mrm`!T^|n4BUA5w zuhv_fz)e?fZf+6d80kPiu-b0xwrto9K=VRs2UY^>@wn}=W5@1)^wCH25lW|BFedIT z383O#_uqeiHJqig0e^&Z-TRY(#)Wa5TjQ*qHN5oFOB<wO~&x=Q{r4Iw&C9;LHaZq!@p07bE(*df0qR5(_{?)J|)hjVjKQl5~NR) zG5q_KIG2iT_;*Q=K265(?^EJjDz@R@B|-W$8Ngi2 zr}7tuI)A&5nrvj@RsQw*oqY1`D=I2#{_L~Q3SWQy_04a+_0}f&Y7B)!`+0w@nTikU zt0NnsaINz1Yi(_9!5`d#b?equzVXHzTi$&0&CToAuiu4_h!*_8$WO!%`HoW<_X7JX zDk@r(r}9=AR2G$~rKP2nGD;V+DTC+ur%v6qwY8y_Uw*mxiYu-tIpd5o>L*T|Sjjsm z<-D6+j=zoF_}~fQyLB6~2+3DlmNMY-xDeJmy!dt4b^Z0%@0vSzZXf%m^W@%|5fH~M%dwfy3XFV4fql6;fN|6-zdjr;Ml&^CSg^ve70 zyRW#euCCQC#7yG&_vzlcb!!=ZU4lm)b(G$F*1NBXe0qe*zXKnUjkn)^dlmO%HF}$M zyP!0<-|OzW>#l0ju2&v|<*%|Ge)!=vOO`ANZrQRW*x%ouN&cR>|K)e-gAYDvJoVI5 zYp9!39~%9e5(cVMgqpq>BQ9c{e)&XQ6M)sxXUJ?1H?empu+G&#tL-b2B(Lr5<{x;)_ zHlMWQQ!K6eb4?FrFGB}|Y3A<<{%dM#_R!81s~+jrJ$63rMav_PJW_xiQG1+Lpr6@v z<&{?k@c}PC-f77`cK(<#V>*^CTUJJ&E$#g2!?fIX+ig2Z!>Qdlq>Plk>+;JluWW8^ z-j_z@hn#=(wHLp8^0S+Xe^sj-`j0>U_=@e@w-=^Sf1Mh6<&{^q9(m-E`UYg8zJ@iA zWZMz^gEp#vPMYcT2g4gSYzQ86%rWY3BZdOw?6#v!`sm*$L%oV$d0S7x~i7@C+qx4qvM7fZYabCrSu^PM+XCK z_CCfEd0dF3Q*F_gyl+XB~K9+up-=XF|LVrJm?#mZ(HuqjkSGuYHjx_2YEiNvu z!{?jE8p(X%#L`do+sk-$7vop?l6DmEC)&U@CFGxR+h^fFeBy~G?!{lR{MkDRL(PBq z*=L_Ep#RovsY5CmCp+FZXU?43Bz}DDG6}!;-h2NTu>Hx>b$Tpv!U-oF!1tBrrW^(4 zw>nNb=_K9zZs>oMx-`B0_S;*@+({cW)ci;A6}21w-LQS)D1tw$PVQ-^DO{s>DKmORM|D!PWuVCubscz|il)5xM_uO-ae@Ay;RQzvplYhP;RR7_J zAFg+L|1q~)2>)vTlIoSy;{*K5|C%4}|1i3$`3|@AzmK_)x^(P6z8b%Qvj(T~O>tg? z_P4#Dpun;3$elZPwqyTY^CPZrSg}r`F@PWVSO2eKp#SHF{&zEfR=}Kmt+U%d8Z9j? z-9wup`=8C&D^~Vie2DL2KH72n&-ka2Zx84@MXvP!m_utur<~?L)a`$i?r#&{UvX{x z#~e@*+&dlrMwxqQW1XoApS4-bpD|n~Yew4{|2dU^l<{BFjW^!7MPtVlwk4)x$$mT0 z|2*`+&Y}JfIscaVAANr$S=~|{3(uT6Gq{c5uzQozeU+V~HK| z!>u&a39R=v^8LqBY(U2Mpyq#>!`1w6dpddRT-l&I@4T}ZKdc##gD89FWtUx6!Fq5y ze#UCiB8TyxDeWjtH4oBWgY z3A@$a^Zdsy#t>C-uV)mq3e=&SXUiJyzWeUVf&M_U_OOSse$CRQOM|-aDSNuytE`^l zUj@>$0QL3tyI*_lwIbTHLgwacXxnSJ&#Gx-tEWtvQh(-|XO_{{ZsL0zRkWRY_Q%Os zSIMUgaG-BEG=22ZM?sz|(eog6WO^ynI({P!?lU&Wfu4%Ye_>1(UKcP)RV#rj1% z_dz|+e{CnPkn&XCDuc>Gnd*7op^Uj}J^!_rGH05<=lCBWD586jXB^vTQ}-f^uX!GC z9qq$fp3f>qKiafrF0N7%n%N1cVYpX3&^PVGwoBHzU>(m|m11wy&1>VFA1Z&{2mQ)J zd68!c-y~V5GEkOsl}Tk&8Kp0oRq^0eUgh6fC1{THQr8}SHLm}`WNyvWw%spT4`u$! zdw_2@>+c8tZB&@3ixM#WyC_T_CS~~dA!#la+3@e8FnySm;opa(xmaYwzl*~3VN!;F zACl%`kq!SY3e$&48UB4pnu|p?{JSVjA0}n^_aSL67TNIcqA-1!l;Pipq`6pR!@rBd z^kGtle;<_tjWsyMlgAYC! zT7YsZm1KzLdEGa43h3}1W_)PYm zqmC^Dhj4M_H$dPKfF@kLs{diCl``Eg&fW{rV&WKaYrCHP$3v2fwS67Ky}J`dy@ z=l39o_ssbT%meaQGiVzya7=omYvs?|zpv%p|sBnQq1pMZ4y)fk=dwob_R(VO55V7L41iVM;KowpjuUyiW_ z-sU#hz1IVOyTB7*Hb})ir+goN!PXT(A`$(9asTY0_l^1_hj&Ac+p($)nG2r7%!cBp3@VDfq9}Fj%v>EFauA@K-|RU z^w>1PNWe(INWe(INFcEUEbGRx&B%<{)_1@CV{P#fws^oEUl6w=60ju@$cZ~14cHP~ z5qKc(gxtUaTYOSnyvmkAl`X-RKzstkXIK-!A!}TM#N$eM@FQZD3lx9A7GGeA%i(-n zg2dwz6cv{9qbrD86Ht7H<%C?~)F4yEROzvc>{|vAOZ_ zoE6FABW%ZA>1)PkX<|kKMgm3xo|M2L_HRXeF-PRa$8)OU<74e{yM4CXbDO*eLLV`*qf{KDw!J(oB zMX2IK1hGCD1QZpOp{R)9fB~yms36s%6v1~BP;tGk^_FYB{xfSO_uO;7{hhtPv-e&( zJJ>;f6HKO?pirm@0sh_`l9wP++7o2_%dGF>t&T#DGd6 zghDCri`dJ|YcNK`U6yWJ&`vXJ&*R%B#Jy8-?$9>+UU^e*kvKA3(E(Opug>=`WEkp) z6}D3M5^wpYsr+m(Fs?m59PQ*YAX_tMXeeWMUF1ww2~0|VwDko(ByMy3v_U_d0yGzfyua%kpGpbp_dmsz{~4HJV+Er683ud*Ld1oT^pQ= zU2f=>GLL@}f8WQ)W{Fj5&!zOMFU;!e_2_NaS$zC*NXdMYlbK*{Cy8`;rB(UbN!9cIBIzvR{*>iU zfYgl2-i{|dtCI!s*+cTzj`0~f3-;1)!Zp}-OxB}waO2PwyU=vYPI+fQ65jva_1Lzj zu8lt1^|P$Woy!a|Ec(o_#Q|kUWhsg}v3}Mt{=#OMT*o%dhtx zwoKDeO?@yjO}9*;f*zf(+AS)NN59Qh%|HF(3@x#A>Fpj7Zj+u_jPBHXB38RDUXgfg z?rQREzd!C+n&_*C(<&Ve&Dzlyqs5Zb2}c&7th?6@8qn!_*HZUg?CtN(YHT0zxYONd zn2VN`i3WU;6q*nxMDi<~70BdCM0kKN;evRjNQxvL3gx~~DFt}ZAdKOH0-@Lg+gDzN z#R&Nx*f19sktOv4qlEq&WMJ@ypb*}MXdZ))UAVx+UCBfUL?8@cl%g21oT>D{YI&K+ zSTjw)VzdxA+5;QTVq?4{G7v+>Q}INck5U*%#x5|yxXbtuljH3>MuEI}V54AI$|MjJ z3I$$4!AoQU0*S$35Qt;~nT$gaIC;Dn29!9l++IU5%Ha*lc`~6C7D~hz4JW{r#KIm} zEV7RI;Gam!Vtu3+%g0ne^dKk!DS?D15=0`xcndk~6NiwDIrO&{@(`p<2^>%^iIwp{ zpEytq+mEN<^FG>3V`VYgaQHj|7z2tBs2tgq^x2kv0W9`M3ylNKYTOI(_P}cPXYwUHA)l!oa$WgEo-2co zbLCQLI4T#U;22aA3CHI{5a>!J@kw;zIH~}#90tTZP(y`~8V zR3eRvqmyW49D@t+00^W*pbIdLVyR4sWF-(Y-YX3iAEDyXC=>>t0pa){9l%k^h$CE( zM#S;SkSm=Aaa{o}UrWX3F?}USQ$vnZC;|i^K`It#7c_!17qJ68uw?wGW2vyg(~12EtXz#u|OBvY6~7bckz zf()osCXq6q$Y2u3;U#<_6#u`_n)1Q8j~=?eP>$>$ubmpLsbFyZ=-cRPj8I!l7>u?o zm;i4y1UV1~j%ti>jV|$`0I>i>`o~zpewY{jO)?-!L#8oYX*d#vO2JV9h=${GK{AfV zBT|up3mxK8v?_dLmrEd60m#5b0z^kdDlQoQXp5QfT&C^a?LW}F7fl$Ubs+$ zUgevSk+IZ2LXJY2T4=sHsLUhNkV!*0faPP@rEhEQtlwKhNkXCYZ3Da)g+#T#$XpU= zyL?)0m8pK3`#DZ?%04GH&dQFI68a>^0Z%P!YQ5I{11&c1FpYMrxqI7mE7K6^Q(o}t zGWhB7T?aDR7Qw~EC!-2js%DnO%A=MYmw##4oc;vtt$$c5&u49<>B=kby+6Wu+_S1= z{uA=F>ciFUskJsmPdI3@?*IwTPdtg9l4$B#{Hl`ECrAVXdT>st0}y4&p*DY~{h4pp z{KUx*pv-V7Ayc9&qC68fM7psp7MJ<{vNqw_cehLCc!y0Wns9Q7rBM?fN`hK=P))VH zof{V#Nd4tuQI?rXT8Z9dpIMv0_sAtLhT%CtuiX ztlEVAG3ihtbJj6dqbhnOdg2?mpxNmUc4g+|))zF_JJkOkqUY6uzF1wfs%PIwuYQYf zlj}N24u!@*83Bb&8&@1_oLI&wZH7IkXB+&Lcza?*-_Gw8984jnjA&Y5q_e)sIDMn4 z{OPqimAP6pS8QX3J|4B(eA6H)_^o6N$k zx#4G&8|AHo3;JJszx-h-il947cj3BAsAt2|DwbJ3k-v+mc)@rHr*tXJ60T+yN2spA zMj6~Q*HtGLyIYWAM{3)1*A383+4~G7N>4o}-gWD`dGg~l#|rhq9cLRYldld#z zyNYr|)e9#*?0XflsxK`j!Or=C$bg1Eqq8=3ur8$+b~Z}$RL*(bo}$w)KWfp6D%f#X z9lTQ(>iBIeYW0qcoO+{Hk3WK}Zch2@XNxsEIh-QPIoWzagQ08vZ_`h>7tGFSY@A*4 z*umOW$9eY;*WRFNt#}T9DilqJq;Gp(>m#W&*EqK=z;s1HwQl&_)xp)>xyc6{t8|o` zjh8(7ChyhuqWg1BRj)Y_A6lC1>2$;)GOY6QOpe)&DqD-Co68$-qF+i^*|}5;PhU^9 zyBYdx+tHO%iB-Y0Bi~E&I_5`~>%IFiZ-41SVOwB6v7BT$_^;--1LPe`huw2B+uCu} z=jooIOsgjyX$}J$mPf6zdqyiZOgy3ctN2eLcPTUCQj)dkK~0cP_*BJ%88$@Sb@Tex zpSoe6Q|hC7nU-<*u3(2>@6U@57Fta|;-IeRWc>^~#QAk@>Cc2qs`1%%nUnnnk1se& zqc{5RHq+ib0Ke5&J^%m! diff --git a/test_model/img/info-icon-white.png b/test_model/img/info-icon-white.png deleted file mode 100644 index 210613e03a30370a1821eb81a53c181084186de5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4945 zcmeHKX;2f{7ES6`%#eBb7kXf@w1?@lw>_RQM1%R;*IXl?L7`_mCk{%ymA0^WFhe12R# zwXyVv5t++Y`S7gW{2(>D za|{!I2)6mYot+wEw6T6pg!w+SYv~^ivGtkF6bdyH4+sbs2LybK2Sib{I7b+Aa`MR9 zd9$);-}U!h>8aSoXcCD=h7Qe&*-|<5`vmwPNy6uFtVuJ0iLad~P#rk>9(8Kc_4W92<)G4wZ zn?jh%X8zIE+jpMK&5TVhey{8BO5Q;A&*7aTPtuIgrr#>b`uC?s&(3vf(Y1&(7$Kc! z6K>q&){9p3+cX?Oeo1a|*cbIQ=1@j-UHGcll_yu1&OEw4A^LVrZFJe?s3)5`mmRO! zpL<_$(ec#2Osian(c6|IY;ObX$_b&8I@z{dt<%kR4IeeLIU3x4fa_FOfDvd4*fa z91|sCYI00p2!*`YUX!4WH;1E;GqHF~1yprlSJr1+28$)(y%r`4VsVwmYz4^vOq0Zw zpUC>mHW!gvOvdKOQ4Zh7n=kJJC06T5R4vC$Pyn2P0~|IV<^Tj49YYW{ovq-> z=rWAUqH_^0hs~D3as?Od12IF3gRDg3`+8-9QkbBW2#d=BP_Tl}r?WW<9-YtgQP8<^ zmYgd`5tOTxnV}SNK@g!;q2M@i6&i~%HR@Qiz(lxUO1M}^Lm2RNwnZ&H+$H2N~N-83NOoJ;5?f9phuPe#}4d z;b&Ip(_(;K_Znzjpc68CTVW4hrl#{ZK0W8~H|_wSzi#qH{C=hDD_vj2z!xch&91L> zeGvm+r2I9z{%>^Ie!5IyYVZ$853Wi?(6(T3&9at-1_zoiT$Hx~hnIknjV2^oN1@ma zF}+kuVUY`1>`#g%qW%y1xec1=l&KrCkV3KO6bDX`&Nn{$sUcK4)9K8y$jVcrDzj|q zW6zAbJbPF^J!`%5_6d=%n3*m?qo3t!3s&!5s)=mcRoU>btt8c;~pPkW&5f57U~~cN}-A zIMlY4Qe@$gW`FLEw`SPGTZ5PDDbdgj&E53m&BAuNl~i0k`cZy~WqI*|v1QZVc|0oc zPK=7EC{c^6%HR6tszY75r|)i9nIimsPe@>~MP%ALq~;I9RL8E`h?<$MAI@!mW8XL{ zeUGp8FM3aE(x zgRnWz52Ww+bUR!9Dz$2``=Yq%?{0-naBkS($@KDVc>N~3X2kGOsYkAM&Ye`SvG}C+ zN;UI8r+>V*?+WL1&H75k4Iigk+w1p^-=m~%n9`7a@*2@zwt~}jaNN^*jU(OeI?VC2 zH{@?W;FWE;IDhj4o2yah^Nm^C@8_TQpgV8ez4@Q6q<`!hmQ_qc@^y#n=|Ndtj_~@7 zT=$`!IqVT>uMdkx*=|^Ng6&u4ywRp`eP`Ij2L>X`zc6O^!V4#t8wRCTJ=|gCwl`t1 z%^ky{tX6B+Z_Z9$lUwZ-#PFT~# z6`bZljgEBzlLywb3;z_{9Ba( diff --git a/test_model/img/xsoverlay.png b/test_model/img/xsoverlay.png deleted file mode 100644 index 35c793eaed87c7b132f6b5400b732341c3e74660..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1209 zcmV;q1V;ObP)EX>4Tx04UFukvmAkP!xv$rWPMm9PFUt5TQERMMWG-6pLV?v=v%)FuCaqnlvOS zE{=k0!NJF3)xpJCR|i)?5PX2Rx;QDiNQvhrg%+WL7Y_I1zxV$+_gp}zmzidDMFCB> z&16gxGuc(K`xOBUQ;nflW|k38EvC_Neci*y_qzzs!v9vknzI<-6N#hDFm2)u;@M5x z;Ji;9VntacJ|~_usX^jLt}7nDaW1$l@XU~zPRr|tNVF__0AVNVEC6r+!Lc30ig(RIz9sDD%KS3^qTqQ7a zET94nvg-%`gWt2Y^3zjZQcwZ{FOKsu4s`DVjhf?pA3ILt1n@rtS9;4|sRMJLq}N(n z_y`!-1}?5!nz9F6?f^qihHT2NMD1tTjqx>_{=000?uMObu0Z*6U5Zgc=ca%Ew3 zWn>_CX>@2HM@dakSAh-}0007zNklKpZw0w%6M~B1PFzS{!IeUb z+V%4@9U}oh&A@9A_IkbZXf*oE zXf%?!H7SZ>jK||Ayq2Zq9vzd-W>@@Wt!%?er9#_8FHRR= z$IMom%_jM><^!qSZcmu4bUK|eV&al7*X#9jW?#JxGnov_PznZ#^mKA(#|pO4dg z96Op!Ci?lyO90lFVo8$3YPHHW8Vzo-Sja4_3%*pV)kd_oSfT|B>!KKWbPQdKg;i~o zz&3Sw7cA?Lz}^#}g7OMHbGclTSS)r&jcy>P(チャットボックス" - checkbox_transcription_receive: "スピーカー->ログ" - checkbox_foreground: "最前面表示" - - # main tabview - main_tab_title_log: "ログ" - main_tab_title_send: "送信" - main_tab_title_receive: "受信" - main_tab_title_system: "システム" - - - # configure window - # config tabview - config_tab_title_ui: "UI" - config_tab_title_translation: "翻訳方法" - config_tab_title_transcription: "音声認識" - config_tab_title_parameter: "パラメーター" - config_tab_title_others: "その他" - - # tab UI - label_transparency: "透過度" - label_appearance_theme: "外観テーマを選択" - label_ui_scaling: "UIの拡大縮小" - label_font_family: "使用フォントの変更" - label_ui_language: "UI 言語" - - # tab Translation - label_translation_translator: "翻訳エンジンの選択" - label_translation_input_language: "送信言語-->翻訳言語" - label_translation_output_language: "受信言語-->翻訳言語" - - # tab Transcription - label_input_mic_host: "マイク入力ホスト" - label_input_mic_device: "マイク入力デバイス" - label_input_mic_voice_language: "マイクで話す言語" - label_input_mic_energy_threshold: "音声取得のしきい値" - checkbox_input_mic_threshold_check: "音声取得のしきい値の視覚化" - label_input_mic_dynamic_energy_threshold: "音声取得のしきい値の自動調整" - label_input_mic_record_timeout: "マイク音声の区切りの無音時間" - label_input_mic_phrase_timeout: "文字起こしする音声時間の上限" - label_input_mic_max_phrases: "保留する単語の上限(マイク)" - label_input_mic_word_filter: "ワードフィルタ" - - label_input_speaker_device: "スピーカー(聞き取りたいデバイス)" - label_input_speaker_voice_language: "聞き取る音声の言語" - label_input_speaker_energy_threshold: "音声取得のしきい値" - checkbox_input_speaker_threshold_check: "音声取得のしきい値の視覚化" - label_input_speaker_dynamic_energy_threshold: "音声取得のしきい値の自動調整" - label_input_speaker_record_timeout: "スピーカー音声の区切りの無音時間" - label_input_speaker_phrase_timeout: "文字起こしする音声時間の上限" - label_input_speaker_max_phrases: "保留する単語の上限(スピーカー)" - - # tab Parameter - # label_ip_address: "" - # label_port: "" - # label_authkey: "" - label_message_format: "送信するメッセージのフォーマット" - - # tab Others - label_checkbox_auto_clear_chatbox: "送信後はチャットボックスを空にする" - label_checkbox_notice_xsoverlay: "XSOverlayの通知機能を有効" - - -ko: - # main window - checkbox_translation: "번역" - checkbox_transcription_send: "마이크 -> 챗박스" - checkbox_transcription_receive: "스피커 -> 로그" - checkbox_foreground: "항상 위로" - - # main tabview - main_tab_title_log: "로그" - main_tab_title_send: "전송" - main_tab_title_receive: "수신" - main_tab_title_system: "시스템" - - - # configure window - # config tabview - config_tab_title_ui: "UI" - config_tab_title_translation: "번역" - config_tab_title_transcription: "음성인식" - config_tab_title_parameter: "파라미터" - config_tab_title_others: "기타" - # tab UI - label_transparency: "투명도" - label_appearance_theme: "테마" - label_ui_scaling: "UI 크기" - label_font_family: "폰트" - label_ui_language: "UI 언어" - - # tab Translation - label_translation_translator: "번역기 선택" - label_translation_input_language: "전송시 번역 언어" - label_translation_output_language: "수신시 번역 언어" - - # tab Transcription - label_input_mic_host: "마이크 호스트" - label_input_mic_device: "마이크 장치" - label_input_mic_voice_language: "입력 언어" - label_input_mic_energy_threshold: "음성 입력 최소 볼륨" - checkbox_input_mic_threshold_check: "임계점 확인" - label_input_mic_dynamic_energy_threshold: "동적 임계값" - label_input_mic_record_timeout: "최대 무음 시간" - label_input_mic_phrase_timeout: "최대 인식 시간" - label_input_mic_max_phrases: "최대 입력 절(phrases) 수" - label_input_mic_word_filter: "단어 필터" - - label_input_speaker_device: "스피커 장치" - label_input_speaker_voice_language: "입력 언어" - label_input_speaker_energy_threshold: "음성 입력 최소 볼륨" - checkbox_input_speaker_threshold_check: "임계점 확인" - label_input_speaker_dynamic_energy_threshold: "동적 임계값" - label_input_speaker_record_timeout: "최대 무음 시간" - label_input_speaker_phrase_timeout: "최대 인식 시간" - label_input_speaker_max_phrases: "최대 입력 절(phrases) 수" - - # tab Parameter - label_ip_address: "OSC IP 주소" - label_port: "OSC 포트" - label_authkey: "DeepL 인증키" - label_message_format: "전송 형식" - - # tab Others - label_checkbox_auto_clear_chatbox: "챗박스 자동 삭제" \ No newline at end of file diff --git a/test_model/main.py b/test_model/main.py deleted file mode 100644 index dd4ca55b..00000000 --- a/test_model/main.py +++ /dev/null @@ -1,503 +0,0 @@ -import os -import sys -sys.path.append(os.path.join(os.path.dirname(__file__), '..')) - -from time import sleep -from os import path as os_path - -import customtkinter -from customtkinter import CTk, CTkFrame, CTkCheckBox, CTkFont, CTkButton, CTkImage, CTkTabview, CTkTextbox, CTkEntry -from PIL.Image import open as Image_open - -from threading import Thread -from utils import print_textbox, get_localized_text, widget_main_window_label_setter -from window_config import ToplevelWindowConfig -from window_information import ToplevelWindowInformation -from config import config -from model import model - -class App(CTk): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - ## set UI theme - customtkinter.set_appearance_mode(config.APPEARANCE_THEME) - customtkinter.set_default_color_theme("blue") - - # init main window - self.iconbitmap(os_path.join(os_path.dirname(__file__), "img", "app.ico")) - self.title("VRCT") - self.geometry(f"{400}x{175}") - self.minsize(400, 175) - self.grid_columnconfigure(1, weight=1) - self.grid_rowconfigure(0, weight=1) - self.wm_attributes("-alpha", config.TRANSPARENCY/100) - customtkinter.set_widget_scaling(int(config.UI_SCALING.replace("%", "")) / 100) - self.protocol("WM_DELETE_WINDOW", self.delete_window) - - # add sidebar - self.add_sidebar() - - # add entry message box - self.entry_message_box = CTkEntry( - self, - placeholder_text="message", - font=CTkFont(family=config.FONT_FAMILY), - ) - self.entry_message_box.grid(row=1, column=1, columnspan=2, padx=5, pady=(5, 10), sticky="nsew") - self.entry_message_box.bind("", self.entry_message_box_press_key_enter) - self.entry_message_box.bind("", self.entry_message_box_press_key_any) - self.entry_message_box.bind("", self.entry_message_box_leave) - - # add tabview textbox - self.add_tabview_logs(get_localized_text(f"{config.UI_LANGUAGE}")) - - self.config_window = ToplevelWindowConfig(self) - self.information_window = ToplevelWindowInformation(self) - self.init_process() - - def init_process(self): - # set translator - if model.authenticationTranslator() is False: - # error update Auth key - self.printLogAuthenticationError() - - # set word filter - model.addKeywords() - - # check OSC started - model.checkOSCStarted() - - # check Software Updated - model.checkSoftwareUpdated() - - def button_config_callback(self): - self.foreground_stop() - self.transcription_stop() - self.checkbox_translation.configure(state="disabled") - self.checkbox_transcription_send.configure(state="disabled") - self.checkbox_transcription_receive.configure(state="disabled") - self.checkbox_foreground.configure(state="disabled") - self.tabview_logs.configure(state="disabled") - self.textbox_message_log.configure(state="disabled") - self.textbox_message_send_log.configure(state="disabled") - self.textbox_message_receive_log.configure(state="disabled") - self.textbox_message_system_log.configure(state="disabled") - self.entry_message_box.configure(state="disabled") - self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) - self.button_information.configure(state="disabled", fg_color=["gray92", "gray14"]) - self.config_window.deiconify() - self.config_window.focus_set() - self.config_window.focus() - self.config_window.grab_set() - - def button_information_callback(self): - self.information_window.deiconify() - self.information_window.focus_set() - self.information_window.focus() - - def checkbox_translation_callback(self): - config.ENABLE_TRANSLATION = self.checkbox_translation.get() - if config.ENABLE_TRANSLATION is True: - self.printLogStartTranslation() - else: - self.printLogStopTranslation() - - def transcription_send_start(self): - model.startMicTranscript(self.sendMicMessage) - self.printLogStartVoice2chatbox() - self.checkbox_transcription_send.configure(state="normal") - self.checkbox_transcription_receive.configure(state="normal") - self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) - - def transcription_send_stop(self): - model.stopMicTranscript() - self.printLogStopVoice2chatbox() - self.checkbox_transcription_send.configure(state="normal") - self.checkbox_transcription_receive.configure(state="normal") - self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) - - def transcription_send_stop_for_config(self): - model.stopMicTranscript() - self.printLogStopVoice2chatbox() - - def checkbox_transcription_send_callback(self): - config.ENABLE_TRANSCRIPTION_SEND = self.checkbox_transcription_send.get() - self.checkbox_transcription_send.configure(state="disabled") - self.checkbox_transcription_receive.configure(state="disabled") - self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) - if config.ENABLE_TRANSCRIPTION_SEND is True: - th_transcription_send_start = Thread(target=self.transcription_send_start) - th_transcription_send_start.daemon = True - th_transcription_send_start.start() - else: - th_transcription_send_stop = Thread(target=self.transcription_send_stop) - th_transcription_send_stop.daemon = True - th_transcription_send_stop.start() - - def transcription_receive_start(self): - model.startSpeakerTranscript(self.receiveSpeakerMessage) - self.printLogStartSpeaker2log() - self.checkbox_transcription_send.configure(state="normal") - self.checkbox_transcription_receive.configure(state="normal") - self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) - - def transcription_receive_stop(self): - model.stopSpeakerTranscript() - self.printLogStopSpeaker2log() - self.checkbox_transcription_send.configure(state="normal") - self.checkbox_transcription_receive.configure(state="normal") - self.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) - - def transcription_receive_stop_for_config(self): - model.stopSpeakerTranscript() - self.printLogStopSpeaker2log() - - def checkbox_transcription_receive_callback(self): - config.ENABLE_TRANSCRIPTION_RECEIVE = self.checkbox_transcription_receive.get() - self.checkbox_transcription_send.configure(state="disabled") - self.checkbox_transcription_receive.configure(state="disabled") - self.button_config.configure(state="disabled", fg_color=["gray92", "gray14"]) - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - th_transcription_receive_start = Thread(target=self.transcription_receive_start) - th_transcription_receive_start.daemon = True - th_transcription_receive_start.start() - else: - th_transcription_receive_stop = Thread(target=self.transcription_receive_stop) - th_transcription_receive_stop.daemon = True - th_transcription_receive_stop.start() - - def transcription_start(self): - if config.ENABLE_TRANSCRIPTION_SEND is True: - th_transcription_send_start = Thread(target=self.transcription_send_start) - th_transcription_send_start.daemon = True - th_transcription_send_start.start() - sleep(2) - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - th_transcription_receive_start = Thread(target=self.transcription_receive_start) - th_transcription_receive_start.daemon = True - th_transcription_receive_start.start() - - def transcription_stop(self): - if config.ENABLE_TRANSCRIPTION_SEND is True: - th_transcription_send_stop = Thread(target=self.transcription_send_stop_for_config) - th_transcription_send_stop.daemon = True - th_transcription_send_stop.start() - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - th_transcription_receive_stop = Thread(target=self.transcription_receive_stop_for_config) - th_transcription_receive_stop.daemon = True - th_transcription_receive_stop.start() - - def checkbox_foreground_callback(self): - config.ENABLE_FOREGROUND = self.checkbox_foreground.get() - if config.ENABLE_FOREGROUND: - self.attributes("-topmost", True) - self.printLogStartForeground() - else: - self.attributes("-topmost", False) - self.printLogStopForeground() - - def foreground_start(self): - if config.ENABLE_FOREGROUND: - self.attributes("-topmost", True) - self.printLogStartForeground() - - def foreground_stop(self): - if config.ENABLE_FOREGROUND: - self.attributes("-topmost", False) - self.printLogStopForeground() - - def entry_message_box_press_key_enter(self, event): - # osc stop send typing - model.oscStopSendTyping() - - if config.ENABLE_FOREGROUND: - self.attributes("-topmost", True) - - message = self.entry_message_box.get() - self.sendChatMessage(message) - - def entry_message_box_press_key_any(self, event): - # osc start send typing - model.oscStartSendTyping() - if config.ENABLE_FOREGROUND: - self.attributes("-topmost", False) - - if event.keysym != "??": - if len(event.char) != 0 and event.keysym in config.BREAK_KEYSYM_LIST: - self.entry_message_box.insert("end", event.char) - return "break" - - def entry_message_box_leave(self, event): - # osc stop send typing - model.oscStopSendTyping() - if config.ENABLE_FOREGROUND: - self.attributes("-topmost", True) - - def delete_window(self): - self.quit() - self.destroy() - - def add_sidebar(self): - init_lang_text = "Loading..." - self.sidebar_frame = CTkFrame(master=self, corner_radius=0) - - # add checkbox translation - self.checkbox_translation = CTkCheckBox( - self.sidebar_frame, - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_translation_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add checkbox transcription send - self.checkbox_transcription_send = CTkCheckBox( - self.sidebar_frame, - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_transcription_send_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add checkbox transcription receive - self.checkbox_transcription_receive = CTkCheckBox( - self.sidebar_frame, - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_transcription_receive_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add checkbox foreground - self.checkbox_foreground = CTkCheckBox( - self.sidebar_frame, - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_foreground_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add button information - self.button_information = CTkButton( - self.sidebar_frame, - text=None, - width=36, - command=self.button_information_callback, - image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "info-icon-white.png"))) - ) - - # add button config - self.button_config = CTkButton( - self.sidebar_frame, - text=None, - width=36, - command=self.button_config_callback, - image=CTkImage(Image_open(os_path.join(os_path.dirname(__file__), "img", "config-icon-white.png"))) - ) - - self.sidebar_frame.grid(row=0, column=0, rowspan=4, sticky="nsw") - self.sidebar_frame.grid_rowconfigure(5, weight=1) - self.checkbox_translation.grid(row=0, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") - self.checkbox_transcription_send.grid(row=1, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") - self.checkbox_transcription_receive.grid(row=2, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") - self.checkbox_foreground.grid(row=3, column=0, columnspan=2, padx=10, pady=(5, 5), sticky="we") - self.button_information.grid(row=5, column=0, padx=(10, 5), pady=(5, 5), sticky="wse") - self.button_config.grid(row=5, column=1, padx=(5, 10), pady=(5, 5), sticky="wse") - - def delete_tabview_logs(self, pre_language_yaml_data): - self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_log"]) - self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_send"]) - self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_receive"]) - self.tabview_logs.delete(pre_language_yaml_data["main_tab_title_system"]) - - def add_tabview_logs(self, language_yaml_data): - main_tab_title_log = language_yaml_data["main_tab_title_log"] - main_tab_title_send = language_yaml_data["main_tab_title_send"] - main_tab_title_receive = language_yaml_data["main_tab_title_receive"] - main_tab_title_system = language_yaml_data["main_tab_title_system"] - - # add tabview textbox - self.tabview_logs = CTkTabview(master=self) - self.tabview_logs.add(main_tab_title_log) - self.tabview_logs.add(main_tab_title_send) - self.tabview_logs.add(main_tab_title_receive) - self.tabview_logs.add(main_tab_title_system) - self.tabview_logs.grid(row=0, column=1, padx=0, pady=0, sticky="nsew") - self.tabview_logs._segmented_button.configure(font=CTkFont(family=config.FONT_FAMILY)) - self.tabview_logs._segmented_button.grid(sticky="W") - self.tabview_logs.tab(main_tab_title_log).grid_rowconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_log).grid_columnconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_send).grid_rowconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_send).grid_columnconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_receive).grid_rowconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_receive).grid_columnconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_system).grid_rowconfigure(0, weight=1) - self.tabview_logs.tab(main_tab_title_system).grid_columnconfigure(0, weight=1) - self.tabview_logs.configure(fg_color="transparent") - - # add textbox message log - self.textbox_message_log = CTkTextbox( - self.tabview_logs.tab(main_tab_title_log), - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add textbox message send log - self.textbox_message_send_log = CTkTextbox( - self.tabview_logs.tab(main_tab_title_send), - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add textbox message receive log - self.textbox_message_receive_log = CTkTextbox( - self.tabview_logs.tab(main_tab_title_receive), - font=CTkFont(family=config.FONT_FAMILY) - ) - - # add textbox message system log - self.textbox_message_system_log = CTkTextbox( - self.tabview_logs.tab(main_tab_title_system), - font=CTkFont(family=config.FONT_FAMILY) - ) - - self.textbox_message_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") - self.textbox_message_send_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") - self.textbox_message_receive_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") - self.textbox_message_system_log.grid(row=0, column=0, padx=0, pady=0, sticky="nsew") - self.textbox_message_log.configure(state='disabled') - self.textbox_message_send_log.configure(state='disabled') - self.textbox_message_receive_log.configure(state='disabled') - self.textbox_message_system_log.configure(state='disabled') - - widget_main_window_label_setter(self, language_yaml_data) - - def printLogAuthenticationError(self): - print_textbox(self.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") - print_textbox(self.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") - - def printLogStartTranslation(self): - print_textbox(self.textbox_message_log, "Start translation", "INFO") - print_textbox(self.textbox_message_system_log, "Start translation", "INFO") - - def printLogStopTranslation(self): - print_textbox(self.textbox_message_log, "Stop translation", "INFO") - print_textbox(self.textbox_message_system_log, "Stop translation", "INFO") - - def printLogStartVoice2chatbox(self): - print_textbox(self.textbox_message_log, "Start voice2chatbox", "INFO") - print_textbox(self.textbox_message_system_log, "Start voice2chatbox", "INFO") - - def printLogStopVoice2chatbox(self): - print_textbox(self.textbox_message_log, "Stop voice2chatbox", "INFO") - print_textbox(self.textbox_message_system_log, "Stop voice2chatbox", "INFO") - - def printLogStartSpeaker2log(self): - print_textbox(self.textbox_message_log, "Start speaker2log", "INFO") - print_textbox(self.textbox_message_system_log, "Start speaker2log", "INFO") - - def printLogStopSpeaker2log(self): - print_textbox(self.textbox_message_log, "Stop speaker2log", "INFO") - print_textbox(self.textbox_message_system_log, "Stop speaker2log", "INFO") - - def printLogStartForeground(self): - print_textbox(self.textbox_message_log, "Start foreground", "INFO") - print_textbox(self.textbox_message_system_log, "Start foreground", "INFO") - - def printLogStopForeground(self): - print_textbox(self.textbox_message_log, "Stop foreground", "INFO") - print_textbox(self.textbox_message_system_log, "Stop foreground", "INFO") - - def printLogDetectWordFilter(self, message): - print_textbox(self.textbox_message_log, f"Detect WordFilter :{message}", "INFO") - print_textbox(self.textbox_message_system_log, f"Detect WordFilter :{message}", "INFO") - - def printLogOSCError(self): - print_textbox(self.textbox_message_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") - print_textbox(self.textbox_message_system_log, "OSC is not enabled, please enable OSC and rejoin.", "ERROR") - - def printLogSendMessage(self, message): - print_textbox(self.textbox_message_log, f"{message}", "SEND") - print_textbox(self.textbox_message_send_log, f"{message}", "SEND") - - def printLogReceiveMessage(self, message): - print_textbox(self.textbox_message_log, f"{message}", "RECEIVE") - print_textbox(self.textbox_message_receive_log, f"{message}", "RECEIVE") - - def sendChatMessage(self, message): - if len(message) > 0: - # translate - if config.ENABLE_TRANSLATION is False: - chat_message = f"{message}" - elif model.getTranslatorStatus() is False: - self.printLogAuthenticationError() - chat_message = f"{message}" - else: - chat_message = model.getInputTranslate(message) - - # send OSC message - if config.ENABLE_OSC is True: - model.oscSendMessage(chat_message) - else: - self.printLogOSCError() - - # update textbox message log - self.printLogSendMessage(chat_message) - - # delete message in entry message box - if config.ENABLE_AUTO_CLEAR_CHATBOX is True: - self.entry_message_box.delete(0, customtkinter.END) - - def sendMicMessage(self, message): - if len(message) > 0: - # word filter - if model.checkKeywords(message): - self.printLogDetectWordFilter(message) - return - - # translate - if config.ENABLE_TRANSLATION is False: - voice_message = f"{message}" - elif model.getTranslatorStatus() is False: - self.printLogAuthenticationError() - voice_message = f"{message}" - else: - voice_message = model.getInputTranslate(message) - - if config.ENABLE_TRANSCRIPTION_SEND is True: - if config.ENABLE_OSC is True: - # osc send message - model.oscSendMessage(voice_message) - else: - self.printLogOSCError() - # update textbox message log - self.printLogSendMessage(voice_message) - - def receiveSpeakerMessage(self, message): - if len(message) > 0: - # translate - if config.ENABLE_TRANSLATION is False: - voice_message = f"{message}" - elif model.getTranslatorStatus() is False: - self.printLogAuthenticationError() - voice_message = f"{message}" - else: - voice_message = model.getOutputTranslate(message) - - if config.ENABLE_TRANSCRIPTION_RECEIVE is True: - # update textbox message receive log - self.printLogReceiveMessage(voice_message) - if config.ENABLE_NOTICE_XSOVERLAY is True: - model.notificationXsoverlay(voice_message) - -if __name__ == "__main__": - try: - app = App() - app.mainloop() - except Exception as e: - import traceback - with open(os.path.join(os.path.dirname(__file__), 'error.log'), 'a') as f: - traceback.print_exc(file=f) \ No newline at end of file diff --git a/test_model/utils.py b/test_model/utils.py deleted file mode 100644 index 2bee5814..00000000 --- a/test_model/utils.py +++ /dev/null @@ -1,106 +0,0 @@ -from os import path as os_path -import yaml -from datetime import datetime - -def print_textbox(textbox, message, tags=None): - now = datetime.now() - now = now.strftime('%H:%M:%S') - - textbox.tag_config("ERROR", foreground="#FF0000") - textbox.tag_config("INFO", foreground="#1BFF00") - textbox.tag_config("SEND", foreground="#0378e2") - textbox.tag_config("RECEIVE", foreground="#ffa500") - - textbox.configure(state='normal') - textbox.insert("end", f"[{now}][") - textbox.insert("end", f"{tags}", tags) - textbox.insert("end", f"]{message}\n") - textbox.configure(state='disabled') - textbox.see("end") - -def get_localized_text(language): - file_path = os_path.join(os_path.dirname(__file__), "locales.yml") - - with open(file_path, encoding="utf-8") as file: - languages_yaml_data = yaml.safe_load(file) - default_language = "en" - if language in languages_yaml_data: - localized_text = languages_yaml_data[language] - if default_language in languages_yaml_data: - default_text = languages_yaml_data[default_language] - merged_text = {**default_text, **localized_text} - return merged_text - else: - return localized_text - else: - return None - -def get_key_by_value(dictionary, value): - for key, val in dictionary.items(): - if val == value: - return key - return None - -def widget_config_window_label_setter(self, language_yaml_data): - widget_names = [ - # tab UI - "label_transparency", - "label_appearance_theme", - "label_ui_scaling", - "label_font_family", - "label_ui_language", - - # tab Translation - "label_translation_translator", - "label_translation_input_language", - "label_translation_output_language", - - # tab Transcription - "label_input_mic_host", - "label_input_mic_device", - "label_input_mic_voice_language", - "label_input_mic_energy_threshold", - "checkbox_input_mic_threshold_check", - "label_input_mic_dynamic_energy_threshold", - "label_input_mic_record_timeout", - "label_input_mic_phrase_timeout", - "label_input_mic_max_phrases", - "label_input_mic_word_filter", - - "label_input_speaker_device", - "label_input_speaker_voice_language", - "label_input_speaker_energy_threshold", - "checkbox_input_speaker_threshold_check", - "label_input_speaker_dynamic_energy_threshold", - "label_input_speaker_record_timeout", - "label_input_speaker_phrase_timeout", - "label_input_speaker_max_phrases", - - # tab Parameter - "label_ip_address", - "label_port", - "label_authkey", - "label_message_format", - - # tab Others - "label_checkbox_auto_clear_chatbox", - "label_checkbox_notice_xsoverlay", - ] - for name in widget_names: - widget = getattr(self, name) - text_value = language_yaml_data.get(name) - if widget is not None and text_value is not None: - widget.configure(text=text_value + ":") - -def widget_main_window_label_setter(self, language_yaml_data): - widget_names = [ - "checkbox_translation", - "checkbox_transcription_send", - "checkbox_transcription_receive", - "checkbox_foreground", - ] - for name in widget_names: - widget = getattr(self, name) - text_value = language_yaml_data.get(name) - if widget is not None and text_value is not None: - widget.configure(text=text_value) \ No newline at end of file diff --git a/test_model/window_config.py b/test_model/window_config.py deleted file mode 100644 index 89dcf6ce..00000000 --- a/test_model/window_config.py +++ /dev/null @@ -1,1348 +0,0 @@ -import os -import sys -sys.path.append(os.path.join(os.path.dirname(__file__), '..')) - -from os import path as os_path -from tkinter import DoubleVar, IntVar -from tkinter import font as tk_font -import customtkinter -from customtkinter import CTkToplevel, CTkTabview, CTkFont, CTkLabel, CTkSlider, CTkOptionMenu, StringVar, CTkEntry, CTkCheckBox, CTkProgressBar - -from threading import Thread -from config import config -from model import model -from utils import print_textbox, get_localized_text, get_key_by_value, widget_config_window_label_setter -from languages import selectable_languages -from models.translation.translation_languages import translation_lang -from models.transcription.transcription_languages import transcription_lang -from ctk_scrollable_dropdown import CTkScrollableDropdown - -SCROLLABLE_DROPDOWN = False - -class ToplevelWindowConfig(CTkToplevel): - - def __init__(self, parent, *args, **kwargs): - super().__init__(parent, *args, **kwargs) - - self.withdraw() - self.parent = parent - # self.geometry(f"{350}x{270}") - # self.resizable(False, False) - self.grid_columnconfigure(0, weight=1) - self.grid_rowconfigure(0, weight=1) - - self.after(200, lambda: self.iconbitmap(os_path.join(os_path.dirname(__file__), "img", "app.ico"))) - self.title("Config") - - # load ui language data - language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") - # add tabview config - self.add_tabview_config(language_yaml_data, selectable_languages) - # set all config window labels - widget_config_window_label_setter(self, language_yaml_data) - - self.protocol("WM_DELETE_WINDOW", self.delete_window) - - def slider_transparency_callback(self, value): - self.parent.wm_attributes("-alpha", value/100) - config.TRANSPARENCY = value - - def optionmenu_appearance_theme_callback(self, choice): - self.optionmenu_appearance_theme.set(choice) - - customtkinter.set_appearance_mode(choice) - config.APPEARANCE_THEME = choice - - def optionmenu_ui_scaling_callback(self, choice): - self.optionmenu_ui_scaling.set(choice) - - new_scaling_float = int(choice.replace("%", "")) / 100 - customtkinter.set_widget_scaling(new_scaling_float) - config.UI_SCALING = choice - - def optionmenu_font_family_callback(self, choice): - self.optionmenu_font_family.set(choice) - - # tab menu - self.tabview_config._segmented_button.configure(font=CTkFont(family=choice)) - - # tab UI - self.label_transparency.configure(font=CTkFont(family=choice)) - self.label_appearance_theme.configure(font=CTkFont(family=choice)) - self.optionmenu_appearance_theme.configure(font=CTkFont(family=choice)) - self.optionmenu_appearance_theme._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_ui_scaling.configure(font=CTkFont(family=choice)) - self.optionmenu_ui_scaling.configure(font=CTkFont(family=choice)) - self.optionmenu_ui_scaling._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_font_family.configure(font=CTkFont(family=choice)) - self.optionmenu_font_family.configure(font=CTkFont(family=choice)) - self.optionmenu_font_family._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_ui_language.configure(font=CTkFont(family=choice)) - self.optionmenu_ui_language.configure(font=CTkFont(family=choice)) - self.optionmenu_ui_language._dropdown_menu.configure(font=CTkFont(family=choice)) - - # tab Translation - self.label_translation_translator.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_translator.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_translator._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_translation_input_language.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_input_source_language.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_input_source_language._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_translation_input_arrow.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_input_target_language.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_input_target_language._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_translation_output_language.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_output_source_language.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_output_source_language._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_translation_output_arrow.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_output_target_language.configure(font=CTkFont(family=choice)) - self.optionmenu_translation_output_target_language._dropdown_menu.configure(font=CTkFont(family=choice)) - - # tab Transcription - self.label_input_mic_host.configure(font=CTkFont(family=choice)) - self.optionmenu_input_mic_host.configure(font=CTkFont(family=choice)) - self.optionmenu_input_mic_host._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_input_mic_device.configure(font=CTkFont(family=choice)) - self.optionmenu_input_mic_device.configure(font=CTkFont(family=choice)) - self.optionmenu_input_mic_device._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_input_mic_voice_language.configure(font=CTkFont(family=choice)) - self.optionmenu_input_mic_voice_language.configure(font=CTkFont(family=choice)) - self.optionmenu_input_mic_voice_language._dropdown_menu.configure(font=CTkFont(family=choice)) - self.checkbox_input_mic_threshold_check.configure(font=CTkFont(family=choice)) - self.label_input_mic_energy_threshold.configure(font=CTkFont(family=choice)) - self.label_input_mic_dynamic_energy_threshold.configure(font=CTkFont(family=choice)) - self.label_input_mic_record_timeout.configure(font=CTkFont(family=choice)) - self.entry_input_mic_record_timeout.configure(font=CTkFont(family=choice)) - self.label_input_mic_phrase_timeout.configure(font=CTkFont(family=choice)) - self.entry_input_mic_phrase_timeout.configure(font=CTkFont(family=choice)) - self.label_input_mic_max_phrases.configure(font=CTkFont(family=choice)) - self.entry_input_mic_max_phrases.configure(font=CTkFont(family=choice)) - self.label_input_mic_word_filter.configure(font=CTkFont(family=choice)) - self.entry_input_mic_word_filter.configure(font=CTkFont(family=choice)) - self.label_input_speaker_device.configure(font=CTkFont(family=choice)) - self.optionmenu_input_speaker_device.configure(font=CTkFont(family=choice)) - self.optionmenu_input_speaker_device._dropdown_menu.configure(font=CTkFont(family=choice)) - self.label_input_speaker_voice_language.configure(font=CTkFont(family=choice)) - self.optionmenu_input_speaker_voice_language.configure(font=CTkFont(family=choice)) - self.optionmenu_input_speaker_voice_language._dropdown_menu.configure(font=CTkFont(family=choice)) - self.checkbox_input_speaker_threshold_check.configure(font=CTkFont(family=choice)) - self.label_input_speaker_energy_threshold.configure(font=CTkFont(family=choice)) - self.label_input_speaker_dynamic_energy_threshold.configure(font=CTkFont(family=choice)) - self.label_input_speaker_record_timeout.configure(font=CTkFont(family=choice)) - self.entry_input_speaker_record_timeout.configure(font=CTkFont(family=choice)) - self.label_input_speaker_phrase_timeout.configure(font=CTkFont(family=choice)) - self.entry_input_speaker_phrase_timeout.configure(font=CTkFont(family=choice)) - self.label_input_speaker_max_phrases.configure(font=CTkFont(family=choice)) - self.entry_input_speaker_max_phrases.configure(font=CTkFont(family=choice)) - - # tab Parameter - self.label_ip_address.configure(font=CTkFont(family=choice)) - self.entry_ip_address.configure(font=CTkFont(family=choice)) - self.label_port.configure(font=CTkFont(family=choice)) - self.entry_port.configure(font=CTkFont(family=choice)) - self.label_authkey.configure(font=CTkFont(family=choice)) - self.entry_authkey.configure(font=CTkFont(family=choice)) - self.label_message_format.configure(font=CTkFont(family=choice)) - self.entry_message_format.configure(font=CTkFont(family=choice)) - - # tab Others - self.label_checkbox_auto_clear_chatbox.configure(font=CTkFont(family=choice)) - - # main window - self.parent.checkbox_translation.configure(font=CTkFont(family=choice)) - self.parent.checkbox_transcription_send.configure(font=CTkFont(family=choice)) - self.parent.checkbox_transcription_receive.configure(font=CTkFont(family=choice)) - self.parent.checkbox_foreground.configure(font=CTkFont(family=choice)) - self.parent.textbox_message_log.configure(font=CTkFont(family=choice)) - self.parent.textbox_message_send_log.configure(font=CTkFont(family=choice)) - self.parent.textbox_message_receive_log.configure(font=CTkFont(family=choice)) - self.parent.textbox_message_system_log.configure(font=CTkFont(family=choice)) - self.parent.entry_message_box.configure(font=CTkFont(family=choice)) - self.parent.tabview_logs._segmented_button.configure(font=CTkFont(family=choice)) - - # window information - try: - self.parent.information_window.textbox_information.configure(font=CTkFont(family=choice)) - except: - pass - - config.FONT_FAMILY = choice - - def optionmenu_ui_language_callback(self, choice): - self.optionmenu_ui_language.set(choice) - - self.withdraw() - pre_language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") - config.UI_LANGUAGE = get_key_by_value(selectable_languages, choice) - language_yaml_data = get_localized_text(f"{config.UI_LANGUAGE}") - - # delete - self.parent.delete_tabview_logs(pre_language_yaml_data) - self.delete_tabview_config(pre_language_yaml_data) - # add tabview textbox - self.parent.add_tabview_logs(language_yaml_data) - self.add_tabview_config(language_yaml_data, selectable_languages) - - # 翻訳予定 - # window information - # try: - # self.parent.information_window.textbox_information.configure(font=customtkinter.CTkFont(family=choice)) - # except: - # pass - self.deiconify() - - def optionmenu_translation_translator_callback(self, choice): - self.optionmenu_translation_translator.set(choice) - - if model.authenticationTranslator(choice_translator=choice) is False: - print_textbox(self.parent.textbox_message_log, "Auth Key or language setting is incorrect", "ERROR") - print_textbox(self.parent.textbox_message_system_log, "Auth Key or language setting is incorrect", "ERROR") - else: - self.optionmenu_translation_input_source_language.configure( - values=list(translation_lang[choice]["source"].keys()), - variable=StringVar(value=list(translation_lang[choice]["source"].keys())[0])) - self.optionmenu_translation_input_target_language.configure( - values=list(translation_lang[choice]["target"].keys()), - variable=StringVar(value=list(translation_lang[choice]["target"].keys())[1])) - self.optionmenu_translation_output_source_language.configure( - values=list(translation_lang[choice]["source"].keys()), - variable=StringVar(value=list(translation_lang[choice]["source"].keys())[1])) - self.optionmenu_translation_output_target_language.configure( - values=list(translation_lang[choice]["target"].keys()), - variable=StringVar(value=list(translation_lang[choice]["target"].keys())[0])) - - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_translation_input_source_language.configure( - values=list(translation_lang[choice]["source"].keys())) - self.scrollableDropdown_translation_input_target_language.configure( - values=list(translation_lang[choice]["target"].keys())) - self.scrollableDropdown_translation_output_source_language.configure( - values=list(translation_lang[choice]["source"].keys())) - self.scrollableDropdown_translation_output_target_language.configure( - values=list(translation_lang[choice]["target"].keys())) - - config.CHOICE_TRANSLATOR = choice - config.INPUT_SOURCE_LANG = list(translation_lang[choice]["source"].keys())[0] - config.INPUT_TARGET_LANG = list(translation_lang[choice]["target"].keys())[1] - config.OUTPUT_SOURCE_LANG = list(translation_lang[choice]["source"].keys())[1] - config.OUTPUT_TARGET_LANG = list(translation_lang[choice]["target"].keys())[0] - - def optionmenu_translation_input_source_language_callback(self, choice): - self.optionmenu_translation_input_source_language.set(choice) - config.INPUT_SOURCE_LANG = choice - - def optionmenu_translation_input_target_language_callback(self, choice): - self.optionmenu_translation_input_target_language.set(choice) - config.INPUT_TARGET_LANG = choice - - def optionmenu_translation_output_source_language_callback(self, choice): - self.optionmenu_translation_output_source_language.set(choice) - config.OUTPUT_SOURCE_LANG = choice - - def optionmenu_translation_output_target_language_callback(self, choice): - self.optionmenu_translation_output_target_language.set(choice) - config.OUTPUT_TARGET_LANG = choice - - def optionmenu_input_mic_host_callback(self, choice): - self.optionmenu_input_mic_host.set(choice) - config.CHOICE_MIC_HOST = choice - config.CHOICE_MIC_DEVICE = model.getInputDefaultDevice() - - self.optionmenu_input_mic_device.configure( - values=model.getListInputDevice(), - variable=StringVar(value=model.getInputDefaultDevice())) - - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_input_mic_device.configure(values=model.getListInputDevice()) - - def optionmenu_input_mic_device_callback(self, choice): - self.optionmenu_input_mic_device.set(choice) - config.CHOICE_MIC_DEVICE = choice - self.checkbox_input_mic_threshold_check.deselect() - self.checkbox_input_mic_threshold_check_callback() - - def optionmenu_input_mic_voice_language_callback(self, choice): - self.optionmenu_input_mic_voice_language.set(choice) - config.INPUT_MIC_VOICE_LANGUAGE = choice - - def mic_threshold_check_start(self): - def plotProgressBar(energy): - try: - self.progressBar_input_mic_energy_threshold.set(energy/config.MAX_MIC_ENERGY_THRESHOLD) - except: - pass - model.startCheckMicEnergy(plotProgressBar) - self.checkbox_input_mic_threshold_check.configure(state="normal") - self.checkbox_input_speaker_threshold_check.configure(state="normal") - - def mic_threshold_check_stop(self): - model.stopCheckMicEnergy() - self.progressBar_input_mic_energy_threshold.set(0) - self.checkbox_input_mic_threshold_check.configure(state="normal") - self.checkbox_input_speaker_threshold_check.configure(state="normal") - - def checkbox_input_mic_threshold_check_callback(self): - self.checkbox_input_mic_threshold_check.configure(state="disabled") - self.checkbox_input_speaker_threshold_check.configure(state="disabled") - self.update() - if self.checkbox_input_mic_threshold_check.get(): - th_mic_threshold_check_start = Thread(target=self.mic_threshold_check_start) - th_mic_threshold_check_start.daemon = True - th_mic_threshold_check_start.start() - else: - th_mic_threshold_check_stop = Thread(target=self.mic_threshold_check_stop) - th_mic_threshold_check_stop.daemon = True - th_mic_threshold_check_stop.start() - - def slider_input_mic_energy_threshold_callback(self, value): - config.INPUT_MIC_ENERGY_THRESHOLD = int(value) - - def checkbox_input_mic_dynamic_energy_threshold_callback(self): - config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD = self.checkbox_input_mic_dynamic_energy_threshold.get() - - def entry_input_mic_record_timeout_callback(self, event): - config.INPUT_MIC_RECORD_TIMEOUT = int(self.entry_input_mic_record_timeout.get()) - - def entry_input_mic_phrase_timeout_callback(self, event): - config.INPUT_MIC_PHRASE_TIMEOUT = int(self.entry_input_mic_phrase_timeout.get()) - - def entry_input_mic_max_phrases_callback(self, event): - config.INPUT_MIC_MAX_PHRASES = int(self.entry_input_mic_max_phrases.get()) - - def entry_input_mic_word_filters_callback(self, event): - word_filter = self.entry_input_mic_word_filter.get() - word_filter = [w.strip() for w in word_filter.split(",") if len(w.strip()) > 0] - word_filter = ",".join(word_filter) - if len(word_filter) > 0: - config.INPUT_MIC_WORD_FILTER = word_filter.split(",") - else: - config.INPUT_MIC_WORD_FILTER = [] - model.resetKeywordProcessor() - model.addKeywords() - - def optionmenu_input_speaker_device_callback(self, choice): - if model.checkSpeakerStatus(choice): - self.optionmenu_input_speaker_device.set(choice) - config.CHOICE_SPEAKER_DEVICE = choice - else: - print_textbox(self.parent.textbox_message_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") - print_textbox(self.parent.textbox_message_system_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") - self.optionmenu_input_speaker_device.configure(variable=StringVar(value=config.CHOICE_SPEAKER_DEVICE)) - - def optionmenu_input_speaker_voice_language_callback(self, choice): - self.optionmenu_input_speaker_voice_language.set(choice) - config.INPUT_SPEAKER_VOICE_LANGUAGE = choice - - def speaker_threshold_check_start(self): - def plotProgressBar(energy): - try: - self.progressBar_input_speaker_energy_threshold.set(energy/config.MAX_MIC_ENERGY_THRESHOLD) - except: - pass - model.startCheckSpeakerEnergy(plotProgressBar) - self.checkbox_input_mic_threshold_check.configure(state="normal") - self.checkbox_input_speaker_threshold_check.configure(state="normal") - - def speaker_threshold_check_stop(self): - model.stopCheckSpeakerEnergy() - self.progressBar_input_speaker_energy_threshold.set(0) - self.checkbox_input_mic_threshold_check.configure(state="normal") - self.checkbox_input_speaker_threshold_check.configure(state="normal") - - def checkbox_input_speaker_threshold_check_callback(self): - self.checkbox_input_mic_threshold_check.configure(state="disabled") - self.checkbox_input_speaker_threshold_check.configure(state="disabled") - self.update() - if self.checkbox_input_speaker_threshold_check.get(): - if model.checkSpeakerStatus(): - th_speaker_threshold_check_start = Thread(target=self.speaker_threshold_check_start) - th_speaker_threshold_check_start.daemon = True - th_speaker_threshold_check_start.start() - else: - print_textbox(self.parent.textbox_message_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") - print_textbox(self.parent.textbox_message_system_log, "Windows playback device and selected device do not match. Change the Windows playback device.", "ERROR") - self.checkbox_input_speaker_threshold_check.deselect() - else: - th_speaker_threshold_check_stop = Thread(target=self.speaker_threshold_check_stop) - th_speaker_threshold_check_stop.daemon = True - th_speaker_threshold_check_stop.start() - - def slider_input_speaker_energy_threshold_callback(self, value): - config.INPUT_SPEAKER_ENERGY_THRESHOLD = int(value) - - def checkbox_input_speaker_dynamic_energy_threshold_callback(self): - config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = self.checkbox_input_speaker_dynamic_energy_threshold.get() - - def entry_input_speaker_record_timeout_callback(self, event): - config.INPUT_SPEAKER_RECORD_TIMEOUT = int(self.entry_input_speaker_record_timeout.get()) - - def entry_input_speaker_phrase_timeout_callback(self, event): - config.INPUT_SPEAKER_PHRASE_TIMEOUT = int(self.entry_input_speaker_phrase_timeout.get()) - - def entry_input_speaker_max_phrases_callback(self, event): - config.INPUT_SPEAKER_MAX_PHRASES = int(self.entry_input_speaker_max_phrases.get()) - - def entry_ip_address_callback(self, event): - config.OSC_IP_ADDRESS = self.entry_ip_address.get() - - def entry_port_callback(self, event): - config.OSC_PORT = self.entry_port.get() - - def entry_authkey_callback(self, event): - value = self.entry_authkey.get() - if len(value) > 0: - if model.authenticationTranslator(choice_translator="DeepL(auth)", auth_key=value) is True: - print_textbox(self.parent.textbox_message_log, "Auth key update completed", "INFO") - print_textbox(self.parent.textbox_message_system_log, "Auth key update completed", "INFO") - else: - pass - - def checkbox_auto_clear_chatbox_callback(self): - config.ENABLE_AUTO_CLEAR_CHATBOX = self.checkbox_auto_clear_chatbox.get() - - def checkbox_notice_xsoverlay_callback(self): - config.ENABLE_NOTICE_XSOVERLAY = self.checkbox_notice_xsoverlay.get() - - def delete_window(self): - self.checkbox_input_mic_threshold_check.deselect() - self.checkbox_input_speaker_threshold_check.deselect() - self.checkbox_input_mic_threshold_check_callback() - self.checkbox_input_speaker_threshold_check_callback() - self.parent.transcription_start() - self.parent.foreground_start() - self.parent.checkbox_translation.configure(state="normal") - self.parent.checkbox_transcription_send.configure(state="normal") - self.parent.checkbox_transcription_receive.configure(state="normal") - self.parent.checkbox_foreground.configure(state="normal") - self.parent.tabview_logs.configure(state="normal") - self.parent.textbox_message_log.configure(state="normal") - self.parent.textbox_message_send_log.configure(state="normal") - self.parent.textbox_message_receive_log.configure(state="normal") - self.parent.textbox_message_system_log.configure(state="normal") - self.parent.entry_message_box.configure(state="normal") - self.parent.button_config.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) - self.parent.button_information.configure(state="normal", fg_color=["#3B8ED0", "#1F6AA5"]) - self.withdraw() - self.grab_release() - - def entry_message_format_callback(self, event): - value = self.entry_message_format.get() - if len(value) > 0: - config.MESSAGE_FORMAT = value - - def delete_tabview_config(self, pre_language_yaml_data): - self.tabview_config.delete(pre_language_yaml_data["config_tab_title_ui"]) - self.tabview_config.delete(pre_language_yaml_data["config_tab_title_translation"]) - self.tabview_config.delete(pre_language_yaml_data["config_tab_title_transcription"]) - self.tabview_config.delete(pre_language_yaml_data["config_tab_title_parameter"]) - self.tabview_config.delete(pre_language_yaml_data["config_tab_title_others"]) - - def add_tabview_config(self, language_yaml_data, selectable_languages): - config_tab_title_ui = language_yaml_data["config_tab_title_ui"] - config_tab_title_translation = language_yaml_data["config_tab_title_translation"] - config_tab_title_transcription = language_yaml_data["config_tab_title_transcription"] - config_tab_title_parameter = language_yaml_data["config_tab_title_parameter"] - config_tab_title_others = language_yaml_data["config_tab_title_others"] - - init_lang_text = "Loading..." - - # tabwiew config - self.tabview_config = CTkTabview(self) - self.tabview_config.grid(row=0, column=0, padx=5, pady=5, sticky="nsew") - self.tabview_config.add(config_tab_title_ui) - self.tabview_config.add(config_tab_title_translation) - self.tabview_config.add(config_tab_title_transcription) - self.tabview_config.add(config_tab_title_parameter) - self.tabview_config.add(config_tab_title_others) - self.tabview_config.tab(config_tab_title_ui).grid_columnconfigure(1, weight=1) - self.tabview_config.tab(config_tab_title_translation).grid_columnconfigure([1,2,3], weight=1) - self.tabview_config.tab(config_tab_title_transcription).grid_columnconfigure(1, weight=1) - self.tabview_config.tab(config_tab_title_parameter).grid_columnconfigure(1, weight=1) - self.tabview_config.tab(config_tab_title_others).grid_columnconfigure(1, weight=1) - self.tabview_config._segmented_button.configure(font=CTkFont(family=config.FONT_FAMILY)) - self.tabview_config._segmented_button.grid(sticky="W") - - # tab UI - ## slider transparency - row = 0 - padx = 5 - pady = 1 - self.label_transparency = CTkLabel( - self.tabview_config.tab(config_tab_title_ui), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_transparency.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.slider_transparency = CTkSlider( - self.tabview_config.tab(config_tab_title_ui), - from_=50, - to=100, - command=self.slider_transparency_callback, - variable=DoubleVar(value=config.TRANSPARENCY), - ) - self.slider_transparency.grid(row=row, column=1, columnspan=1, padx=padx, pady=10, sticky="nsew") - - ## optionmenu theme - row += 1 - self.label_appearance_theme = CTkLabel( - self.tabview_config.tab(config_tab_title_ui), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_appearance_theme.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_appearance_theme = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_ui), - values=["Light", "Dark", "System"], - command=self.optionmenu_appearance_theme_callback, - variable=StringVar(value=config.APPEARANCE_THEME), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_appearance_theme.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown appearance theme - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_appearance_theme = CTkScrollableDropdown( - self.optionmenu_appearance_theme, - values=["Light", "Dark", "System"], - justify="left", - button_color="transparent", - command=self.optionmenu_appearance_theme_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_appearance_theme.bind( - "", - lambda e: self.scrollableDropdown_appearance_theme._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_appearance_theme.frame._parent_frame)) else None, - ) - - ## optionmenu UI scaling - row += 1 - self.label_ui_scaling = CTkLabel( - self.tabview_config.tab(config_tab_title_ui), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_ui_scaling.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_ui_scaling = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_ui), - values=["80%", "90%", "100%", "110%", "120%"], - command=self.optionmenu_ui_scaling_callback, - variable=StringVar(value=config.UI_SCALING), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_ui_scaling.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown ui scaling - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_ui_scaling = CTkScrollableDropdown( - self.optionmenu_ui_scaling, - values=["80%", "90%", "100%", "110%", "120%"], - justify="left", - button_color="transparent", - command=self.optionmenu_ui_scaling_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_ui_scaling.bind( - "", - lambda e: self.scrollableDropdown_ui_scaling._iconify() if not str(e.widget).startswith(str(self.scrollableDropdown_ui_scaling.frame._parent_frame)) else None, - ) - - ## optionmenu font family - row += 1 - self.label_font_family = CTkLabel( - self.tabview_config.tab(config_tab_title_ui), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_font_family.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - font_families = list(tk_font.families()) - self.optionmenu_font_family = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_ui), - values=font_families, - command=self.optionmenu_font_family_callback, - variable=StringVar(value=config.FONT_FAMILY), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_font_family.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown font family - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_font_family = CTkScrollableDropdown( - self.optionmenu_font_family, - values=font_families, - justify="left", - button_color="transparent", - command=self.optionmenu_font_family_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_font_family.bind( - "", - lambda e: self.scrollableDropdown_font_family._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_font_family.frame._parent_frame)) else None, - ) - - ## optionmenu ui language - row += 1 - self.label_ui_language = CTkLabel( - self.tabview_config.tab(config_tab_title_ui), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_ui_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - selectable_languages_values = list(selectable_languages.values()) - self.optionmenu_ui_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_ui), - values=selectable_languages_values, - command=self.optionmenu_ui_language_callback, - variable=StringVar(value=selectable_languages[config.UI_LANGUAGE]), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_ui_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown ui language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_ui_language = CTkScrollableDropdown( - self.optionmenu_ui_language, - values=selectable_languages_values, - justify="left", - button_color="transparent", - command=self.optionmenu_ui_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_ui_language.bind( - "", - lambda e: self.scrollableDropdown_ui_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_ui_language.frame._parent_frame)) else None, - ) - - # tab Translation - ## optionmenu translation translator - row = 0 - padx = 5 - pady = 1 - self.label_translation_translator = CTkLabel( - self.tabview_config.tab(config_tab_title_translation), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY), - ) - self.label_translation_translator.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_translation_translator = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_translation), - values=model.getListTranslatorName(), - command=self.optionmenu_translation_translator_callback, - variable=StringVar(value=config.CHOICE_TRANSLATOR), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_translation_translator.grid(row=row, column=1, columnspan=3, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown translation translator - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_translation_translator = CTkScrollableDropdown( - self.optionmenu_translation_translator, - values=model.getListTranslatorName(), - justify="left", - button_color="transparent", - command=self.optionmenu_translation_translator_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_translation_translator.bind( - "", - lambda e: self.scrollableDropdown_translation_translator._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_translator.frame._parent_frame)) else None, - ) - - ## optionmenu translation input language - row +=1 - self.label_translation_input_language = CTkLabel( - self.tabview_config.tab(config_tab_title_translation), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_translation_input_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - - ## select translation input source language - self.optionmenu_translation_input_source_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_translation), - command=self.optionmenu_translation_input_source_language_callback, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), - variable=StringVar(value=config.INPUT_SOURCE_LANG), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_translation_input_source_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown translation input source language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_translation_input_source_language = CTkScrollableDropdown( - self.optionmenu_translation_input_source_language, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), - justify="left", - button_color="transparent", - command=self.optionmenu_translation_input_source_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_translation_input_source_language.bind( - "", - lambda e: self.scrollableDropdown_translation_input_source_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_input_source_language.frame._parent_frame)) else None, - ) - - ## label translation input arrow - self.label_translation_input_arrow = CTkLabel( - self.tabview_config.tab(config_tab_title_translation), - text="-->", - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_translation_input_arrow.grid(row=row, column=2, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## select translation input target language - self.optionmenu_translation_input_target_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_translation), - command=self.optionmenu_translation_input_target_language_callback, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), - variable=StringVar(value=config.INPUT_TARGET_LANG), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_translation_input_target_language.grid(row=row, column=3, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown translation input target language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_translation_input_target_language = CTkScrollableDropdown( - self.optionmenu_translation_input_target_language, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), - justify="left", - button_color="transparent", - command=self.optionmenu_translation_input_target_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_translation_input_target_language.bind( - "", - lambda e: self.scrollableDropdown_translation_input_target_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_input_target_language.frame._parent_frame)) else None, - ) - - ## optionmenu translation output language - row +=1 - self.label_translation_output_language = CTkLabel( - self.tabview_config.tab(config_tab_title_translation), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_translation_output_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - - ## select translation output source language - self.optionmenu_translation_output_source_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_translation), - command=self.optionmenu_translation_output_source_language_callback, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), - variable=StringVar(value=config.OUTPUT_SOURCE_LANG), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_translation_output_source_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown translation output source language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_translation_output_source_language = CTkScrollableDropdown( - self.optionmenu_translation_output_source_language, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["source"].keys()), - justify="left", - button_color="transparent", - command=self.optionmenu_translation_output_source_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_translation_output_source_language.bind( - "", - lambda e: self.scrollableDropdown_translation_output_source_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_output_source_language.frame._parent_frame)) else None, - ) - - ## label translation output arrow - self.label_translation_output_arrow = CTkLabel( - self.tabview_config.tab(config_tab_title_translation), - text="-->", - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_translation_output_arrow.grid(row=row, column=2, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## select translation output target language - self.optionmenu_translation_output_target_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_translation), - command=self.optionmenu_translation_output_target_language_callback, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), - variable=StringVar(value=config.OUTPUT_TARGET_LANG), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_translation_output_target_language.grid(row=row, column=3, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown translation output target language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_translation_output_target_language = CTkScrollableDropdown( - self.optionmenu_translation_output_target_language, - values=list(translation_lang[config.CHOICE_TRANSLATOR]["target"].keys()), - justify="left", - button_color="transparent", - command=self.optionmenu_translation_output_target_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_translation_output_target_language.bind( - "", - lambda e: self.scrollableDropdown_translation_output_target_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_translation_output_target_language.frame._parent_frame)) else None, - ) - - # tab Transcription - ## optionmenu input mic device's host - row = 0 - padx = 5 - pady = 1 - self.label_input_mic_host = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_host.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_input_mic_host = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_transcription), - values=model.getListInputHost(), - command=self.optionmenu_input_mic_host_callback, - variable=StringVar(value=config.CHOICE_MIC_HOST), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_input_mic_host.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown input mic device's host - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_input_mic_host = CTkScrollableDropdown( - self.optionmenu_input_mic_host, - values=model.getListInputHost(), - justify="left", - button_color="transparent", - command=self.optionmenu_input_mic_host_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_input_mic_host.bind( - "", - lambda e: self.scrollableDropdown_input_mic_host._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_mic_host.frame._parent_frame)) else None, - ) - - ## optionmenu input mic device - row += 1 - self.label_input_mic_device = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_device.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_input_mic_device = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_transcription), - values=model.getListInputDevice(), - command=self.optionmenu_input_mic_device_callback, - variable=StringVar(value=config.CHOICE_MIC_DEVICE), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_input_mic_device.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown input mic device - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_input_mic_device = CTkScrollableDropdown( - self.optionmenu_input_mic_device, - values=model.getListInputDevice(), - justify="left", - button_color="transparent", - command=self.optionmenu_input_mic_device_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_input_mic_device.bind( - "", - lambda e: self.scrollableDropdown_input_mic_device._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_mic_device.frame._parent_frame)) else None, - ) - - ## optionmenu input mic voice language - row +=1 - self.label_input_mic_voice_language = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_voice_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_input_mic_voice_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_transcription), - values=list(transcription_lang.keys()), - command=self.optionmenu_input_mic_voice_language_callback, - variable=StringVar(value=config.INPUT_MIC_VOICE_LANGUAGE), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_input_mic_voice_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown input mic voice language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_input_voice_language = CTkScrollableDropdown( - self.optionmenu_input_mic_voice_language, - values=list(transcription_lang.keys()), - justify="left", - button_color="transparent", - command=self.optionmenu_input_mic_voice_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_input_voice_language.bind( - "", - lambda e: self.scrollableDropdown_input_voice_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_voice_language.frame._parent_frame)) else None, - ) - - ## slider input mic energy threshold - row +=1 - self.label_input_mic_energy_threshold = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - - self.slider_input_mic_energy_threshold = CTkSlider( - self.tabview_config.tab(config_tab_title_transcription), - from_=0, - to=config.MAX_MIC_ENERGY_THRESHOLD, - border_width=7, - button_length=0, - button_corner_radius=3, - number_of_steps=config.MAX_MIC_ENERGY_THRESHOLD, - command=self.slider_input_mic_energy_threshold_callback, - variable=IntVar(value=config.INPUT_MIC_ENERGY_THRESHOLD), - ) - self.slider_input_mic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=0, pady=5, sticky="nsew") - - ## progressBar input mic energy threshold - row +=1 - self.checkbox_input_mic_threshold_check = CTkCheckBox( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_input_mic_threshold_check_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_input_mic_threshold_check.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - - self.progressBar_input_mic_energy_threshold = CTkProgressBar( - self.tabview_config.tab(config_tab_title_transcription), - corner_radius=0 - ) - self.progressBar_input_mic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=5, sticky="nsew") - self.progressBar_input_mic_energy_threshold.set(0) - - ## checkbox input mic dynamic energy threshold - row +=1 - self.label_input_mic_dynamic_energy_threshold = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_dynamic_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.checkbox_input_mic_dynamic_energy_threshold = CTkCheckBox( - self.tabview_config.tab(config_tab_title_transcription), - text="", - onvalue=True, - offvalue=False, - command=self.checkbox_input_mic_dynamic_energy_threshold_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_input_mic_dynamic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - if config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD is True: - self.checkbox_input_mic_dynamic_energy_threshold.select() - else: - self.checkbox_input_mic_dynamic_energy_threshold.deselect() - - ## entry input mic record timeout - row +=1 - self.label_input_mic_record_timeout = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_record_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_input_mic_record_timeout = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=config.INPUT_MIC_RECORD_TIMEOUT), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_mic_record_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_mic_record_timeout.bind("", self.entry_input_mic_record_timeout_callback) - - ## entry input mic phrase timeout - row +=1 - self.label_input_mic_phrase_timeout = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_phrase_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_input_mic_phrase_timeout = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=config.INPUT_MIC_PHRASE_TIMEOUT), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_mic_phrase_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_mic_phrase_timeout.bind("", self.entry_input_mic_phrase_timeout_callback) - - ## entry input mic max phrases - row +=1 - self.label_input_mic_max_phrases = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_max_phrases.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_input_mic_max_phrases = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=config.INPUT_MIC_MAX_PHRASES), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_mic_max_phrases.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_mic_max_phrases.bind("", self.entry_input_mic_max_phrases_callback) - - ## entry input mic word filter - row +=1 - self.label_input_mic_word_filter = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_mic_word_filter.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - if len(config.INPUT_MIC_WORD_FILTER) > 0: - textvariable=StringVar(value=",".join(config.INPUT_MIC_WORD_FILTER)) - else: - textvariable=None - self.entry_input_mic_word_filter = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=textvariable, - placeholder_text="AAA,BBB,CCC", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_mic_word_filter.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_mic_word_filter.bind("", self.entry_input_mic_word_filters_callback) - - ## optionmenu input speaker device - row +=1 - self.label_input_speaker_device = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_device.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_input_speaker_device = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_transcription), - values=model.getListOutputDevice(), - command=self.optionmenu_input_speaker_device_callback, - variable=StringVar(value=config.CHOICE_SPEAKER_DEVICE), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_input_speaker_device.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown input speaker device - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_input_speaker_device = CTkScrollableDropdown( - self.optionmenu_input_speaker_device, - values=model.getListOutputDevice(), - justify="left", - button_color="transparent", - command=self.optionmenu_input_speaker_device_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_input_speaker_device.bind( - "", - lambda e: self.scrollableDropdown_input_speaker_device._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_speaker_device.frame._parent_frame)) else None, - ) - - ## optionmenu input speaker voice language - row +=1 - self.label_input_speaker_voice_language = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_voice_language.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.optionmenu_input_speaker_voice_language = CTkOptionMenu( - self.tabview_config.tab(config_tab_title_transcription), - values=list(transcription_lang.keys()), - command=self.optionmenu_input_speaker_voice_language_callback, - variable=StringVar(value=config.INPUT_SPEAKER_VOICE_LANGUAGE), - font=CTkFont(family=config.FONT_FAMILY), - dropdown_font=CTkFont(family=config.FONT_FAMILY), - ) - self.optionmenu_input_speaker_voice_language.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - - ## scrollableDropdown input speaker voice language - if SCROLLABLE_DROPDOWN: - self.scrollableDropdown_input_speaker_voice_language = CTkScrollableDropdown( - self.optionmenu_input_speaker_voice_language, - values=list(transcription_lang.keys()), - justify="left", - button_color="transparent", - command=self.optionmenu_input_speaker_voice_language_callback, - font=CTkFont(family=config.FONT_FAMILY), - ) - self.scrollableDropdown_input_speaker_voice_language.bind( - "", - lambda e: self.scrollableDropdown_input_speaker_voice_language._withdraw() if not str(e.widget).startswith(str(self.scrollableDropdown_input_speaker_voice_language.frame._parent_frame)) else None, - ) - - ## entry input speaker energy threshold - row +=1 - self.label_input_speaker_energy_threshold = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - - ## progressBar input speaker energy threshold - self.slider_input_speaker_energy_threshold = CTkSlider( - self.tabview_config.tab(config_tab_title_transcription), - from_=0, - to=config.MAX_SPEAKER_ENERGY_THRESHOLD, - border_width=7, - button_length=0, - button_corner_radius=3, - number_of_steps=config.MAX_SPEAKER_ENERGY_THRESHOLD, - command=self.slider_input_speaker_energy_threshold_callback, - variable=IntVar(value=config.INPUT_SPEAKER_ENERGY_THRESHOLD), - ) - self.slider_input_speaker_energy_threshold.grid(row=row, column=1, columnspan=1, padx=0, pady=5, sticky="nsew") - - ## progressBar input speaker energy threshold - row +=1 - self.checkbox_input_speaker_threshold_check = CTkCheckBox( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - onvalue=True, - offvalue=False, - command=self.checkbox_input_speaker_threshold_check_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_input_speaker_threshold_check.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - - self.progressBar_input_speaker_energy_threshold = CTkProgressBar( - self.tabview_config.tab(config_tab_title_transcription), - corner_radius=0 - ) - self.progressBar_input_speaker_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=5, sticky="nsew") - self.progressBar_input_speaker_energy_threshold.set(0) - - ## checkbox input speaker dynamic energy threshold - row +=1 - self.label_input_speaker_dynamic_energy_threshold = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_dynamic_energy_threshold.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.checkbox_input_speaker_dynamic_energy_threshold = CTkCheckBox( - self.tabview_config.tab(config_tab_title_transcription), - text="", - onvalue=True, - offvalue=False, - command=self.checkbox_input_speaker_dynamic_energy_threshold_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_input_speaker_dynamic_energy_threshold.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - if config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD is True: - self.checkbox_input_speaker_dynamic_energy_threshold.select() - else: - self.checkbox_input_speaker_dynamic_energy_threshold.deselect() - - ## entry input speaker record timeout - row +=1 - self.label_input_speaker_record_timeout = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_record_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_input_speaker_record_timeout = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=config.INPUT_SPEAKER_RECORD_TIMEOUT), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_speaker_record_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_speaker_record_timeout.bind("", self.entry_input_speaker_record_timeout_callback) - - ## entry input speaker phrase timeout - row +=1 - self.label_input_speaker_phrase_timeout = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_phrase_timeout.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_input_speaker_phrase_timeout = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=config.INPUT_SPEAKER_PHRASE_TIMEOUT), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_speaker_phrase_timeout.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_speaker_phrase_timeout.bind("", self.entry_input_speaker_phrase_timeout_callback) - - ## entry input speaker max phrases - row +=1 - self.label_input_speaker_max_phrases = CTkLabel( - self.tabview_config.tab(config_tab_title_transcription), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_input_speaker_max_phrases.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_input_speaker_max_phrases = CTkEntry( - self.tabview_config.tab(config_tab_title_transcription), - textvariable=StringVar(value=config.INPUT_SPEAKER_MAX_PHRASES), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_input_speaker_max_phrases.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_input_speaker_max_phrases.bind("", self.entry_input_speaker_max_phrases_callback) - - # tab Parameter - ## entry ip address - row = 0 - padx = 5 - pady = 1 - self.label_ip_address = CTkLabel( - self.tabview_config.tab(config_tab_title_parameter), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_ip_address.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_ip_address = CTkEntry( - self.tabview_config.tab(config_tab_title_parameter), - textvariable=StringVar(value=config.OSC_IP_ADDRESS), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_ip_address.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_ip_address.bind("", self.entry_ip_address_callback) - - ## entry port - row +=1 - self.label_port = CTkLabel( - self.tabview_config.tab(config_tab_title_parameter), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_port.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_port = CTkEntry( - self.tabview_config.tab(config_tab_title_parameter), - textvariable=StringVar(value=config.OSC_PORT), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_port.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_port.bind("", self.entry_port_callback) - - ## entry authkey - row +=1 - self.label_authkey = CTkLabel( - self.tabview_config.tab(config_tab_title_parameter), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_authkey.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_authkey = CTkEntry( - self.tabview_config.tab(config_tab_title_parameter), - textvariable=StringVar(value=config.AUTH_KEYS["DeepL(auth)"]), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_authkey.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_authkey.bind("", self.entry_authkey_callback) - - ## entry message format - row +=1 - self.label_message_format = CTkLabel( - self.tabview_config.tab(config_tab_title_parameter), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_message_format.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.entry_message_format = CTkEntry( - self.tabview_config.tab(config_tab_title_parameter), - textvariable=StringVar(value=config.MESSAGE_FORMAT), - font=CTkFont(family=config.FONT_FAMILY) - ) - self.entry_message_format.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - self.entry_message_format.bind("", self.entry_message_format_callback) - - # tab Others - ## checkbox auto clear chat box - row = 0 - self.label_checkbox_auto_clear_chatbox = CTkLabel( - self.tabview_config.tab(config_tab_title_others), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_checkbox_auto_clear_chatbox.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.checkbox_auto_clear_chatbox = CTkCheckBox( - self.tabview_config.tab(config_tab_title_others), - text="", - onvalue=True, - offvalue=False, - command=self.checkbox_auto_clear_chatbox_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_auto_clear_chatbox.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - if config.ENABLE_AUTO_CLEAR_CHATBOX is True: - self.checkbox_auto_clear_chatbox.select() - else: - self.checkbox_auto_clear_chatbox.deselect() - - # checkbox notice xsoverlay - row += 1 - self.label_checkbox_notice_xsoverlay = CTkLabel( - self.tabview_config.tab(config_tab_title_others), - text=init_lang_text, - fg_color="transparent", - font=CTkFont(family=config.FONT_FAMILY) - ) - self.label_checkbox_notice_xsoverlay.grid(row=row, column=0, columnspan=1, padx=padx, pady=pady, sticky="nsw") - self.checkbox_notice_xsoverlay = CTkCheckBox( - self.tabview_config.tab(config_tab_title_others), - text="", - onvalue=True, - offvalue=False, - command=self.checkbox_notice_xsoverlay_callback, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.checkbox_notice_xsoverlay.grid(row=row, column=1, columnspan=1, padx=padx, pady=pady, sticky="nsew") - if config.ENABLE_NOTICE_XSOVERLAY is True: - self.checkbox_notice_xsoverlay.select() - else: - self.checkbox_notice_xsoverlay.deselect() - widget_config_window_label_setter(self, language_yaml_data) \ No newline at end of file diff --git a/test_model/window_information.py b/test_model/window_information.py deleted file mode 100644 index cc8d8792..00000000 --- a/test_model/window_information.py +++ /dev/null @@ -1,161 +0,0 @@ -import os -from customtkinter import CTkToplevel, CTkTextbox, CTkFont -from config import config - -class ToplevelWindowInformation(CTkToplevel): - def __init__(self, parent, *args, **kwargs): - super().__init__(parent, *args, **kwargs) - self.withdraw() - self.parent = parent - self.grid_columnconfigure(0, weight=1) - self.grid_rowconfigure(0, weight=1) - # self.geometry(f"{500}x{300}") - self.minsize(500, 300) - - self.after(200, lambda: self.iconbitmap(os.path.join(os.path.dirname(__file__), "img", "app.ico"))) - self.title("Information") - # create textbox information - self.textbox_information = CTkTextbox( - self, - font=CTkFont(family=config.FONT_FAMILY) - ) - self.textbox_information.grid(row=0, column=0, padx=(10, 10), pady=(10, 10), sticky="nsew") - textbox_information_message = """VRCT(v1.3.2) - -# 概要 -VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツールになります。 -翻訳エンジンを使用してメッセージとその翻訳部分を同時に送信することができます。 -(翻訳エンジンはDeepL,Google,Bingに対応) - -# 使用方法 - 初期設定時 - 0. VRChatのOSCを有効にする(重要) - - (任意) - 1. DeepLのAPIを使用するためにアカウント登録し、認証キーを取得する - 2. ギアアイコンのボタンでconfigウィンドウを開く - 3. ParameterタブのDeepL Auth Keyに認証キーを記載 - 4. configウィンドウを閉じる - - 通常使用時 - 1. メッセージボックスにメッセージを記入 - 2. Enterキーを押し、メッセージを送信する - -# その他の設定 - translation チェックボックス: 翻訳の有効無効 - voice2chatbox チェックボックス : マイクの音声を文字起こししてチャットボックスに送信する - speaker2log チェックボックス : スピーカーの音声から文字起こししてログに表示する - foreground チェックボックス: 最前面表示の有効無効 - - テキストボックス - logタブ - すべてのログを表示 - sendタブ - 送信したメッセージを表示 - receiveタブ - 受信したメッセージを表示 - systemタブ - 機能についてのメッセージを表示 - - configウィンドウ - UIタブ - Transparency: ウィンドウの透過度の調整 - Appearance Theme: ウィンドウテーマを選択 - UI Scaling: UIサイズを調整 - Font Family: 表示フォントを選択 - UI Language: UIの表示言語を選択 - Translationタブ - Select Translator: 翻訳エンジンの変更 - Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択 - Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択 - Transcriptionタブ - Input Mic Host: マイクのホストAPIを選択 - Input Mic Device: マイクを選択 - Input Mic Voice Language: 入力する音声の言語 - Input Mic Energy Threshold: 音声取得のしきい値 - Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化 - Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - Input Mic Record Timeout: 音声の区切りの無音時間 - Input Mic Phase Timeout: 文字起こしする音声時間の上限 - Input Mic Max Phrases: 保留する単語の上限 - Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC) - Input Speaker Device: スピーカーを選択 - Input Speaker Voice Language: 受信する音声の言語 - Input Speaker Energy Threshold: 音声取得のしきい値 - Check threshold point: Input Speaker Energy Thresholdのしきい値を視覚化 - Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - Input Speaker Record Timeout: 音声の区切りの無音時間 - Input Speaker Phase Timeout: 文字起こしする音声時間の上限 - Input Speaker Max Phrases: 保留する単語の上限 - Parameterタブ - OSC IP address: 変更不要 - OSC port: 変更不要 - DeepL Auth key: DeepLの認証キーの設定 - Message Format: 送信するメッセージのデコレーションの設定 - [message]がメッセージボックスに記入したメッセージに置換される - [translation]が翻訳されたメッセージに置換される - 初期フォーマット:"[message]([translation])" - Othersタブ - Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする - (New!) Notification XSOverlay: XSOverlayの通知機能を有効(VR only) - - 設定の初期化 - config.jsonを削除 - -# お問い合わせ -要望などはTwitterまで -https://twitter.com/misya_ai - -# アップデート履歴 -[2023-05-29: v0.1b] v0.1b リリース -[2023-05-30: v0.2b] -- configボタンをギアアイコンに変更 -- 詳細情報のボタンを追加 -- 翻訳機能有効無効のチェックボックスを追加 -- 最前面表示の有効無効のチェックボックスを追加 -- いくつかのバグを修正 -[2023-06-03: v0.3b] -- 全体的にUIを刷新 -- 透過機能を追加 -- テーマのLight/Dark/Systemのモードの変更機能を追加 -- UIのスケール変更機能を追加 -- フォントの変更機能を追加 -[2023-06-06: v0.4b] -- 翻訳エンジンを追加 -- 入力と出力の翻訳言語を選択できるように変更 -[2023-06-20: v1.0] -- マイクからの音声の文字起こし機能を追加 -- スピーカーからの音声の文字起こし機能を追加 -[2023-06-28: v1.1] -- いくつかのバクを修正 -- 翻訳/文字起こし言語の表記を略称からわかりやすい文字に変更 -- 文字起こしの処理の軽量化 -[2023-07-05: v1.2] -- 文字起こし精度の向上 -[2023-07-21: v1.3] -- UIの表示言語を日本語/英語を選択できる機能を追加 -- Energy Thresholdの視覚化機能を追加 -- 文字起こしの誤認識対策のため、Word Filterを追加 -- WASAPI以外のHostAPIでもマイクとして使用できるようにHostAPIを選択できる機能を追加 -- メッセージ送信後に書き込んだメッセージを空にするか選択できる機能を追加 -- バグ対策のため、translation/voice2chatbox/speaker2log/foregroundは起動時はOFFになるように変更 -- バグ対策のため、スピーカーについて既定デバイス以外を選択した時にERRORが出るように変更 -- 半角入力時に一部の文字が書き込めないバグを修正 -[2023-07-22: v1.3.1] -- UIの表示言語選択に韓国語を追加 -[2023-07-30: v1.3.2] -- 試験的にXSOverlayへの通知機能を追加 -- checkbox ONの状態でもConfigを開けるように変更 -- 文字起こし言語の表示を修正 -- いくつかのバグを修正 - -# 注意事項 -再配布とかはやめてね -""" - - self.textbox_information.insert("end", textbox_information_message) - self.textbox_information.configure(state='disabled') - self.protocol("WM_DELETE_WINDOW", self.delete_window) - - def delete_window(self): - self.withdraw() \ No newline at end of file diff --git a/window_help_and_info.py b/window_help_and_info.py deleted file mode 100644 index cc0d3b7f..00000000 --- a/window_help_and_info.py +++ /dev/null @@ -1,154 +0,0 @@ -import os -from customtkinter import CTkToplevel, CTkTextbox, CTkFont - -class ToplevelWindowInformation(CTkToplevel): - def __init__(self, parent, *args, **kwargs): - super().__init__(parent, *args, **kwargs) - self.withdraw() - self.parent = parent - self.grid_columnconfigure(0, weight=1) - self.grid_rowconfigure(0, weight=1) - # self.geometry(f"{500}x{300}") - self.minsize(500, 300) - - self.after(200, lambda: self.iconbitmap(os.path.join(os.path.dirname(__file__), "img", "app.ico"))) - self.title("Information") - # create textbox information - self.textbox_information = CTkTextbox( - self, - font=CTkFont(family=self.parent.FONT_FAMILY) - ) - self.textbox_information.grid(row=0, column=0, padx=(10, 10), pady=(10, 10), sticky="nsew") - textbox_information_message = """VRCT(v1.3.1) - -# 概要 -VRChatで使用されるChatBoxをOSC経由でメッセージを送信するツールになります。 -翻訳エンジンを使用してメッセージとその翻訳部分を同時に送信することができます。 -(翻訳エンジンはDeepL,Google,Bingに対応) - -# 使用方法 - 初期設定時 - 0. VRChatのOSCを有効にする(重要) - - (任意) - 1. DeepLのAPIを使用するためにアカウント登録し、認証キーを取得する - 2. ギアアイコンのボタンでconfigウィンドウを開く - 3. ParameterタブのDeepL Auth Keyに認証キーを記載 - 4. configウィンドウを閉じる - - 通常使用時 - 1. メッセージボックスにメッセージを記入 - 2. Enterキーを押し、メッセージを送信する - -# その他の設定 - translation チェックボックス: 翻訳の有効無効 - voice2chatbox チェックボックス : マイクの音声を文字起こししてチャットボックスに送信する - speaker2log チェックボックス : スピーカーの音声から文字起こししてログに表示する - foreground チェックボックス: 最前面表示の有効無効 - - テキストボックス - logタブ - すべてのログを表示 - sendタブ - 送信したメッセージを表示 - receiveタブ - 受信したメッセージを表示 - systemタブ - 機能についてのメッセージを表示 - - configウィンドウ - UIタブ - Transparency: ウィンドウの透過度の調整 - Appearance Theme: ウィンドウテーマを選択 - UI Scaling: UIサイズを調整 - Font Family: 表示フォントを選択 - (New!) UI Language: UIの表示言語を選択 - Translationタブ - Select Translator: 翻訳エンジンの変更 - Send Language: 送信するメッセージに対して翻訳する言語[source, target]を選択 - Receive Language: 受信したメッセージに対して翻訳する言語[source, target]を選択 - Transcriptionタブ - (New!) Input Mic Host: マイクのホストAPIを選択 - Input Mic Device: マイクを選択 - Input Mic Voice Language: 入力する音声の言語 - Input Mic Energy Threshold: 音声取得のしきい値 - (New!) Check threshold point: Input Mic Energy Thresholdのしきい値を視覚化 - Input Mic Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - Input Mic Record Timeout: 音声の区切りの無音時間 - Input Mic Phase Timeout: 文字起こしする音声時間の上限 - Input Mic Max Phrases: 保留する単語の上限 - (New!) Input Mic Word Filter: MICの文字起こし時にWord Filterで設定した文字が入っていた場合にChatboxに表示しない (ex AAA,BBB,CCC) - Input Speaker Device: スピーカーを選択 - Input Speaker Voice Language: 受信する音声の言語 - Input Speaker Energy Threshold: 音声取得のしきい値 - (New!) Check threshold point: (New!)Input Speaker Energy Thresholdのしきい値を視覚化 - Input Speaker Dynamic Energy Threshold: 音声取得のしきい値の自動調整 - Input Speaker Record Timeout: 音声の区切りの無音時間 - Input Speaker Phase Timeout: 文字起こしする音声時間の上限 - Input Speaker Max Phrases: 保留する単語の上限 - Parameterタブ - OSC IP address: 変更不要 - OSC port: 変更不要 - DeepL Auth key: DeepLの認証キーの設定 - Message Format: 送信するメッセージのデコレーションの設定 - [message]がメッセージボックスに記入したメッセージに置換される - [translation]が翻訳されたメッセージに置換される - 初期フォーマット:"[message]([translation])" - Othersタブ - (New!) Auto clear chat box: メッセージ送信後に書き込んだメッセージを空にする - - 設定の初期化 - config.jsonを削除 - -# お問い合わせ -要望などはTwitterまで -https://twitter.com/misya_ai - -# アップデート履歴 -[2023-05-29: v0.1b] v0.1b リリース -[2023-05-30: v0.2b] -- configボタンをギアアイコンに変更 -- 詳細情報のボタンを追加 -- 翻訳機能有効無効のチェックボックスを追加 -- 最前面表示の有効無効のチェックボックスを追加 -- いくつかのバグを修正 -[2023-06-03: v0.3b] -- 全体的にUIを刷新 -- 透過機能を追加 -- テーマのLight/Dark/Systemのモードの変更機能を追加 -- UIのスケール変更機能を追加 -- フォントの変更機能を追加 -[2023-06-06: v0.4b] -- 翻訳エンジンを追加 -- 入力と出力の翻訳言語を選択できるように変更 -[2023-06-20: v1.0] -- マイクからの音声の文字起こし機能を追加 -- スピーカーからの音声の文字起こし機能を追加 -[2023-06-28: v1.1] -- いくつかのバクを修正 -- 翻訳/文字起こし言語の表記を略称からわかりやすい文字に変更 -- 文字起こしの処理の軽量化 -[2023-07-05: v1.2] -- 文字起こし精度の向上 -[2023-07-21: v1.3] -- UIの表示言語を日本語/英語を選択できる機能を追加 -- Energy Thresholdの視覚化機能を追加 -- 文字起こしの誤認識対策のため、Word Filterを追加 -- WASAPI以外のHostAPIでもマイクとして使用できるようにHostAPIを選択できる機能を追加 -- メッセージ送信後に書き込んだメッセージを空にするか選択できる機能を追加 -- バグ対策のため、translation/voice2chatbox/speaker2log/foregroundは起動時はOFFになるように変更 -- バグ対策のため、スピーカーについて既定デバイス以外を選択した時にERRORが出るように変更 -- 半角入力時に一部の文字が書き込めないバグを修正 -[2023-07-22: v1.3.1] -- UIの表示言語選択に韓国語を追加 - -# 注意事項 -再配布とかはやめてね -""" - - self.textbox_information.insert("end", textbox_information_message) - self.textbox_information.configure(state='disabled') - self.protocol("WM_DELETE_WINDOW", self.delete_window) - - def delete_window(self): - self.withdraw() \ No newline at end of file From f24ca9c30dd89c4b58ce70e8faaa7354f2eb99c4 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 17:58:49 +0900 Subject: [PATCH 289/355] =?UTF-8?q?[bugfix]=20Splash=20Window:=20toolwindo?= =?UTF-8?q?w=E3=82=92True=E3=81=AB=E3=81=97=E5=BF=98=E3=82=8C=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=81=A6=E3=80=81=E8=B5=B7=E5=8B=95=E6=99=82=E3=81=AB?= =?UTF-8?q?=E3=82=BF=E3=82=B9=E3=82=AF=E3=83=90=E3=83=BC=E3=81=AB=E8=A6=8B?= =?UTF-8?q?=E3=81=88=E3=81=A6=E3=81=97=E3=81=BE=E3=81=A3=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/splash_window/SplashWindow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/vrct_gui/splash_window/SplashWindow.py b/vrct_gui/splash_window/SplashWindow.py index d5e1d2e7..96537c5e 100644 --- a/vrct_gui/splash_window/SplashWindow.py +++ b/vrct_gui/splash_window/SplashWindow.py @@ -8,6 +8,7 @@ class SplashWindow(CTkToplevel): self.overrideredirect(True) self.configure(fg_color="#292a2d") self.title("SplashWindow") + self.wm_attributes("-toolwindow", True) sw=self.winfo_screenwidth() From bfc5ff04f85e1cd3f247b192f1c928cc322056d4 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 18:23:55 +0900 Subject: [PATCH 290/355] =?UTF-8?q?[Update]=20Config=20Window:=20UI=20Them?= =?UTF-8?q?e=20Dark=E3=83=A2=E3=83=BC=E3=83=89=E5=9B=BA=E5=AE=9A=E3=80=82?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E9=A0=85=E7=9B=AE=E3=81=8B=E3=82=89=E3=83=86?= =?UTF-8?q?=E3=83=BC=E3=83=9E=E9=81=B8=E6=8A=9E=E3=82=92=E3=81=A7=E3=81=8D?= =?UTF-8?q?=E3=81=AA=E3=81=8F=E3=81=97=E3=81=A6=E3=80=81config.json?= =?UTF-8?q?=E3=82=92=E7=B7=A8=E9=9B=86=E3=81=97=E3=81=9F=E3=81=A8=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=82=82=E5=B8=B8=E3=81=ABDark=E3=83=A2=E3=83=BC?= =?UTF-8?q?=E3=83=89=E3=81=AB=E5=9B=BA=E5=AE=9A=E3=80=82=E2=80=BB=E9=A0=85?= =?UTF-8?q?=E7=9B=AE=E3=81=A8=E3=81=97=E3=81=A6=E6=AE=8B=E3=81=97=E3=81=A6?= =?UTF-8?q?=E3=81=84=E3=82=8B=E3=81=AE=E3=81=AF=E3=80=81Light=E3=83=A2?= =?UTF-8?q?=E3=83=BC=E3=83=89=E5=AE=9F=E8=A3=85=E4=BA=88=E5=AE=9A=E3=81=A7?= =?UTF-8?q?=E9=96=8B=E7=99=BA=E4=B8=AD=E3=81=A8=E3=81=84=E3=81=86=E4=BA=8B?= =?UTF-8?q?=E3=82=92=E8=A6=8B=E3=81=9B=E3=81=9F=E3=81=84=E3=81=9F=E3=82=81?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 15 ++++++++++++--- vrct_gui/_changeConfigWindowWidgetsStatus.py | 4 ++++ 2 files changed, 16 insertions(+), 3 deletions(-) diff --git a/view.py b/view.py index 66ec769e..89af1564 100644 --- a/view.py +++ b/view.py @@ -16,7 +16,8 @@ from config import config class View(): def __init__(self): self.settings = SimpleNamespace() - theme = get_appearance_mode() if config.APPEARANCE_THEME == "System" else config.APPEARANCE_THEME + # theme = get_appearance_mode() if config.APPEARANCE_THEME == "System" else config.APPEARANCE_THEME + theme = "Dark" all_ctm = ColorThemeManager(theme) all_uism = UiScalingManager(config.UI_SCALING) image_file = ImageFileManager(theme) @@ -161,9 +162,11 @@ class View(): VAR_LABEL_APPEARANCE_THEME=StringVar(value=i18n.t("config_window.appearance_theme.label")), VAR_DESC_APPEARANCE_THEME=StringVar(value=i18n.t("config_window.appearance_theme.desc")), - LIST_APPEARANCE_THEME=["Light", "Dark", "System"], + LIST_APPEARANCE_THEME=["Dark"], + # LIST_APPEARANCE_THEME=["Light", "Dark", "System"], CALLBACK_SET_APPEARANCE_THEME=None, - VAR_APPEARANCE_THEME=StringVar(value=config.APPEARANCE_THEME), + VAR_APPEARANCE_THEME=StringVar(value="Dark"), + # VAR_APPEARANCE_THEME=StringVar(value=config.APPEARANCE_THEME), VAR_LABEL_UI_SCALING=StringVar(value=i18n.t("config_window.ui_size.label")), VAR_DESC_UI_SCALING=None, @@ -450,6 +453,12 @@ class View(): self.enableConfigWindowCompactMode() vrct_gui.config_window.setting_box_compact_mode_switch_box.select() + vrct_gui._changeConfigWindowWidgetsStatus( + status="disabled", + target_names=[ + "sb__optionmenu_appearance_theme", + ] + ) if config.CHOICE_MIC_HOST == "NoHost": diff --git a/vrct_gui/_changeConfigWindowWidgetsStatus.py b/vrct_gui/_changeConfigWindowWidgetsStatus.py index 39ca924b..0263837b 100644 --- a/vrct_gui/_changeConfigWindowWidgetsStatus.py +++ b/vrct_gui/_changeConfigWindowWidgetsStatus.py @@ -32,6 +32,10 @@ def _changeConfigWindowWidgetsStatus(config_window, settings, view_variable, sta target_widget = config_window.sb__widgets["sb__optionmenu_speaker_device"] disableOptionmenuWidget(target_widget) + case "sb__optionmenu_appearance_theme": + if status == "disabled": + target_widget = config_window.sb__widgets["sb__optionmenu_appearance_theme"] + disableOptionmenuWidget(target_widget) case _: raise ValueError(f"No matching case for target_name: {target_name}") From 1bd98c77698b416e3bb8dcd2104bd6ad3c673493 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 19:56:30 +0900 Subject: [PATCH 291/355] =?UTF-8?q?[Update]=20Config=20Window:=20=E5=86=8D?= =?UTF-8?q?=E8=B5=B7=E5=8B=95=E3=81=8C=E5=BF=85=E8=A6=81=E3=81=AA=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=E3=81=8C=E3=81=82=E3=82=8B=E5=A0=B4=E5=90=88=E3=81=AB?= =?UTF-8?q?=E3=80=81=E5=86=8D=E8=B5=B7=E5=8B=95=E3=83=9C=E3=82=BF=E3=83=B3?= =?UTF-8?q?=E3=82=92=E8=A1=A8=E7=A4=BA=E3=80=82=EF=BC=88=E7=8F=BE=E7=8A=B6?= =?UTF-8?q?=E3=80=81=E8=A8=AD=E5=AE=9A=E3=81=8C=E5=A4=89=E3=82=8F=E3=82=89?= =?UTF-8?q?=E3=81=AA=E3=81=8F=E3=81=A6=E3=82=82=E5=A4=89=E6=9B=B4=E3=81=97?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=A8=E3=81=99=E3=82=8B=E3=81=A8=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=95=E3=82=8C=E3=82=8B=E7=8A=B6=E6=85=8B=E3=81=A7?= =?UTF-8?q?=E3=81=AF=E3=81=82=E3=82=8B=E3=80=82=E4=BE=8B:=20UI=20size=2010?= =?UTF-8?q?0%=E3=81=8B=E3=82=89=E3=82=82=E3=81=86=E4=B8=80=E5=BA=A6100%?= =?UTF-8?q?=E3=82=92=E9=81=B8=E6=8A=9E=E3=81=97=E3=81=9F=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AA=E3=81=A9=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 6 ++- locales/en.yml | 1 + locales/ja.yml | 1 + view.py | 6 +++ .../_createRestartButton.py | 37 +++++++++++++++++++ .../createSettingBoxTopBar.py | 5 +++ .../main_window/createMainWindowWidgets.py | 16 -------- vrct_gui/ui_managers/ColorThemeManager.py | 7 ++++ vrct_gui/ui_managers/UiScalingManager.py | 4 ++ 9 files changed, 65 insertions(+), 18 deletions(-) create mode 100644 vrct_gui/config_window/widgets/createSettingBoxTopBar/_createRestartButton.py diff --git a/controller.py b/controller.py index 3164b0f7..d6205f82 100644 --- a/controller.py +++ b/controller.py @@ -11,8 +11,6 @@ def callbackUpdateSoftware(): model.updateSoftware() def callbackRestartSoftware(): - print("callbackRestartSoftware") - # model.updateSoftware(restart=True) model.reStartSoftware() # func transcription send message @@ -343,22 +341,26 @@ def callbackSetTransparency(value): def callbackSetAppearance(value): print("callbackSetAppearance", value) config.APPEARANCE_THEME = value + view.showRestartButton() def callbackSetUiScaling(value): print("callbackSetUiScaling", value) config.UI_SCALING = value new_scaling_float = int(value.replace("%", "")) / 100 print("callbackSetUiScaling_new_scaling_float", new_scaling_float) + view.showRestartButton() def callbackSetFontFamily(value): print("callbackSetFontFamily", value) config.FONT_FAMILY = value + view.showRestartButton() def callbackSetUiLanguage(value): print("callbackSetUiLanguage", value) value = get_key_by_value(selectable_languages, value) print("callbackSetUiLanguage__after_get_key_by_value", value) config.UI_LANGUAGE = value + view.showRestartButton() # Translation Tab def callbackSetDeeplAuthkey(value): diff --git a/locales/en.yml b/locales/en.yml index 126618bc..55a6f92d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -28,6 +28,7 @@ config_window: config_title: Settings compact_mode: Compact Mode version: version %{version} + restart_message: Apply changes with a restart. side_menu_labels: appearance: Appearance translation: Translation diff --git a/locales/ja.yml b/locales/ja.yml index ef2b2e3d..24697ccc 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -29,6 +29,7 @@ config_window: config_title: 設定 compact_mode: コンパクトモード version: バージョン %{version} + restart_message: 再起動して変更を適用する。 side_menu_labels: appearance: デザイン translation: 翻訳 diff --git a/view.py b/view.py index 89af1564..8b7434b7 100644 --- a/view.py +++ b/view.py @@ -139,6 +139,7 @@ class View(): VAR_VERSION=StringVar(value=i18n.t("config_window.version", version=config.VERSION)), VAR_CONFIG_WINDOW_TITLE=StringVar(value=i18n.t("config_window.config_title")), VAR_CONFIG_WINDOW_COMPACT_MODE_LABEL=StringVar(value=i18n.t("config_window.compact_mode")), + VAR_CONFIG_WINDOW_RESTART_BUTTON_LABEL=StringVar(value=i18n.t("config_window.restart_message")), # Side Menu Labels @@ -710,6 +711,11 @@ class View(): # Config Window + def showRestartButton(self): + vrct_gui.config_window.restart_button_container.grid() + def hideRestartButton(self): + vrct_gui.config_window.restart_button_container.grid_remove() + def _updateActiveSettingBoxTabNo(self, active_setting_box_tab_attr_name:str): self.view_variable.ACTIVE_SETTING_BOX_TAB_ATTR_NAME = active_setting_box_tab_attr_name diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createRestartButton.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createRestartButton.py new file mode 100644 index 00000000..58e23b47 --- /dev/null +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createRestartButton.py @@ -0,0 +1,37 @@ +from customtkinter import CTkFont, CTkFrame, CTkLabel +from utils import callFunctionIfCallable +from ....ui_utils import bindButtonFunctionAndColor + +def _createRestartButton(parent_widget, config_window, settings, view_variable, column_num): + + parent_widget.grid_columnconfigure(0, weight=1) + config_window.restart_button_container = CTkFrame(parent_widget, corner_radius=20, fg_color=settings.ctm.RESTART_BUTTON_BG_COLOR, width=0, height=0, cursor="hand2") + config_window.restart_button_container.grid(row=0, column=column_num, padx=settings.uism.RESTART_BUTTON_PADX, sticky="ew") + + + config_window.restart_button_container.grid_rowconfigure(0, weight=1) + config_window.restart_button_label = CTkLabel( + config_window.restart_button_container, + height=0, + textvariable=view_variable.VAR_CONFIG_WINDOW_RESTART_BUTTON_LABEL, + anchor="w", + font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.RESTART_BUTTON_LABEL_FONT_SIZE, weight="normal"), + text_color=settings.ctm.LABELS_TEXT_COLOR + ) + config_window.restart_button_label.grid(row=0, column=0, padx=20, pady=10) + + + + bindButtonFunctionAndColor( + target_widgets=[ + config_window.restart_button_container, + config_window.restart_button_label, + ], + enter_color=settings.ctm.RESTART_BUTTON_HOVERED_BG_COLOR, + leave_color=settings.ctm.RESTART_BUTTON_BG_COLOR, + clicked_color=settings.ctm.RESTART_BUTTON_CLICKED_BG_COLOR, + buttonReleasedFunction=lambda _e: callFunctionIfCallable(view_variable.CALLBACK_RESTART_SOFTWARE), + ) + + + config_window.restart_button_container.grid_remove() diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py index cac83acb..5dad5207 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py @@ -1,6 +1,7 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel from ._createSettingBoxTitle import _createSettingBoxTitle +from ._createRestartButton import _createRestartButton from ._createSettingBoxCompactModeButton import _createSettingBoxCompactModeButton from ....ui_utils import getLatestHeight, getLatestWidth @@ -22,6 +23,10 @@ def createSettingBoxTopBar(config_window, settings, view_variable): config_window.setting_box_top_bar.grid_columnconfigure(column_num, weight=1) column_num+=1 + # Restart Button(Tmp) + _createRestartButton(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings, view_variable=view_variable, column_num=column_num) + column_num+=1 + _createSettingBoxCompactModeButton(parent_widget=config_window.setting_box_top_bar, config_window=config_window, settings=settings, view_variable=view_variable, column_num=column_num) column_num+=1 diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 14b0eda3..c42ba7e5 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -43,22 +43,6 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): # Main Top Bar Container - Right Side # start from 3 main_topbar_column=3 - # Restart Button(Tmp) - vrct_gui.restart_button_container = createButtonWithImage( - parent_widget=vrct_gui.main_topbar_container, - button_fg_color=settings.ctm.HELP_AND_INFO_BUTTON_BG_COLOR, - button_enter_color=settings.ctm.HELP_AND_INFO_BUTTON_HOVERED_BG_COLOR, - button_clicked_color=settings.ctm.HELP_AND_INFO_BUTTON_CLICKED_BG_COLOR, - button_image_file=settings.image_file.VRCT_LOGO_MARK, - button_image_size=settings.uism.HELP_AND_INFO_BUTTON_SIZE, - button_ipadxy=settings.uism.HELP_AND_INFO_BUTTON_IPADXY, - button_command=lambda e: callFunctionIfCallable(view_variable.CALLBACK_RESTART_SOFTWARE), - corner_radius=settings.uism.HELP_AND_INFO_BUTTON_CORNER_RADIUS, - ) - vrct_gui.restart_button_container.grid(row=0, column=main_topbar_column, padx=settings.uism.HELP_AND_INFO_BUTTON_PADX, pady=settings.uism.TOP_BAR_BUTTON_PADY, sticky="e") - main_topbar_column+=1 - - # Update Available Button vrct_gui.update_available_container = CTkFrame( vrct_gui.main_topbar_container, diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 0f46aa14..5af8f120 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -225,6 +225,13 @@ class ColorThemeManager(): # Top bar self.config_window.TOP_BAR_BG_COLOR = self.DARK_850_COLOR + + # Restart Button + self.config_window.RESTART_BUTTON_BG_COLOR = self.PRIMARY_600_COLOR + self.config_window.RESTART_BUTTON_HOVERED_BG_COLOR = self.PRIMARY_500_COLOR + self.config_window.RESTART_BUTTON_CLICKED_BG_COLOR = self.PRIMARY_700_COLOR + + # Compact Mode self.config_window.COMPACT_MODE_SWITCH_BOX_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 1d73764f..cc21d35a 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -155,6 +155,10 @@ class UiScalingManager(): self.config_window.TOP_BAR_SIDE__CONFIG_TITLE_LEFT_PADX = int(self.config_window.TOP_BAR_SIDE__CONFIG_TITLE_FONT_SIZE + self._calculateUiSize(16)) self.config_window.TOP_BAR_SIDE__TITLE_PADX= self._calculateUiSize(30) + # Restart Button + self.config_window.RESTART_BUTTON_LABEL_FONT_SIZE = self._calculateUiSize(12) + self.config_window.RESTART_BUTTON_PADX = (0, self._calculateUiSize(20)) + # Compact Mode self.config_window.COMPACT_MODE_PADX = (0, self._calculateUiSize(20)) self.config_window.COMPACT_MODE_LABEL_FONT_SIZE = self._calculateUiSize(12) From 9ae3bf3530a9b9901d121703f219c0387760f3b5 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 20:03:38 +0900 Subject: [PATCH 292/355] =?UTF-8?q?[bugfix]=20Config=20Window:=20=E5=86=8D?= =?UTF-8?q?=E8=B5=B7=E5=8B=95=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AEUI=20Size?= =?UTF-8?q?=E5=AF=BE=E5=BF=9C=E6=BC=8F=E3=82=8C=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../widgets/createSettingBoxTopBar/_createRestartButton.py | 4 ++-- vrct_gui/ui_managers/UiScalingManager.py | 3 +++ 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createRestartButton.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createRestartButton.py index 58e23b47..91e08761 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createRestartButton.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createRestartButton.py @@ -5,7 +5,7 @@ from ....ui_utils import bindButtonFunctionAndColor def _createRestartButton(parent_widget, config_window, settings, view_variable, column_num): parent_widget.grid_columnconfigure(0, weight=1) - config_window.restart_button_container = CTkFrame(parent_widget, corner_radius=20, fg_color=settings.ctm.RESTART_BUTTON_BG_COLOR, width=0, height=0, cursor="hand2") + config_window.restart_button_container = CTkFrame(parent_widget, corner_radius=settings.uism.RESTART_BUTTON_CORNER_RADIUS, fg_color=settings.ctm.RESTART_BUTTON_BG_COLOR, width=0, height=0, cursor="hand2") config_window.restart_button_container.grid(row=0, column=column_num, padx=settings.uism.RESTART_BUTTON_PADX, sticky="ew") @@ -18,7 +18,7 @@ def _createRestartButton(parent_widget, config_window, settings, view_variable, font=CTkFont(family=settings.FONT_FAMILY, size=settings.uism.RESTART_BUTTON_LABEL_FONT_SIZE, weight="normal"), text_color=settings.ctm.LABELS_TEXT_COLOR ) - config_window.restart_button_label.grid(row=0, column=0, padx=20, pady=10) + config_window.restart_button_label.grid(row=0, column=0, padx=settings.uism.RESTART_BUTTON_IPADX, pady=settings.uism.RESTART_BUTTON_IPADY) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index cc21d35a..73560339 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -158,6 +158,9 @@ class UiScalingManager(): # Restart Button self.config_window.RESTART_BUTTON_LABEL_FONT_SIZE = self._calculateUiSize(12) self.config_window.RESTART_BUTTON_PADX = (0, self._calculateUiSize(20)) + self.config_window.RESTART_BUTTON_CORNER_RADIUS = self._calculateUiSize(20) + self.config_window.RESTART_BUTTON_IPADX = self._calculateUiSize(20) + self.config_window.RESTART_BUTTON_IPADY = self._calculateUiSize(10) # Compact Mode self.config_window.COMPACT_MODE_PADX = (0, self._calculateUiSize(20)) From 5283741f30eb7c644d3b4430c771be3edb08e12d Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 13 Oct 2023 21:44:18 +0900 Subject: [PATCH 293/355] =?UTF-8?q?[Update]=20Config=20Window:=20=E6=96=87?= =?UTF-8?q?=E8=A8=80=E3=81=AE=E8=AA=BF=E6=95=B4=E3=80=81=E6=97=A5=E6=9C=AC?= =?UTF-8?q?=E8=AA=9E=E3=81=A8=E8=8B=B1=E8=AA=9E=E3=80=82=20=E3=82=B9?= =?UTF-8?q?=E3=83=AC=E3=83=83=E3=82=B7=E3=83=A7=E3=83=AB=E3=83=89=E3=81=AE?= =?UTF-8?q?=E8=A8=AD=E5=AE=9A=E9=A0=85=E7=9B=AE=20=E6=89=8B=E5=8B=95?= =?UTF-8?q?=E8=87=AA=E5=8B=95=E5=88=87=E3=82=8A=E6=9B=BF=E3=81=88=E6=99=82?= =?UTF-8?q?=E3=81=AB=E3=81=9D=E3=82=8C=E3=81=9E=E3=82=8C=E3=81=AE=E6=96=87?= =?UTF-8?q?=E7=AB=A0=E3=82=92=E5=85=A5=E3=82=8C=E8=BE=BC=E3=82=80=E6=96=B9?= =?UTF-8?q?=E5=BC=8F=E3=81=AB=E5=A4=89=E6=9B=B4=E3=80=82=E6=9F=94=E8=BB=9F?= =?UTF-8?q?=E6=80=A7=E3=81=AF=E3=81=82=E3=82=8B=E3=81=8C=E3=80=81=E4=BB=8A?= =?UTF-8?q?=E3=81=AE=E3=81=A8=E3=81=93=E3=82=8D=E5=88=87=E6=9B=BF=E6=99=82?= =?UTF-8?q?=E3=81=AB=E3=82=B9=E3=82=A4=E3=83=83=E3=83=81=E3=81=AE=E4=BD=8D?= =?UTF-8?q?=E7=BD=AE=E3=81=8C=E5=A4=89=E3=82=8F=E3=81=A3=E3=81=A6=E3=81=97?= =?UTF-8?q?=E3=81=BE=E3=81=86=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 20 +++++++++++--------- locales/ja.yml | 45 +++++++++++++++++++++++---------------------- view.py | 38 ++++++++++++++++++++++++-------------- 3 files changed, 58 insertions(+), 45 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 55a6f92d..0bdcc10c 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -43,7 +43,7 @@ config_window: desc: Change the main window's transparency. appearance_theme: label: Theme [Under development] - desc: Change the color theme. If you selected "System", it will adjust based on your Windows theme. + desc: Change the color theme. Currently, only the Dark theme is supported. The Light theme is under development. ui_size: label: UI Size font_family: @@ -59,15 +59,16 @@ config_window: mic_device: label: Mic Device mic_dynamic_energy_threshold: - label: Mic Energy Threshold (Automatic) - desc: Enabling this option will automatically adjust the microphone's input sensitivity. If you disable it, you can manually set the input sensitivity using the slider. Press the microphone icon to input your voice and adjust the sensitivity while monitoring the actual volume. + label_for_automatic: "Mic Energy Threshold (Current Setting: Automatic)" + desc_for_automatic: "Automatically determine microphone input sensitivity." + label_for_manual: "Mic Energy Threshold (Current Setting: Manual)" + desc_for_manual: "Manually determine the microphone input sensitivity using the slider. Press the microphone icon to input your voice and adjust the sensitivity while monitoring the volume." mic_record_timeout: label: Mic Record Timeout mic_phrase_timeout: label: Mic Phrase Timeout mic_max_phrase: label: Mic Max Phrases - desc: It will stop recording and send the recordings when the set count of phrase(s) is reached. mic_word_filter: label: Mic Word Filter desc: "It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC" @@ -75,15 +76,16 @@ config_window: speaker_device: label: Speaker Device speaker_dynamic_energy_threshold: - label: Speaker Energy Threshold (Automatic) - desc: Enabling this option will automatically adjust the speaker's input sensitivity. If you disable it, you can manually set the input sensitivity using the slider. Press the headphones icon to listen to the audio and adjust the sensitivity while monitoring the volume. + label_for_automatic: "Speaker Energy Threshold (Current Setting: Automatic)" + desc_for_automatic: "Automatically determine speaker input sensitivity." + label_for_manual: "Speaker Energy Threshold (Current Setting: Manual)" + desc_for_manual: "Manually determine the speaker input sensitivity using the slider. Press the headphones icon to listen to the audio and adjust the sensitivity while monitoring the volume." speaker_record_timeout: label: Speaker Record Timeout speaker_phrase_timeout: label: Speaker Phrase Timeout speaker_max_phrase: label: Speaker Max Phrases - desc: It will stop recording and receive the recordings when the set count of phrase(s) is reached. auto_clear_the_message_box: label: Auto Clear The Message Box @@ -95,10 +97,10 @@ config_window: desc: Automatically export the conversation messages as a text file. message_format: label: Message Format - desc: You can change the decoration of the message you want to send. + desc: "You can change the decoration of the message you want to send.\n[message] will be replaced with the message, and [translation] will be replaced with the translated message." send_message_to_vrc: label: Send Message To VRChat - desc: There is a way to use it without sending messages to VRChat. That is not covered by support, though. + desc: There is a way to use it without sending messages to VRChat, but it is not supported. Enable this feature when you intend to send a message to VRChat. osc_ip_address: label: OSC IP Address diff --git a/locales/ja.yml b/locales/ja.yml index 24697ccc..c0133468 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -1,7 +1,7 @@ main_window: translation: 翻訳 - transcription_send: マイク->チャットボックス - transcription_receive: スピーカー->ログ + transcription_send: 音声認識(マイク) + transcription_receive: 音声認識(スピーカー) foreground: 最前面表示 language_settings: 言語設定 @@ -44,15 +44,14 @@ config_window: desc: メイン画面の透明度を変更できます。 appearance_theme: label: 外観テーマ [開発中] - desc: カラーテーマを変更できます。「System」を選択した場合、Windowsのテーマに基づいて自動的に「Dark」か「Light」テーマを判断し、適用します。 + desc: カラーテーマを変更できます。現在はDarkテーマのみ対応。Lightテーマは制作中です。 ui_size: - label: UIのサイズ + label: UIサイズ font_family: label: 使用フォント ui_language: - label: UIの言語 + label: UIの言語 / UI Language - tab_translation: 翻訳 deepl_auth_key: label: DeepL 認証キー @@ -61,31 +60,33 @@ config_window: mic_device: label: マイク (デバイス) mic_dynamic_energy_threshold: - label: マイク入力感度の調整(自動) - desc: 有効にするとマイクの入力感度が自動的に調節されます。無効の場合は、スライダーを調整して入力感度を手動で決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。 + label_for_automatic: "マイク入力感度の調整 (現在の設定: 自動)" + desc_for_automatic: マイクの入力感度を自動的に調節する。 + label_for_manual: "マイク入力感度の調整 (現在の設定: 手動)" + desc_for_manual: スライダーを調整して入力感度を手動で決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。 mic_record_timeout: - label: マイク音声の区切りの無音時間 + label: 入力が終了したとみなす無音時間 mic_phrase_timeout: - label: 文字起こしする音声時間の上限 + label: 一度に文字起こしする時間の長さ mic_max_phrase: - label: 保留する単語の上限(マイク) - desc: It will stop recording and send the recordings when the set count of phrase(s) is reached. + label: 送信するまでに保持する単語数 mic_word_filter: - label: ワードフィルタ + label: ワードフィルター desc: "設定された単語を検出すると、その文章は送信されません。\n設定の例: AAA,BBB,CCC" speaker_device: label: スピーカー(デバイス) speaker_dynamic_energy_threshold: - label: スピーカー入力感度の調整(自動) - desc: 有効にするとスピーカーの入力感度が自動的に調節されます。無効の場合は、スライダーを調整して入力感度を手動で決められます。ヘッドフォンのアイコンを押すと、実際に音声を聞き取り、音量を確認しながら調節できます。 + label_for_automatic: "スピーカー入力感度の調整 (現在の設定: 自動)" + desc_for_automatic: スピーカーの入力感度を自動的に調節する。 + label_for_manual: "スピーカー入力感度の調整 (現在の設定: 手動)" + desc_for_manual: スライダーを調整して入力感度を手動で決められます。ヘッドフォンのアイコンを押すと、実実際に音声を聞き取り、音量を確認しながら調節できます。 speaker_record_timeout: - label: スピーカー音声の区切りの無音時間 + label: 入力が終了したとみなす無音時間 speaker_phrase_timeout: - label: 文字起こしする音声時間の上限 + label: 一度に文字起こしする時間の長さ speaker_max_phrase: - label: 保留する単語の上限 - desc: It will stop recording and receive the recordings when the set count of phrase(s) is reached. + label: ログとして表示するまでに保持する単語数 auto_clear_the_message_box: label: 送信後はチャットボックスを空にする @@ -94,13 +95,13 @@ config_window: desc: 文字起こし(受信)されたメッセージをXSOverlayの機能を使って通知として受け取れます。 auto_export_message_logs: label: 会話ログを自動的に保存する - desc: テキストファイルとしてログが保存されます。保存先は/logs/...(調整中) + desc: テキストファイルとしてログがlogsフォルダ内に保存されます。 message_format: label: 送信するメッセージのフォーマット - desc: VRChatで相手に実際に見えるフォーマットを変更できます。 + desc: "VRChatで相手に実際に見えるフォーマットを変更できます。\n[message]がメッセージに置換され、\n[translation]が翻訳されたメッセージに置換されます。" send_message_to_vrc: label: VRChatにメッセージを送信する - desc: VRChatにメッセージを送信せずに使う方法があります。サポート対象外ですが。 + desc: "サポート対象外ですが、VRChatにメッセージを送信せずに使う方法があります。送信したい場合、この機能を有効にする事を忘れないでください。" osc_ip_address: label: OSC IP Address diff --git a/view.py b/view.py index 8b7434b7..58b60f97 100644 --- a/view.py +++ b/view.py @@ -210,8 +210,8 @@ class View(): VAR_MIC_DEVICE=StringVar(value=config.CHOICE_MIC_DEVICE), - VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_dynamic_energy_threshold.label")), - VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.mic_dynamic_energy_threshold.desc")), + VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=""), + VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=""), CALLBACK_SET_MIC_DYNAMIC_ENERGY_THRESHOLD=None, VAR_MIC_DYNAMIC_ENERGY_THRESHOLD=BooleanVar(value=config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD), @@ -235,7 +235,7 @@ class View(): CALLBACK_FOCUS_OUT_MIC_PHRASE_TIMEOUT=self.setLatestConfigVariable_MicPhraseTimeout, VAR_LABEL_MIC_MAX_PHRASES=StringVar(value=i18n.t("config_window.mic_max_phrase.label")), - VAR_DESC_MIC_MAX_PHRASES=StringVar(value=i18n.t("config_window.mic_max_phrase.desc")), + VAR_DESC_MIC_MAX_PHRASES=None, CALLBACK_SET_MIC_MAX_PHRASES=None, VAR_MIC_MAX_PHRASES=StringVar(value=config.INPUT_MIC_MAX_PHRASES), CALLBACK_FOCUS_OUT_MIC_MAX_PHRASES=self.setLatestConfigVariable_MicMaxPhrases, @@ -254,8 +254,8 @@ class View(): VAR_SPEAKER_DEVICE=StringVar(value=config.CHOICE_SPEAKER_DEVICE), - VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_dynamic_energy_threshold.label")), - VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=i18n.t("config_window.speaker_dynamic_energy_threshold.desc")), + VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=""), + VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=""), CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=None, VAR_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=BooleanVar(value=config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD), @@ -279,7 +279,7 @@ class View(): CALLBACK_FOCUS_OUT_SPEAKER_PHRASE_TIMEOUT=self.setLatestConfigVariable_SpeakerPhraseTimeout, VAR_LABEL_SPEAKER_MAX_PHRASES=StringVar(value=i18n.t("config_window.speaker_max_phrase.label")), - VAR_DESC_SPEAKER_MAX_PHRASES=StringVar(value=i18n.t("config_window.speaker_max_phrase.desc")), + VAR_DESC_SPEAKER_MAX_PHRASES=None, CALLBACK_SET_SPEAKER_MAX_PHRASES=None, VAR_SPEAKER_MAX_PHRASES=StringVar(value=config.INPUT_SPEAKER_MAX_PHRASES), CALLBACK_FOCUS_OUT_SPEAKER_MAX_PHRASES=self.setLatestConfigVariable_SpeakerMaxPhrases, @@ -492,9 +492,13 @@ class View(): if config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD is True: self.closeMicEnergyThresholdWidget() + else: + self.openMicEnergyThresholdWidget() if config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD is True: self.closeSpeakerEnergyThresholdWidget() + else: + self.openSpeakerEnergyThresholdWidget() # Insert sample conversation for testing. @@ -727,21 +731,27 @@ class View(): def setWidgetsStatus_ConfigWindowCompactModeSwitch_Normal(): vrct_gui.config_window.setting_box_compact_mode_switch_box.configure(state="normal") - @staticmethod - def openMicEnergyThresholdWidget(): + def openMicEnergyThresholdWidget(self): + self.view_variable.VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD.set(i18n.t("config_window.mic_dynamic_energy_threshold.label_for_manual")) + self.view_variable.VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD.set(i18n.t("config_window.mic_dynamic_energy_threshold.desc_for_manual")) vrct_gui.config_window.sb__mic_dynamic_energy_threshold.grid(pady=0) vrct_gui.config_window.sb__mic_energy_threshold.grid() - @staticmethod - def closeMicEnergyThresholdWidget(): + + def closeMicEnergyThresholdWidget(self): + self.view_variable.VAR_LABEL_MIC_DYNAMIC_ENERGY_THRESHOLD.set(i18n.t("config_window.mic_dynamic_energy_threshold.label_for_automatic")) + self.view_variable.VAR_DESC_MIC_DYNAMIC_ENERGY_THRESHOLD.set(i18n.t("config_window.mic_dynamic_energy_threshold.desc_for_automatic")) vrct_gui.config_window.sb__mic_dynamic_energy_threshold.grid(pady=(0,1)) vrct_gui.config_window.sb__mic_energy_threshold.grid_remove() - @staticmethod - def openSpeakerEnergyThresholdWidget(): + def openSpeakerEnergyThresholdWidget(self): + self.view_variable.VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD.set(i18n.t("config_window.speaker_dynamic_energy_threshold.label_for_manual")) + self.view_variable.VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD.set(i18n.t("config_window.speaker_dynamic_energy_threshold.desc_for_manual")) vrct_gui.config_window.sb__speaker_dynamic_energy_threshold.grid(pady=0) vrct_gui.config_window.sb__speaker_energy_threshold.grid() - @staticmethod - def closeSpeakerEnergyThresholdWidget(): + + def closeSpeakerEnergyThresholdWidget(self): + self.view_variable.VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD.set(i18n.t("config_window.speaker_dynamic_energy_threshold.label_for_automatic")) + self.view_variable.VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD.set(i18n.t("config_window.speaker_dynamic_energy_threshold.desc_for_automatic")) vrct_gui.config_window.sb__speaker_dynamic_energy_threshold.grid(pady=(0,1)) vrct_gui.config_window.sb__speaker_energy_threshold.grid_remove() From f9a1ec5febfd3cb38d47cdc22188c20c3cbc48ef Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 13 Oct 2023 21:59:38 +0900 Subject: [PATCH 294/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20batc?= =?UTF-8?q?h=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=81=AE=E3=82=B3?= =?UTF-8?q?=E3=83=B3=E3=82=BD=E3=83=BC=E3=83=AB=E3=82=92=E8=A1=A8=E7=A4=BA?= =?UTF-8?q?=E3=81=97=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- batch/restart.bat | 2 +- batch/update.bat | 8 +++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/batch/restart.bat b/batch/restart.bat index fc305a6e..b86be544 100644 --- a/batch/restart.bat +++ b/batch/restart.bat @@ -1,4 +1,4 @@ -@echo off +@if not "%~0"=="%~dp0.\%~nx0" start /min cmd /c,"%~dp0.\%~nx0" %* & goto :eof taskkill /im %1 /F START "" %1 \ No newline at end of file diff --git a/batch/update.bat b/batch/update.bat index 099d8689..2a7fc7b2 100644 --- a/batch/update.bat +++ b/batch/update.bat @@ -1,9 +1,11 @@ +@if not "%~0"=="%~dp0.\%~nx0" start /min cmd /c,"%~dp0.\%~nx0" %* & goto :eof + taskkill /im %1 /F -timeout 2 +ping -n 2 127.0.0.1 > nul del /f %1 -timeout 2 +ping -n 2 127.0.0.1 > nul rename %2 %1 -timeout 2 +ping -n 2 127.0.0.1 > nul if %3 == True ( START "" %1 ) \ No newline at end of file From db328692ef69adc6ff8f117ef34cc3f60b8abb3e Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 13 Oct 2023 22:49:42 +0900 Subject: [PATCH 295/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Config=20:=20ver?= =?UTF-8?q?sion=E3=82=92=202.0.0=20alpha=203=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 4ccad224..6e1da88e 100644 --- a/config.py +++ b/config.py @@ -522,7 +522,7 @@ class Config: def init_config(self): # Read Only - self._VERSION = "2.0.0" + self._VERSION = "2.0.0 alpha 3" self._PATH_CONFIG = os_path.join(os_path.dirname(sys.argv[0]), "config.json") self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" self._BOOTH_URL = "https://misyaguziya.booth.pm/" From 6968f81366e8d6334b3a4dbd9667e9a831d6cc19 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 14 Oct 2023 08:10:57 +0900 Subject: [PATCH 296/355] =?UTF-8?q?[Refactor]=20=E5=A4=89=E6=95=B0?= =?UTF-8?q?=E5=90=8D=E5=A4=89=E6=9B=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 3 +-- locales/ja.yml | 3 +-- view.py | 20 ++++++++++------ vrct_gui/_CreateErrorWindow.py | 22 ++++++++--------- ...teModalWindow.py => _CreateWindowCover.py} | 14 +++++------ vrct_gui/ui_managers/ColorThemeManager.py | 5 ++-- vrct_gui/ui_managers/UiScalingManager.py | 5 ++-- vrct_gui/vrct_gui.py | 24 +++++++++---------- 8 files changed, 51 insertions(+), 45 deletions(-) rename vrct_gui/{_CreateModalWindow.py => _CreateWindowCover.py} (79%) diff --git a/locales/en.yml b/locales/en.yml index 0bdcc10c..3fef05fe 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -16,8 +16,7 @@ main_window: update_available: New version is here! - modal_message: - opened_config_window: The functionality is temporarily disabled until the settings window is closed. + cover_message: The functionality is temporarily disabled until the settings window is closed. selectable_language_window: title_your_language: Select Your Language diff --git a/locales/ja.yml b/locales/ja.yml index c0133468..f6bb107e 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -16,8 +16,7 @@ main_window: update_available: 新しいバージョンが出ました! - modal_message: - opened_config_window: 設定画面が閉じられるまで、一時的に機能を停止しています。 + cover_message: 設定画面が閉じられるまで、一時的に機能を停止しています。 selectable_language_window: diff --git a/view.py b/view.py index 58b60f97..7ec6110c 100644 --- a/view.py +++ b/view.py @@ -54,9 +54,15 @@ class View(): **common_args ) - self.settings.modal_window = SimpleNamespace( - ctm=all_ctm.modal_window, - uism=all_uism.modal_window, + self.settings.main_window_cover = SimpleNamespace( + ctm=all_ctm.main_window_cover, + uism=all_uism.main_window_cover, + **common_args + ) + + self.settings.error_message_window = SimpleNamespace( + ctm=all_ctm.error_message_window, + uism=all_uism.error_message_window, **common_args ) @@ -123,8 +129,8 @@ class View(): VAR_UPDATE_AVAILABLE=StringVar(value=i18n.t("main_window.update_available")), - # Modal Window For Main Window - VAR_LABEL_MODAL_MESSAGE_FOR__MAIN_WINDOW=StringVar(value=i18n.t("main_window.modal_message.opened_config_window")), + # Main Window Cover + VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE=StringVar(value=i18n.t("main_window.cover_message")), # Selectable Language Window VAR_TITLE_LABEL_SELECTABLE_LANGUAGE=StringVar(value=""), @@ -558,12 +564,12 @@ class View(): @staticmethod def _openTheCoverOfMainWindow(): - vrct_gui.modal_window.show() + vrct_gui.main_window_cover.show() vrct_gui.config_window.lift() @staticmethod def _closeTheCoverOfMainWindow(): - vrct_gui.modal_window.withdraw() + vrct_gui.main_window_cover.withdraw() def enableMainWindowSidebarCompactMode(self): self.view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE = True diff --git a/vrct_gui/_CreateErrorWindow.py b/vrct_gui/_CreateErrorWindow.py index f92e47f6..5a2cbd7d 100644 --- a/vrct_gui/_CreateErrorWindow.py +++ b/vrct_gui/_CreateErrorWindow.py @@ -58,12 +58,12 @@ class _CreateErrorWindow(CTkToplevel): self.grid_rowconfigure(0,weight=1) self.grid_columnconfigure(0,weight=1) - self.modal_container = CTkFrame(self, corner_radius=0, fg_color=self.message_bg_color, width=0, height=0) - self.modal_container.grid(row=0, column=0, sticky="nsew") + self.error_message_container = CTkFrame(self, corner_radius=0, fg_color=self.message_bg_color, width=0, height=0) + self.error_message_container.grid(row=0, column=0, sticky="nsew") - self.modal_container_label_wrapper = CTkLabel( - self.modal_container, + self.error_message_container_label_wrapper = CTkLabel( + self.error_message_container, # text=message, textvariable=self._view_variable.VAR_ERROR_MESSAGE, height=0, @@ -73,7 +73,7 @@ class _CreateErrorWindow(CTkToplevel): justify="left", text_color=self.message_text_color, ) - self.modal_container_label_wrapper.grid(row=0, column=0, padx=self.message_ipadx, pady=self.message_ipady, sticky="nsew") + self.error_message_container_label_wrapper.grid(row=0, column=0, padx=self.message_ipadx, pady=self.message_ipady, sticky="nsew") @@ -90,20 +90,20 @@ class _CreateErrorWindow(CTkToplevel): self.hide = False - label_width = getLatestWidth(self.modal_container_label_wrapper) - label_height = getLatestHeight(self.modal_container_label_wrapper) + label_width = getLatestWidth(self.error_message_container_label_wrapper) + label_height = getLatestHeight(self.error_message_container_label_wrapper) # for fixing 1px bug if isEven(label_width) is False: - self.modal_container_label_wrapper.grid(padx=(self.message_ipadx[0], self.message_ipadx[1]-1)) + self.error_message_container_label_wrapper.grid(padx=(self.message_ipadx[0], self.message_ipadx[1]-1)) else: - self.modal_container_label_wrapper.grid(padx=self.message_ipadx) + self.error_message_container_label_wrapper.grid(padx=self.message_ipadx) # for fixing 1px bug if isEven(label_height) is False: - self.modal_container_label_wrapper.grid(pady=(self.message_ipady[0], self.message_ipady[1]-1)) + self.error_message_container_label_wrapper.grid(pady=(self.message_ipady[0], self.message_ipady[1]-1)) else: - self.modal_container_label_wrapper.grid(pady=self.message_ipady) + self.error_message_container_label_wrapper.grid(pady=self.message_ipady) for i in range(0,101,20): diff --git a/vrct_gui/_CreateModalWindow.py b/vrct_gui/_CreateWindowCover.py similarity index 79% rename from vrct_gui/_CreateModalWindow.py rename to vrct_gui/_CreateWindowCover.py index f7a656b1..bab5382e 100644 --- a/vrct_gui/_CreateModalWindow.py +++ b/vrct_gui/_CreateWindowCover.py @@ -2,7 +2,7 @@ from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont from .ui_utils import fadeInAnimation -class _CreateModalWindow(CTkToplevel): +class _CreateWindowCover(CTkToplevel): def __init__(self, attach_window, settings, view_variable): super().__init__() self.withdraw() @@ -35,20 +35,20 @@ class _CreateModalWindow(CTkToplevel): self.grid_rowconfigure(0,weight=1) self.grid_columnconfigure(0,weight=1) - self.modal_container = CTkFrame(self, corner_radius=0, fg_color="black", width=0, height=0) - self.modal_container.grid(row=0, column=0, sticky="nsew") + self.cover_container = CTkFrame(self, corner_radius=0, fg_color="black", width=0, height=0) + self.cover_container.grid(row=0, column=0, sticky="nsew") - self.modal_container_label_wrapper = CTkLabel( - self.modal_container, - textvariable=self._view_variable.VAR_LABEL_MODAL_MESSAGE_FOR__MAIN_WINDOW, + self.cover_container_label_wrapper = CTkLabel( + self.cover_container, + textvariable=self._view_variable.VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE, height=0, corner_radius=0, font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.TEXT_FONT_SIZE, weight="normal"), anchor="w", text_color=self.settings.ctm.TEXT_COLOR, ) - self.modal_container_label_wrapper.place(relx=0.5, rely=0.5, anchor="center") + self.cover_container_label_wrapper.place(relx=0.5, rely=0.5, anchor="center") def show(self): diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 5af8f120..bdded267 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -5,7 +5,8 @@ class ColorThemeManager(): self.main = SimpleNamespace() self.config_window = SimpleNamespace() self.selectable_language_window = SimpleNamespace() - self.modal_window = SimpleNamespace() + self.main_window_cover = SimpleNamespace() + self.error_message_window = SimpleNamespace() # old one. But leave it here for now. # self.PRIMARY_100_COLOR = "#c4eac1" @@ -212,7 +213,7 @@ class ColorThemeManager(): # Modal Window (Main Window) - self.modal_window.TEXT_COLOR = self.LIGHT_100_COLOR + self.main_window_cover.TEXT_COLOR = self.LIGHT_100_COLOR # Common diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 73560339..6e359211 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -8,7 +8,8 @@ class UiScalingManager(): self.main = SimpleNamespace() self.config_window = SimpleNamespace() self.selectable_language_window = SimpleNamespace() - self.modal_window = SimpleNamespace() + self.main_window_cover = SimpleNamespace() + self.error_message_window = SimpleNamespace() self._calculatedUiSizes() @@ -138,7 +139,7 @@ class UiScalingManager(): self.selectable_language_window.VALUES_TEXT_IPADY = self._calculateUiSize(8) - self.modal_window.TEXT_FONT_SIZE = self._calculateUiSize(20) + self.main_window_cover.TEXT_FONT_SIZE = self._calculateUiSize(20) # Config Window self.config_window.DEFAULT_WIDTH = self._calculateUiSize(1080) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 38335495..6971f7bb 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -2,7 +2,7 @@ from customtkinter import CTk, CTkImage from ._CreateSelectableLanguagesWindow import _CreateSelectableLanguagesWindow -from ._CreateModalWindow import _CreateModalWindow +from ._CreateWindowCover import _CreateWindowCover from ._CreateErrorWindow import _CreateErrorWindow from ._CreateDropdownMenuWindow import _CreateDropdownMenuWindow from ._changeMainWindowWidgetsStatus import _changeMainWindowWidgetsStatus @@ -36,9 +36,9 @@ class VRCT_GUI(CTk): self.window_state = self.new_window_state if self.window_state == "iconic": - self.modal_window.withdraw() + self.main_window_cover.withdraw() elif self.window_state == "normal": - self.modal_window.show() + self.main_window_cover.show() @@ -93,14 +93,14 @@ class VRCT_GUI(CTk): view_variable=self._view_variable ) - self.modal_window = _CreateModalWindow( + self.main_window_cover = _CreateWindowCover( attach_window=self, - settings=self.settings.modal_window, + settings=self.settings.main_window_cover, view_variable=self._view_variable ) self.error_message_window = _CreateErrorWindow( - settings=self.settings.modal_window, + settings=self.settings.error_message_window, view_variable=self._view_variable, wrapper_widget=self.config_window.main_bg_container, @@ -130,14 +130,14 @@ class VRCT_GUI(CTk): callFunctionIfCallable(self._view_variable.CALLBACK_OPEN_CONFIG_WINDOW) self._adjustToMainWindowGeometry() - self.modal_window.show() + self.main_window_cover.show() self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID = self.bind("", self._adjustToMainWindowGeometry, "+") self.BIND_UNMAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID = self.bind("", self.detectMainWindowState, "+") self.BIND_MAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID = self.bind("", self.detectMainWindowState, "+") - self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID = self.modal_window.bind("", lambda _e: self.config_window.lift(), "+") + self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID = self.main_window_cover.bind("", lambda _e: self.config_window.lift(), "+") self.config_window.attributes("-alpha", 0) self.config_window.deiconify() @@ -153,11 +153,11 @@ class VRCT_GUI(CTk): self.config_window.withdraw() - self.modal_window.withdraw() + self.main_window_cover.withdraw() self.unbind("", self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID) self.unbind("", self.BIND_UNMAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID) self.unbind("", self.BIND_MAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID) - self.modal_window.unbind("", self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID) + self.main_window_cover.unbind("", self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID) self.adjusted_event=None @@ -263,9 +263,9 @@ class VRCT_GUI(CTk): y_pos = self.winfo_rooty() width_new = makeEven(self.winfo_width()) height_new = makeEven(self.winfo_height()) - self.modal_window.geometry("{}x{}+{}+{}".format(width_new, height_new, x_pos, y_pos)) + self.main_window_cover.geometry("{}x{}+{}+{}".format(width_new, height_new, x_pos, y_pos)) - self.modal_window.lift() + self.main_window_cover.lift() if self.adjusted_event == str(e): self.after(150, lambda: self.config_window.lift()) elif self.adjusted_event is None: From 31ea7ebf8ababee3130d571a0b751e7c6e2560d9 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 14 Oct 2023 08:33:54 +0900 Subject: [PATCH 297/355] =?UTF-8?q?[Refactor]=20view=E4=BB=A5=E4=B8=8B?= =?UTF-8?q?=E3=81=AE=E5=BC=95=E6=95=B0=E7=B3=BB=E3=81=AE=E3=82=A4=E3=83=B3?= =?UTF-8?q?=E3=83=87=E3=83=B3=E3=83=88=E8=AA=BF=E6=95=B4=20=E2=80=BB?= =?UTF-8?q?=E3=81=A0=E3=81=84=E3=81=B6=E6=84=9F=E8=A6=9A=E7=9A=84=E3=81=AA?= =?UTF-8?q?=E7=8B=AC=E8=87=AA=E3=83=AB=E3=83=BC=E3=83=AB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateDropdownMenuWindow.py | 38 +++++++------- vrct_gui/_CreateErrorWindow.py | 18 +++---- vrct_gui/_changeMainWindowWidgetsStatus.py | 11 +++- vrct_gui/_printToTextbox.py | 10 +++- .../_SettingBoxGenerator.py | 52 ++++++++++++++++--- 5 files changed, 94 insertions(+), 35 deletions(-) diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index a4f880b0..340beb99 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -9,26 +9,28 @@ from functools import partial from utils import isEven, makeEven class _CreateDropdownMenuWindow(CTkToplevel): - def __init__(self, - settings, - view_variable, + def __init__( + self, + settings, + view_variable, - window_additional_y_pos, - window_border_width, - scrollbar_ipadx, - scrollbar_width, - value_ipadx, - value_ipady, - value_pady, - value_font_size, + window_additional_y_pos, + window_border_width, + scrollbar_ipadx, + scrollbar_width, + value_ipadx, + value_ipady, + value_pady, + value_font_size, + + window_bg_color, + window_border_color, + values_bg_color, + values_hovered_bg_color, + values_clicked_bg_color, + values_text_color, + ): - window_bg_color, - window_border_color, - values_bg_color, - values_hovered_bg_color, - values_clicked_bg_color, - values_text_color, - ): super().__init__() self.withdraw() self.hide = True diff --git a/vrct_gui/_CreateErrorWindow.py b/vrct_gui/_CreateErrorWindow.py index 5a2cbd7d..f6091453 100644 --- a/vrct_gui/_CreateErrorWindow.py +++ b/vrct_gui/_CreateErrorWindow.py @@ -7,17 +7,17 @@ from utils import isEven class _CreateErrorWindow(CTkToplevel): def __init__( - self, - settings, - view_variable, - wrapper_widget, + self, + settings, + view_variable, + wrapper_widget, - message_ipadx, - message_ipady, - message_font_size, + message_ipadx, + message_ipady, + message_font_size, - message_bg_color, - message_text_color, + message_bg_color, + message_text_color, ): super().__init__() diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index e785d810..0f2f7af2 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -9,7 +9,16 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta - def update_switch_status(widget_frame, widget_label, widget_switch_box, widget_selected_mark, widget_compact_mode_icon, icon_name, disabled_icon_name): + def update_switch_status( + widget_frame, + widget_label, + widget_switch_box, + widget_selected_mark, + widget_compact_mode_icon, + icon_name, + disabled_icon_name, + ): + if status == "disabled": widget_frame.configure(cursor="") widget_label.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) diff --git a/vrct_gui/_printToTextbox.py b/vrct_gui/_printToTextbox.py index e99ab1c4..943b047d 100644 --- a/vrct_gui/_printToTextbox.py +++ b/vrct_gui/_printToTextbox.py @@ -1,7 +1,15 @@ from datetime import datetime from customtkinter import CTkFont -def _printToTextbox(vrct_gui, settings, target_type, original_message=None, translated_message=None, tags=None, disable_print_to_textbox_all:bool=False): +def _printToTextbox(vrct_gui, + settings, + target_type, + original_message=None, + translated_message=None, + tags=None, + disable_print_to_textbox_all:bool=False, + ): + now_raw_data = datetime.now() # now = now_raw_data.strftime("%H:%M:%S") now_hm = now_raw_data.strftime("%H:%M") diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index db613ad7..e5f7ecd7 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -91,7 +91,16 @@ class _SettingBoxGenerator(): - def createSettingBoxDropdownMenu(self, for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, dropdown_menu_width=None, variable=None, dropdown_menu_values=None): + def createSettingBoxDropdownMenu( + self, + for_var_label_text, for_var_desc_text, + optionmenu_attr_name, + command, + dropdown_menu_width=None, + dropdown_menu_values=None, + variable=None, + ): + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(optionmenu_attr_name, for_var_label_text, for_var_desc_text) def adjustedCommand(value): @@ -143,7 +152,13 @@ class _SettingBoxGenerator(): - def createSettingBoxSwitch(self, for_var_label_text, for_var_desc_text, switch_attr_name, variable, command): + def createSettingBoxSwitch(self, + for_var_label_text, for_var_desc_text, + switch_attr_name, + variable, + command, + ): + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(switch_attr_name, for_var_label_text, for_var_desc_text) switch_widget = CTkSwitch( @@ -171,7 +186,13 @@ class _SettingBoxGenerator(): - def createSettingBoxCheckbox(self, for_var_label_text, for_var_desc_text, checkbox_attr_name, variable, command): + def createSettingBoxCheckbox(self, + for_var_label_text, for_var_desc_text, + checkbox_attr_name, + command, + variable, + ): + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(checkbox_attr_name, for_var_label_text, for_var_desc_text) checkbox_widget = CTkCheckBox( @@ -205,7 +226,19 @@ class _SettingBoxGenerator(): - def createSettingBoxSlider(self, for_var_label_text, for_var_desc_text, slider_attr_name, slider_range, command, variable, slider_number_of_steps: Union[int, None] = None, slider_bind__ButtonPress=None, slider_bind__ButtonRelease=None): + def createSettingBoxSlider( + self, + for_var_label_text, for_var_desc_text, + slider_attr_name, + slider_range, + command, + variable, + slider_number_of_steps: Union[int, + None] = None, + slider_bind__ButtonPress=None, + slider_bind__ButtonRelease=None + ): + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(slider_attr_name, for_var_label_text, for_var_desc_text) @@ -258,7 +291,6 @@ class _SettingBoxGenerator(): slider_number_of_steps: Union[int, None] = None, ): - (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(progressbar_x_slider_attr_name) def adjusted_command__for_entry_bind__Any_KeyRelease(e): @@ -352,7 +384,15 @@ class _SettingBoxGenerator(): - def createSettingBoxEntry(self, for_var_label_text, for_var_desc_text, entry_attr_name, entry_width, entry_bind__Any_KeyRelease, entry_textvariable, entry_bind__FocusOut=None): + def createSettingBoxEntry(self, + for_var_label_text, for_var_desc_text, + entry_attr_name, + entry_width, + entry_textvariable, + entry_bind__Any_KeyRelease, + entry_bind__FocusOut=None, + ): + (setting_box_frame, setting_box_item_frame) = self._createSettingBoxFrame(entry_attr_name, for_var_label_text, for_var_desc_text) def adjusted_command__for_entry_bind__Any_KeyRelease(e): From fb67930ed140852c7a3d3dda8c6982e4faa8fda0 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 14 Oct 2023 08:58:01 +0900 Subject: [PATCH 298/355] [Refactor] Remove the code that is no longe in use. --- view.py | 1 - vrct_gui/_CreateDropdownMenuWindow.py | 3 +-- vrct_gui/_CreateSelectableLanguagesWindow.py | 1 - vrct_gui/_CreateWindowCover.py | 1 - vrct_gui/_changeMainWindowWidgetsStatus.py | 6 ++---- vrct_gui/config_window/ConfigWindow.py | 2 +- .../config_window/widgets/createConfigWindowTitle.py | 1 - .../createSettingBoxTopBar/createSettingBoxTopBar.py | 4 ++-- .../_createSettingBoxContainer.py | 2 +- .../createSideMenuAndSettingsBoxContainers.py | 4 ++-- .../widgets/_create_sidebar/createSidebarFeatures.py | 4 ---- .../_create_sidebar/createSidebarLanguagesSettings.py | 2 +- vrct_gui/main_window/widgets/create_sidebar.py | 10 ---------- vrct_gui/main_window/widgets/create_textbox.py | 4 ++-- vrct_gui/ui_utils/__init__.py | 3 +-- vrct_gui/ui_utils/ui_utils.py | 2 +- vrct_gui/vrct_gui.py | 4 ++-- 17 files changed, 16 insertions(+), 38 deletions(-) diff --git a/view.py b/view.py index 7ec6110c..6af00b34 100644 --- a/view.py +++ b/view.py @@ -1,5 +1,4 @@ from os import path as os_path -from typing import Union from types import SimpleNamespace from tkinter import font as tk_font import webbrowser diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index 340beb99..b4a64775 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -3,7 +3,7 @@ from types import SimpleNamespace from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont, CTkScrollableFrame from time import sleep -from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor, getLatestWidth, getLatestHeight, applyUiScalingAndFixTheBugScrollBar +from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor, getLatestHeight, applyUiScalingAndFixTheBugScrollBar from functools import partial from utils import isEven, makeEven @@ -239,7 +239,6 @@ class _CreateDropdownMenuWindow(CTkToplevel): anchor="w", text_color=self.values_text_color, ) - # setattr(self, f"l", label_widget) label_widget.grid(row=1, column=0, padx=self.value_ipadx, pady=self.value_ipady, sticky="w") diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index 0ba102b4..ca87c547 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -159,7 +159,6 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): anchor="w", text_color=self.settings.ctm.BASIC_TEXT_COLOR, ) - # setattr(self, f"l", label_widget) label_widget.grid(row=1, column=0, padx=self.settings.uism.VALUES_TEXT_IPADX, pady=self.settings.uism.VALUES_TEXT_IPADY) diff --git a/vrct_gui/_CreateWindowCover.py b/vrct_gui/_CreateWindowCover.py index bab5382e..f86929c1 100644 --- a/vrct_gui/_CreateWindowCover.py +++ b/vrct_gui/_CreateWindowCover.py @@ -11,7 +11,6 @@ class _CreateWindowCover(CTkToplevel): self.title("") self.overrideredirect(True) - # self.wm_attributes("-alpha", 0.5) self.wm_attributes("-toolwindow", True) self.attach_window = attach_window diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index 0f2f7af2..dc303094 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -93,8 +93,7 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta vrct_gui.sls__title_text_target_language.configure(text_color=settings.ctm.SF__TEXT_DISABLED_COLOR) if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE) - # vrct_gui.sls__optionmenu_your_language.configure(state="disabled") - # vrct_gui.sls__optionmenu_target_language.configure(state="disabled") + elif status == "normal": vrct_gui.sls__container_title.configure(text_color=settings.ctm.LABELS_TEXT_COLOR) @@ -103,8 +102,7 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta if view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is False: vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) vrct_gui.current_active_preset_tab.children["!ctklabel"].configure(text_color=settings.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR) - # vrct_gui.sls__optionmenu_your_language.configure(state="normal") - # vrct_gui.sls__optionmenu_target_language.configure(state="normal") + case "config_button": diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index ee88da4b..9e57aa3c 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -3,7 +3,7 @@ from .widgets import createConfigWindowTitle, createSideMenuAndSettingsBoxContai from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont -from ..ui_utils import getImagePath, getLatestWidth, getLatestHeight +from ..ui_utils import getImagePath, getLatestWidth from utils import isEven class ConfigWindow(CTkToplevel): diff --git a/vrct_gui/config_window/widgets/createConfigWindowTitle.py b/vrct_gui/config_window/widgets/createConfigWindowTitle.py index 4e949667..4db6b4f4 100644 --- a/vrct_gui/config_window/widgets/createConfigWindowTitle.py +++ b/vrct_gui/config_window/widgets/createConfigWindowTitle.py @@ -18,7 +18,6 @@ def createConfigWindowTitle(config_window, settings, view_variable): config_window.side_menu_config_window_title_logo_wrapper.grid_rowconfigure(0,weight=1) config_window.side_menu_config_window_title = CTkLabel( config_window.side_menu_config_window_title_logo_frame, - # text="Settings", textvariable=view_variable.VAR_CONFIG_WINDOW_TITLE, height=0, anchor="w", diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py index 5dad5207..12a0403c 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/createSettingBoxTopBar.py @@ -1,10 +1,10 @@ -from customtkinter import CTkFont, CTkFrame, CTkLabel +from customtkinter import CTkFrame from ._createSettingBoxTitle import _createSettingBoxTitle from ._createRestartButton import _createRestartButton from ._createSettingBoxCompactModeButton import _createSettingBoxCompactModeButton -from ....ui_utils import getLatestHeight, getLatestWidth +from ....ui_utils import getLatestHeight from utils import isEven def createSettingBoxTopBar(config_window, settings, view_variable): diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py index 8efbbdf5..97859e3a 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/_createSettingBoxContainer.py @@ -28,7 +28,7 @@ def _createSettingBoxContainer(config_window, settings, view_variable, setting_b setting_box_row=0 - for i, setting_box_setting in enumerate(setting_box_container_settings["setting_boxes"]): + for setting_box_setting in setting_box_container_settings["setting_boxes"]: # Top-Padding that can be container the section title setting_box_top_padding = CTkFrame(setting_box_container_widget, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=settings.uism.SB__TOP_PADY) setting_box_top_padding.grid(row=setting_box_row, column=0, sticky="ew", padx=0, pady=0) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index 2fac3c15..f9694a1a 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -1,6 +1,6 @@ from customtkinter import CTkFrame, CTkScrollableFrame -from ....ui_utils import _setDefaultActiveTab, applyUiScalingAndFixTheBugScrollBar +from ....ui_utils import setDefaultActiveTab, applyUiScalingAndFixTheBugScrollBar from ._addConfigSideMenuItem import _addConfigSideMenuItem from ._createSettingBoxContainer import _createSettingBoxContainer @@ -152,7 +152,7 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl # Set default active side menu tab view_variable.VAR_CURRENT_ACTIVE_CONFIG_TITLE.set(sm_and_sbc_setting["textvariable"].get()) config_window.current_active_side_menu_tab = getattr(config_window, sm_and_sbc_setting["side_menu_tab_attr_name"]) - _setDefaultActiveTab( + setDefaultActiveTab( active_tab_widget=config_window.current_active_side_menu_tab, active_bg_color=settings.ctm.SIDE_MENU_LABELS_BG_COLOR, active_text_color=settings.ctm.SIDE_MENU_LABELS_SELECTED_TEXT_COLOR diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py index 1785f6bb..0f8314f0 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py @@ -214,10 +214,6 @@ def createSidebarFeatures(settings, main_window, view_variable): bg_color=settings.ctm.SF__BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR, ) - # # if sfs["is_checked"] is True: - # # target_attr.select() - # # else: - # # target_attr.deselect() setattr(main_window, switch_box_attr_name, switch_box_widget) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py index 98fda4ae..346afae5 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarLanguagesSettings.py @@ -1,6 +1,6 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkImage -from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor, createOptionMenuBox, getLatestWidth +from ....ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, switchActiveTabAndPassiveTab, switchTabsColor, createOptionMenuBox from utils import callFunctionIfCallable diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 4605bef4..763472a0 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -55,16 +55,6 @@ def createSidebar(settings, main_window, view_variable): main_window.sidebar_config_button.grid(row=0, column=0, padx=0, pady=settings.uism.SIDEBAR_CONFIG_BUTTON_IPADY) - # main_window.sidebar_config_button_update_badge = CTkFrame( - # main_window.sidebar_config_button, - # corner_radius=3, - # fg_color="#ca5361", - # width=6, - # height=6, - # ) - # main_window.sidebar_config_button_update_badge.place(relx=0.9, rely=0.1, anchor="center") - - bindButtonFunctionAndColor( target_widgets=[main_window.sidebar_config_button_wrapper, main_window.sidebar_config_button], enter_color=settings.ctm.CONFIG_BUTTON_HOVERED_BG_COLOR, diff --git a/vrct_gui/main_window/widgets/create_textbox.py b/vrct_gui/main_window/widgets/create_textbox.py index ce1f2e7a..99e977f4 100644 --- a/vrct_gui/main_window/widgets/create_textbox.py +++ b/vrct_gui/main_window/widgets/create_textbox.py @@ -1,6 +1,6 @@ from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkTextbox -from ...ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, _setDefaultActiveTab, switchActiveTabAndPassiveTab, switchTabsColor +from ...ui_utils import bindEnterAndLeaveColor, bindButtonPressColor, bindButtonReleaseFunction, setDefaultActiveTab, switchActiveTabAndPassiveTab, switchTabsColor def createTextbox(settings, main_window, view_variable): @@ -152,7 +152,7 @@ def createTextbox(settings, main_window, view_variable): # Set default active textbox tab main_window.current_active_textbox_tab = getattr(main_window, "textbox_tab_all") - _setDefaultActiveTab( + setDefaultActiveTab( active_tab_widget=main_window.current_active_textbox_tab, active_bg_color=settings.ctm.TEXTBOX_TAB_BG_ACTIVE_COLOR, active_text_color=settings.ctm.TEXTBOX_TAB_TEXT_ACTIVE_COLOR diff --git a/vrct_gui/ui_utils/__init__.py b/vrct_gui/ui_utils/__init__.py index e501aebe..bc2a1337 100644 --- a/vrct_gui/ui_utils/__init__.py +++ b/vrct_gui/ui_utils/__init__.py @@ -1,2 +1 @@ -from .ui_utils import * -from .ui_utils import _setDefaultActiveTab \ No newline at end of file +from .ui_utils import * \ No newline at end of file diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index 8f562d32..03c963a8 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -88,7 +88,7 @@ def unbindEventFromActiveTabWidget(active_tab_widget): active_tab_widget.unbind(event_name) active_tab_widget.children["!ctklabel"].unbind(event_name) -def _setDefaultActiveTab(active_tab_widget, active_bg_color, active_text_color): +def setDefaultActiveTab(active_tab_widget, active_bg_color, active_text_color): active_tab_widget.configure(fg_color=active_bg_color, cursor="") active_tab_widget.children["!ctklabel"].configure(fg_color=active_bg_color, text_color=active_text_color) unbindEventFromActiveTabWidget(active_tab_widget) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 6971f7bb..fae93026 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -11,7 +11,7 @@ from ._printToTextbox import _printToTextbox from .main_window import createMainWindowWidgets from .config_window import ConfigWindow -from .ui_utils import _setDefaultActiveTab, getLatestHeight, setGeometryToCenterOfScreen, fadeInAnimation +from .ui_utils import setDefaultActiveTab, setGeometryToCenterOfScreen, fadeInAnimation from utils import callFunctionIfCallable, makeEven @@ -238,7 +238,7 @@ class VRCT_GUI(CTk): def _setDefaultActiveLanguagePresetTab(self, tab_no:str): self.current_active_preset_tab = getattr(self, f"sls__presets_button_{tab_no}") - _setDefaultActiveTab( + setDefaultActiveTab( active_tab_widget=self.current_active_preset_tab, active_bg_color=self.settings.main.ctm.SLS__PRESETS_TAB_BG_ACTIVE_COLOR, active_text_color=self.settings.main.ctm.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR From ff675330c6f453feb5c790b60fe1d72304fd06cb Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:57:01 +0900 Subject: [PATCH 299/355] =?UTF-8?q?[Update]=20Main=20Window:=20Textbox=20l?= =?UTF-8?q?og=E5=86=85=E3=81=AE=E3=82=B7=E3=82=B9=E3=83=86=E3=83=A0?= =?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E8=8B=B1?= =?UTF-8?q?=E8=AA=9E=E3=81=A8=E6=97=A5=E6=9C=AC=E8=AA=9E=E3=81=AB=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C=E3=80=82=E6=96=87=E8=A8=80=E8=AA=BF=E6=95=B4=E3=80=82?= =?UTF-8?q?=20[Refactor]=20Commented=20out=20the=20code=20that=20is=20no?= =?UTF-8?q?=20longer=20in=20use=20in=20view.py=20(osc=20error=20message)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 22 ++++++++++++++++++++++ locales/ja.yml | 21 +++++++++++++++++++++ view.py | 42 ++++++++++++++++++++---------------------- 3 files changed, 63 insertions(+), 22 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 3fef05fe..b1fc34da 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -14,15 +14,37 @@ main_window: textbox_tab_received: Received textbox_tab_system: System + textbox_system_message: + enabled_translation: Translation feature is turned on. + disabled_translation: Translation feature is turned off. + enabled_voice2chatbox: Transcription from the microphone has started. + disabled_voice2chatbox: Transcription from the microphone has been stopped. + enabled_speaker2log: Transcription from the speaker has started. + disabled_speaker2log: Transcription from the speaker has been stopped. + enabled_foreground: The screen is fixed in the foreground. + disabled_foreground: The foreground fixation has been released. + + auth_key_success: Auth key update completed. + auth_key_error: Auth Key is incorrect or Usage limit reached. + + detected_by_word_filter: The word %{detected_message} has not been sent due to detection by the word filter. + + selected_your_language: "\"Your Language\" has set to %{your_language}." + selected_target_language: "\"Target Language\" has set to %{target_language}." + switched_language_preset_tab: Switched to Language Preset Tab No.%{tab_no}." + latest_language_setting: "Currently, \"Your Language\" is set to %{your_language}, and \"Target Language\" is set to %{target_language}." + update_available: New version is here! cover_message: The functionality is temporarily disabled until the settings window is closed. + selectable_language_window: title_your_language: Select Your Language title_target_language: Select Target Language go_back_button: Go Back + config_window: config_title: Settings compact_mode: Compact Mode diff --git a/locales/ja.yml b/locales/ja.yml index f6bb107e..93758f5d 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -14,6 +14,26 @@ main_window: textbox_tab_received: 受信 textbox_tab_system: システム + textbox_system_message: + enabled_translation: 翻訳機能をONにしました。 + disabled_translation: 翻訳機能をOFFしました。 + enabled_voice2chatbox: マイクからの音声入力、文字起こしを開始します。 + disabled_voice2chatbox: マイクからの音声入力、文字起こしを終了しました。 + enabled_speaker2log: スピーカーからの音声聞き取り、文字起こしを開始します。 + disabled_speaker2log: スピーカーからの音声聞き取り、文字起こしを終了しました。 + enabled_foreground: 画面を常に最前面へ固定します。 + disabled_foreground: 最前面への固定を解除しました。 + + auth_key_success: 認証キーの更新が完了しました。 + auth_key_error: 認証キーが間違っているか、API使用制限が上限に達しています. + + detected_by_word_filter: ワードフィルターに登録されている単語 %{detected_message} が検出されたため送信しませんでした。 + + selected_your_language: 「あなたの言語」 を %{your_language} に設定しました。 + selected_target_language: 「相手の言語」 を %{target_language} に設定しました。 + switched_language_preset_tab: 言語プリセット番号 %{tab_no} に切り替わりました。 + latest_language_setting: 現在「あなたの言語」は %{your_language}、「相手の言語」は %{target_language} に設定されています。 + update_available: 新しいバージョンが出ました! cover_message: 設定画面が閉じられるまで、一時的に機能を停止しています。 @@ -24,6 +44,7 @@ selectable_language_window: title_target_language: 相手の言語 go_back_button: 戻る + config_window: config_title: 設定 compact_mode: コンパクトモード diff --git a/view.py b/view.py index 6af00b34..83d82eda 100644 --- a/view.py +++ b/view.py @@ -597,56 +597,54 @@ class View(): def printToTextbox_enableTranslation(self): - self._printToTextbox_Info("翻訳機能をONにしました") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.enabled_translation")) def printToTextbox_disableTranslation(self): - self._printToTextbox_Info("翻訳機能をOFFにしました") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.disabled_translation")) def printToTextbox_enableTranscriptionSend(self): - self._printToTextbox_Info("Voice2chatbox機能をONにしました") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.enabled_voice2chatbox")) def printToTextbox_disableTranscriptionSend(self): - self._printToTextbox_Info("Voice2chatbox機能をOFFにしました") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.disabled_voice2chatbox")) def printToTextbox_enableTranscriptionReceive(self): - self._printToTextbox_Info("Speaker2chatbox機能をONにしました") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.enabled_speaker2log")) def printToTextbox_disableTranscriptionReceive(self): - self._printToTextbox_Info("Speaker2chatbox機能をOFFにしました") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.disabled_speaker2log")) def printToTextbox_enableForeground(self): - self._printToTextbox_Info("Start foreground") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.enabled_foreground")) def printToTextbox_disableForeground(self): - self._printToTextbox_Info("Stop foreground") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.disabled_foreground")) def printToTextbox_AuthenticationSuccess(self): - self._printToTextbox_Info("Auth key update completed") - + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.auth_key_success")) def printToTextbox_AuthenticationError(self): - self._printToTextbox_Info("Auth Key is incorrect or Usage limit reached") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.auth_key_error")) - def printToTextbox_OSCError(self): - self._printToTextbox_Info("OSC is not enabled, please enable OSC and rejoin. or turn off the \"Send Message To VRChat\" setting") + # def printToTextbox_OSCError(self): [Deprecated] + # self._printToTextbox_Info("OSC is not enabled, please enable OSC and rejoin. or turn off the \"Send Message To VRChat\" setting") def printToTextbox_DetectedByWordFilter(self, detected_message): - self._printToTextbox_Info(f"Detect WordFilter :{detected_message}") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.detected_by_word_filter"), detected_message=detected_message) def printToTextbox_selectedYourLanguages(self, selected_your_language): your_language = selected_your_language.replace("\n", " ") - self._printToTextbox_Info(f"Your Language has changed : {your_language}") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.selected_your_language", your_language=your_language)) def printToTextbox_selectedTargetLanguages(self, selected_target_language): target_language = selected_target_language.replace("\n", " ") - self._printToTextbox_Info(f"Target Language has changed : {target_language}") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.selected_target_language", target_language=target_language)) + + def printToTextbox_changedLanguagePresetTab(self, tab_no:str): + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.switched_language_preset_tab", tab_no=tab_no)) + self.printToTextbox_latestSelectedLanguages() def printToTextbox_latestSelectedLanguages(self): your_language = self.view_variable.VAR_YOUR_LANGUAGE.get().replace("\n", " ") target_language = self.view_variable.VAR_TARGET_LANGUAGE.get().replace("\n", " ") - self._printToTextbox_Info(f"Your Language : {your_language} -- Target Language : {target_language}") - - def printToTextbox_changedLanguagePresetTab(self, tab_no:str): - your_language = config.SELECTED_TAB_YOUR_LANGUAGES[tab_no].replace("\n", " ") - target_language = config.SELECTED_TAB_TARGET_LANGUAGES[tab_no].replace("\n", " ") - self._printToTextbox_Info(f"Switched Language Preset. No.{tab_no}\nYour Language : {your_language} -- Target Language : {target_language}") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.latest_language_setting", your_language=your_language, target_language=target_language)) @staticmethod From 6d6e678f72766112c8f91a1ae05e30d924733373 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 14 Oct 2023 15:59:08 +0900 Subject: [PATCH 300/355] [bugfix] Typo --- locales/ja.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/locales/ja.yml b/locales/ja.yml index 93758f5d..71995a37 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -100,7 +100,7 @@ config_window: label_for_automatic: "スピーカー入力感度の調整 (現在の設定: 自動)" desc_for_automatic: スピーカーの入力感度を自動的に調節する。 label_for_manual: "スピーカー入力感度の調整 (現在の設定: 手動)" - desc_for_manual: スライダーを調整して入力感度を手動で決められます。ヘッドフォンのアイコンを押すと、実実際に音声を聞き取り、音量を確認しながら調節できます。 + desc_for_manual: スライダーを調整して入力感度を手動で決められます。ヘッドフォンのアイコンを押すと、実際に音声を聞き取り、音量を確認しながら調節できます。 speaker_record_timeout: label: 入力が終了したとみなす無音時間 speaker_phrase_timeout: From c0fb949a381b7bd69518805d5e374d7c31ddb1b1 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 14 Oct 2023 17:16:18 +0900 Subject: [PATCH 301/355] =?UTF-8?q?[Update]=20Main=20Window:=20Textbox=20l?= =?UTF-8?q?og=E5=86=85=E3=81=AE=E3=82=B7=E3=82=B9=E3=83=86=E3=83=A0?= =?UTF-8?q?=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=AE=E8=8B=B1?= =?UTF-8?q?=E8=AA=9E=E3=80=81=E6=97=A5=E6=9C=AC=E8=AA=9E=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E3=80=82=20help=20and=20info=E3=83=9C?= =?UTF-8?q?=E3=82=BF=E3=83=B3=E3=82=92=E3=82=AF=E3=83=AA=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=81=97=E3=80=81VRCT=E3=83=89=E3=82=AD=E3=83=A5=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=83=9A=E3=83=BC=E3=82=B8=E3=81=8C=E9=96=8B?= =?UTF-8?q?=E3=81=84=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AE=E6=96=87=E8=A8=80?= =?UTF-8?q?=E3=82=92=E3=81=95=E3=82=89=E3=81=AB=E8=BF=BD=E5=8A=A0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 3 +++ locales/ja.yml | 3 +++ view.py | 4 ++-- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index b1fc34da..d40a3e73 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -34,6 +34,9 @@ main_window: switched_language_preset_tab: Switched to Language Preset Tab No.%{tab_no}." latest_language_setting: "Currently, \"Your Language\" is set to %{your_language}, and \"Target Language\" is set to %{target_language}." + opened_web_page_booth: Opened Booth page in your web browser. + opened_web_page_vrct_documents: "Opened VRCT Documents page in your web browser.\nFor any issues, requests, or inquiries, please feel free to contact us through the links at the bottom of the documents page, the \"Contact Form,\" or via X (formerly Twitter)!" + update_available: New version is here! cover_message: The functionality is temporarily disabled until the settings window is closed. diff --git a/locales/ja.yml b/locales/ja.yml index 71995a37..6f7c104c 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -34,6 +34,9 @@ main_window: switched_language_preset_tab: 言語プリセット番号 %{tab_no} に切り替わりました。 latest_language_setting: 現在「あなたの言語」は %{your_language}、「相手の言語」は %{target_language} に設定されています。 + opened_web_page_booth: お使いのブラウザで、Boothのページを開きました。 + opened_web_page_vrct_documents: "お使いのブラウザで、VRCTのドキュメントを開きました。使用方法などはそちらに記載されています。\n不具合、ご要望、その他お問い合わせはドキュメント最下部にあるLinks、「お問合せフォーム」もしくはX(元Twitter)にて気軽にご連絡ください!" + update_available: 新しいバージョンが出ました! cover_message: 設定画面が閉じられるまで、一時的に機能を停止しています。 diff --git a/view.py b/view.py index 83d82eda..f84e955d 100644 --- a/view.py +++ b/view.py @@ -523,11 +523,11 @@ class View(): def openWebPage_Booth(self): self.openWebPage(config.BOOTH_URL) - self._printToTextbox_Info("Opened Booth page in your web browser.") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.opened_web_page_booth")) def openWebPage_VrctDocuments(self): self.openWebPage(config.DOCUMENTS_URL) - self._printToTextbox_Info("Opened the VRCT Documents page in your web browser.") + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.opened_web_page_vrct_documents")) @staticmethod def showUpdateAvailableButton(): From 6ed59df59634373ab691aeb204a6c4b5cef699b8 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 14 Oct 2023 17:48:15 +0900 Subject: [PATCH 302/355] =?UTF-8?q?[Update]=20Config=20Window:=20=E5=86=8D?= =?UTF-8?q?=E8=B5=B7=E5=8B=95=E3=83=9C=E3=82=BF=E3=83=B3=E3=81=AE=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E8=A8=80=E8=AA=9E=E3=80=82UI=20Language=E3=81=8C?= =?UTF-8?q?=E5=A4=89=E6=9B=B4=E3=81=95=E3=82=8C=E3=81=9F=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AF=E3=80=81=E5=A4=89=E6=9B=B4=E5=85=88=E3=81=AE=E8=A8=80?= =?UTF-8?q?=E8=AA=9E=E3=81=A7=E8=A1=A8=E7=A4=BA=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 2 +- view.py | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/controller.py b/controller.py index d6205f82..bf8ee183 100644 --- a/controller.py +++ b/controller.py @@ -360,7 +360,7 @@ def callbackSetUiLanguage(value): value = get_key_by_value(selectable_languages, value) print("callbackSetUiLanguage__after_get_key_by_value", value) config.UI_LANGUAGE = value - view.showRestartButton() + view.showRestartButton(locale=config.UI_LANGUAGE) # Translation Tab def callbackSetDeeplAuthkey(value): diff --git a/view.py b/view.py index f84e955d..541c6b72 100644 --- a/view.py +++ b/view.py @@ -1,3 +1,4 @@ +from typing import Union from os import path as os_path from types import SimpleNamespace from tkinter import font as tk_font @@ -718,7 +719,8 @@ class View(): # Config Window - def showRestartButton(self): + def showRestartButton(self, locale:Union[None,str]=None): + self.view_variable.VAR_CONFIG_WINDOW_RESTART_BUTTON_LABEL.set(i18n.t("config_window.restart_message", locale=locale)) vrct_gui.config_window.restart_button_container.grid() def hideRestartButton(self): vrct_gui.config_window.restart_button_container.grid_remove() From f8ce0ee0e6c35e0aa10fd6d1c5aca3baf38233aa Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 14 Oct 2023 19:16:55 +0900 Subject: [PATCH 303/355] =?UTF-8?q?[Update]=20Config=20Window:=20Transcrip?= =?UTF-8?q?tion=E3=82=BF=E3=83=96=E3=80=81Mic/Speaker=20Max=20Phrases?= =?UTF-8?q?=E3=81=AA=E3=81=A9=E3=81=AE=E6=95=B0=E5=80=A4=E5=85=A5=E5=8A=9B?= =?UTF-8?q?=E7=B3=BB=E3=81=AE=E8=AA=AC=E6=98=8E=E3=81=AB=E3=80=8C=E5=8D=98?= =?UTF-8?q?=E4=BD=8D=E3=80=8D=E3=82=92=E8=A1=A8=E8=A8=98=E3=80=82yml?= =?UTF-8?q?=E5=86=85=E3=81=AB=E8=BF=BD=E5=8A=A0=E3=81=95=E3=82=8C=E3=81=9F?= =?UTF-8?q?=E3=82=B3=E3=83=A1=E3=83=B3=E3=83=88=E3=82=A2=E3=82=A6=E3=83=88?= =?UTF-8?q?desc=E6=96=87=E7=AB=A0=E3=81=AF=E5=88=B6=E4=BD=9C=E4=B8=AD?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 7 +++++++ locales/ja.yml | 7 +++++++ view.py | 8 ++++---- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index d40a3e73..122b7edf 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -89,10 +89,15 @@ config_window: desc_for_manual: "Manually determine the microphone input sensitivity using the slider. Press the microphone icon to input your voice and adjust the sensitivity while monitoring the volume." mic_record_timeout: label: Mic Record Timeout + desc: (Second(s)) + # desc: Duration in seconds for detecting silence and determining the end of audio input. mic_phrase_timeout: label: Mic Phrase Timeout + desc: (Second(s)) + # desc: Duration in seconds for determining the end of audio input and transcribing it in one go. mic_max_phrase: label: Mic Max Phrases + # desc: Once the minimum word count for transcription is reached, it will be send. mic_word_filter: label: Mic Word Filter desc: "It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC" @@ -106,8 +111,10 @@ config_window: desc_for_manual: "Manually determine the speaker input sensitivity using the slider. Press the headphones icon to listen to the audio and adjust the sensitivity while monitoring the volume." speaker_record_timeout: label: Speaker Record Timeout + desc: (Second(s)) speaker_phrase_timeout: label: Speaker Phrase Timeout + desc: (Second(s)) speaker_max_phrase: label: Speaker Max Phrases diff --git a/locales/ja.yml b/locales/ja.yml index 6f7c104c..9f4d3d74 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -89,10 +89,15 @@ config_window: desc_for_manual: スライダーを調整して入力感度を手動で決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。 mic_record_timeout: label: 入力が終了したとみなす無音時間 + desc: 単位は秒です。 + # desc: 無音を検出し、音声入力が終了したとみなす時間の長さです。(秒) mic_phrase_timeout: label: 一度に文字起こしする時間の長さ + desc: 単位は秒です。 + # desc: 一度に文字起こし処理をする音声時間の長さです。(秒) mic_max_phrase: label: 送信するまでに保持する単語数 + # desc: 文字起こしされた単語数を保持する最大値で、その数を超えると送信します。 mic_word_filter: label: ワードフィルター desc: "設定された単語を検出すると、その文章は送信されません。\n設定の例: AAA,BBB,CCC" @@ -106,8 +111,10 @@ config_window: desc_for_manual: スライダーを調整して入力感度を手動で決められます。ヘッドフォンのアイコンを押すと、実際に音声を聞き取り、音量を確認しながら調節できます。 speaker_record_timeout: label: 入力が終了したとみなす無音時間 + desc: 単位は秒です。 speaker_phrase_timeout: label: 一度に文字起こしする時間の長さ + desc: 単位は秒です。 speaker_max_phrase: label: ログとして表示するまでに保持する単語数 diff --git a/view.py b/view.py index 541c6b72..ba07425a 100644 --- a/view.py +++ b/view.py @@ -229,13 +229,13 @@ class View(): VAR_LABEL_MIC_RECORD_TIMEOUT=StringVar(value=i18n.t("config_window.mic_record_timeout.label")), - VAR_DESC_MIC_RECORD_TIMEOUT=None, + VAR_DESC_MIC_RECORD_TIMEOUT=StringVar(value=i18n.t("config_window.mic_record_timeout.desc")), CALLBACK_SET_MIC_RECORD_TIMEOUT=None, VAR_MIC_RECORD_TIMEOUT=StringVar(value=config.INPUT_MIC_RECORD_TIMEOUT), CALLBACK_FOCUS_OUT_MIC_RECORD_TIMEOUT=self.setLatestConfigVariable_MicRecordTimeout, VAR_LABEL_MIC_PHRASE_TIMEOUT=StringVar(value=i18n.t("config_window.mic_phrase_timeout.label")), - VAR_DESC_MIC_PHRASE_TIMEOUT=None, + VAR_DESC_MIC_PHRASE_TIMEOUT=StringVar(value=i18n.t("config_window.mic_phrase_timeout.desc")), CALLBACK_SET_MIC_PHRASE_TIMEOUT=None, VAR_MIC_PHRASE_TIMEOUT=StringVar(value=config.INPUT_MIC_PHRASE_TIMEOUT), CALLBACK_FOCUS_OUT_MIC_PHRASE_TIMEOUT=self.setLatestConfigVariable_MicPhraseTimeout, @@ -273,13 +273,13 @@ class View(): VAR_LABEL_SPEAKER_RECORD_TIMEOUT=StringVar(value=i18n.t("config_window.speaker_record_timeout.label")), - VAR_DESC_SPEAKER_RECORD_TIMEOUT=None, + VAR_DESC_SPEAKER_RECORD_TIMEOUT=StringVar(value=i18n.t("config_window.speaker_record_timeout.desc")), CALLBACK_SET_SPEAKER_RECORD_TIMEOUT=None, VAR_SPEAKER_RECORD_TIMEOUT=StringVar(value=config.INPUT_SPEAKER_RECORD_TIMEOUT), CALLBACK_FOCUS_OUT_SPEAKER_RECORD_TIMEOUT=self.setLatestConfigVariable_SpeakerRecordTimeout, VAR_LABEL_SPEAKER_PHRASE_TIMEOUT=StringVar(value=i18n.t("config_window.speaker_phrase_timeout.label")), - VAR_DESC_SPEAKER_PHRASE_TIMEOUT=None, + VAR_DESC_SPEAKER_PHRASE_TIMEOUT=StringVar(value=i18n.t("config_window.speaker_phrase_timeout.desc")), CALLBACK_SET_SPEAKER_PHRASE_TIMEOUT=None, VAR_SPEAKER_PHRASE_TIMEOUT=StringVar(value=config.INPUT_SPEAKER_PHRASE_TIMEOUT), CALLBACK_FOCUS_OUT_SPEAKER_PHRASE_TIMEOUT=self.setLatestConfigVariable_SpeakerPhraseTimeout, From 8f0ccdda5ba141a2577adfe2d9413f4c0bfd74d8 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 14 Oct 2023 19:49:43 +0900 Subject: [PATCH 304/355] =?UTF-8?q?[bugfix/tmp]=20Config=20Window:=20?= =?UTF-8?q?=E3=83=87=E3=83=90=E3=82=A4=E3=82=B9=E7=B3=BB=E3=81=AE=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E3=80=81=E3=83=89=E3=83=AD=E3=83=83=E3=83=97=E3=83=80?= =?UTF-8?q?=E3=82=A6=E3=83=B3=E3=83=A1=E3=83=8B=E3=83=A5=E3=83=BC=E3=81=AE?= =?UTF-8?q?=E5=B9=85=E3=82=92=E5=9F=BA=E6=9C=AC500px=E3=81=A8=E5=BA=83?= =?UTF-8?q?=E3=81=8F=E3=80=82=E9=96=93=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B?= =?UTF-8?q?=E3=81=A7=E3=81=99=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../setting_box_transcription/createSettingBox_Mic.py | 2 +- .../setting_box_transcription/createSettingBox_Speaker.py | 2 +- vrct_gui/ui_managers/UiScalingManager.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 8b192421..405c8df6 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -58,7 +58,7 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_desc_text=view_variable.VAR_DESC_MIC_DEVICE, optionmenu_attr_name="sb__optionmenu_mic_device", dropdown_menu_values=view_variable.LIST_MIC_DEVICE, - dropdown_menu_width=settings.uism.RESPONSIVE_UI_SIZE_INT_300, + dropdown_menu_width=settings.uism.RESPONSIVE_UI_SIZE_INT_500, command=lambda value: optionmenu_input_mic_device_callback(value), variable=view_variable.VAR_MIC_DEVICE, ) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 62e8dd12..64ec1854 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -41,7 +41,7 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ for_var_desc_text=view_variable.VAR_DESC_SPEAKER_DEVICE, optionmenu_attr_name="sb__optionmenu_speaker_device", dropdown_menu_values=view_variable.LIST_SPEAKER_DEVICE, - dropdown_menu_width=settings.uism.RESPONSIVE_UI_SIZE_INT_300, + dropdown_menu_width=settings.uism.RESPONSIVE_UI_SIZE_INT_500, command=lambda value: optionmenu_input_speaker_device_callback(value), variable=view_variable.VAR_SPEAKER_DEVICE, ) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 6e359211..5159bb81 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -22,7 +22,7 @@ class UiScalingManager(): self.common.SCROLLBAR_IPADX = (self._calculateUiSize(2), self._calculateUiSize(2)) self.common.SCROLLBAR_WIDTH = self._calculateUiSize(16) - for i in range(10, 301, 10): + for i in range(10, 501, 10): setattr(self.main, f"RESPONSIVE_UI_SIZE_INT_{i}", self._calculateUiSize(i)) setattr(self.config_window, f"RESPONSIVE_UI_SIZE_INT_{i}", self._calculateUiSize(i)) From 033cece3a7e62776b3b5c106d21884bb54e1abd3 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 15 Oct 2023 03:18:58 +0900 Subject: [PATCH 305/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20Auth?= =?UTF-8?q?=20key=E3=81=AE=E5=A0=B4=E5=90=88=E3=81=AB=E7=BF=BB=E8=A8=B3?= =?UTF-8?q?=E8=A8=80=E8=AA=9E=E8=A8=AD=E5=AE=9A=E3=81=8C=E5=9B=BD=E3=82=92?= =?UTF-8?q?=E5=90=AB=E3=82=81=E3=81=9F=E8=A8=80=E8=AA=9E=E8=A8=AD=E5=AE=9A?= =?UTF-8?q?=E3=81=AB=E3=81=AA=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/model.py b/model.py index 7b148552..57343efe 100644 --- a/model.py +++ b/model.py @@ -150,6 +150,18 @@ class Model: def getInputTranslate(self, message): try: + if config.CHOICE_TRANSLATOR == "DeepL(auth)": + if config.TARGET_LANGUAGE == "English": + if config.TARGET_COUNTRY in ["United States", "Canada", "Philippines"]: + config.TARGET_LANGUAGE = "English American" + else: + config.TARGET_LANGUAGE = "English British" + elif config.TARGET_LANGUAGE in ["Portuguese"]: + if config.TARGET_COUNTRY == "Portugal": + config.TARGET_LANGUAGE = "Portuguese European" + else: + config.TARGET_LANGUAGE = "Portuguese Brazilian" + translation = self.translator.translate( translator_name=config.CHOICE_TRANSLATOR, source_language=config.SOURCE_LANGUAGE, @@ -162,6 +174,18 @@ class Model: def getOutputTranslate(self, message): try: + if config.CHOICE_TRANSLATOR == "DeepL(auth)": + if config.SOURCE_LANGUAGE == "English": + if config.SOURCE_COUNTRY in ["United States", "Canada", "Philippines"]: + config.SOURCE_LANGUAGE = "English American" + else: + config.SOURCE_LANGUAGE = "English British" + elif config.SOURCE_LANGUAGE in ["Portuguese"]: + if config.SOURCE_COUNTRY == "Portugal": + config.SOURCE_LANGUAGE = "Portuguese European" + else: + config.SOURCE_LANGUAGE = "Portuguese Brazilian" + translation = self.translator.translate( translator_name=config.CHOICE_TRANSLATOR, source_language=config.TARGET_LANGUAGE, From 39a63cb0e2d76cf629ba9f9da8670b412319db8e Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 15 Oct 2023 03:38:06 +0900 Subject: [PATCH 306/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Controller=20:?= =?UTF-8?q?=20ENABLE=5FSEND=5FMESSAGE=5FTO=5FVRC=E3=81=8CFalse=E3=81=AE?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=AB=E3=82=BF=E3=82=A4=E3=83=94=E3=83=B3?= =?UTF-8?q?=E3=82=B0=E3=81=8C=E9=80=81=E4=BF=A1=E3=81=95=E3=82=8C=E3=81=AA?= =?UTF-8?q?=E3=81=84=E3=82=88=E3=81=86=E3=81=AB=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/controller.py b/controller.py index bf8ee183..dab14af3 100644 --- a/controller.py +++ b/controller.py @@ -185,7 +185,10 @@ def messageBoxPressKeyEnter(e): sendChatMessage(message) def messageBoxPressKeyAny(e): - model.oscStartSendTyping() + if config.ENABLE_SEND_MESSAGE_TO_VRC is True: + model.oscStartSendTyping() + else: + model.oscStopSendTyping() # func select languages def initSetLanguageAndCountry(): From 5152b3679c988234a3901f1481697f5d5f817775 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 15 Oct 2023 04:21:52 +0900 Subject: [PATCH 307/355] =?UTF-8?q?[Refactor]=20Model=20=EF=BC=9A=20spk=20?= =?UTF-8?q?->=20speaker?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/model.py b/model.py index 57343efe..9ba41389 100644 --- a/model.py +++ b/model.py @@ -66,8 +66,8 @@ class Model: self.mic_audio_recorder = None self.mic_energy_recorder = None self.mic_energy_plot_progressbar = None - self.spk_print_transcript = None - self.spk_audio_recorder = None + self.speaker_print_transcript = None + self.speaker_audio_recorder = None self.speaker_energy_recorder = None self.speaker_energy_plot_progressbar = None self.translator = Translator() @@ -394,46 +394,46 @@ class Model: def startSpeakerTranscript(self, fnc): if config.CHOICE_SPEAKER_DEVICE == "NoDevice": return - spk_audio_queue = Queue() - spk_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] + speaker_audio_queue = Queue() + speaker_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] record_timeout = config.INPUT_SPEAKER_RECORD_TIMEOUT phase_timeout = config.INPUT_SPEAKER_PHRASE_TIMEOUT if record_timeout > phase_timeout: record_timeout = phase_timeout - self.spk_audio_recorder = SelectedSpeakerRecorder( - device=spk_device, + self.speaker_audio_recorder = SelectedSpeakerRecorder( + device=speaker_device, energy_threshold=config.INPUT_SPEAKER_ENERGY_THRESHOLD, dynamic_energy_threshold=config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, record_timeout=record_timeout, ) - self.spk_audio_recorder.recordIntoQueue(spk_audio_queue) - spk_transcriber = AudioTranscriber( + self.speaker_audio_recorder.recordIntoQueue(speaker_audio_queue) + speaker_transcriber = AudioTranscriber( speaker=True, - source=self.spk_audio_recorder.source, + source=self.speaker_audio_recorder.source, phrase_timeout=phase_timeout, max_phrases=config.INPUT_SPEAKER_MAX_PHRASES, ) - def sendSpkTranscript(): - spk_transcriber.transcribeAudioQueue(spk_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) - message = spk_transcriber.getTranscript() + def sendSpeakerTranscript(): + speaker_transcriber.transcribeAudioQueue(speaker_audio_queue, config.TARGET_LANGUAGE, config.TARGET_COUNTRY) + message = speaker_transcriber.getTranscript() try: fnc(message) except: pass - self.spk_print_transcript = threadFnc(sendSpkTranscript) - self.spk_print_transcript.daemon = True - self.spk_print_transcript.start() + self.speaker_print_transcript = threadFnc(sendSpeakerTranscript) + self.speaker_print_transcript.daemon = True + self.speaker_print_transcript.start() def stopSpeakerTranscript(self): - if isinstance(self.spk_print_transcript, threadFnc): - self.spk_print_transcript.stop() - self.spk_print_transcript = None - if isinstance(self.spk_audio_recorder, SelectedSpeakerRecorder): - self.spk_audio_recorder.stop() - self.spk_audio_recorder = None + if isinstance(self.speaker_print_transcript, threadFnc): + self.speaker_print_transcript.stop() + self.speaker_print_transcript = None + if isinstance(self.speaker_audio_recorder, SelectedSpeakerRecorder): + self.speaker_audio_recorder.stop() + self.speaker_audio_recorder = None def startCheckSpeakerEnergy(self, fnc, end_fnc): if config.CHOICE_SPEAKER_DEVICE == "NoDevice": From 808007ece6efae0eb1944d02d3b331713be19cab Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 15 Oct 2023 10:55:20 +0900 Subject: [PATCH 308/355] =?UTF-8?q?[bufgix]=20XSOverlay=E3=81=B8=E3=81=AE?= =?UTF-8?q?=E5=8F=97=E4=BF=A1=E3=83=A1=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8?= =?UTF-8?q?=E9=80=81=E4=BF=A1=E6=A9=9F=E8=83=BD=E3=81=A7=E3=80=81=E7=BF=BB?= =?UTF-8?q?=E8=A8=B3=E3=82=92=E3=82=AA=E3=83=95=E3=81=AB=E3=81=97=E3=81=9F?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=AB=E3=82=82=E3=83=A1=E3=83=83=E3=82=BB?= =?UTF-8?q?=E3=83=BC=E3=82=B8=E3=83=95=E3=82=A9=E3=83=BC=E3=83=9E=E3=83=83?= =?UTF-8?q?=E3=83=88=E3=81=8C=E9=81=A9=E7=94=A8=E3=81=95=E3=82=8C=E3=81=A6?= =?UTF-8?q?=E6=8B=AC=E5=BC=A7=E3=81=8C=E5=85=A5=E3=81=A3=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=81=9F=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/controller.py b/controller.py index bf8ee183..1154a7e7 100644 --- a/controller.py +++ b/controller.py @@ -99,9 +99,14 @@ def receiveSpeakerMessage(message): if config.ENABLE_TRANSCRIPTION_RECEIVE is True: if config.ENABLE_NOTICE_XSOVERLAY is True: - xsoverlay_message = config.MESSAGE_FORMAT.replace("[message]", message) - xsoverlay_message = xsoverlay_message.replace("[translation]", translation) + if len(translation) > 0: + xsoverlay_message = config.MESSAGE_FORMAT.replace("[message]", message) + xsoverlay_message = xsoverlay_message.replace("[translation]", translation) + else: + xsoverlay_message = message model.notificationXSOverlay(xsoverlay_message) + + # update textbox message log (Received) view.printToTextbox_ReceivedMessage(message, translation) if config.ENABLE_LOGGER is True: if len(translation) > 0: @@ -168,7 +173,7 @@ def sendChatMessage(message): osc_message = message model.oscSendMessage(osc_message) - # update textbox message log + # update textbox message log (Sent) view.printToTextbox_SentMessage(message, translation) if config.ENABLE_LOGGER is True: if len(translation) > 0: From 465f9f962890b4863a769f2c777526191dd6c752 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sun, 15 Oct 2023 11:32:38 +0900 Subject: [PATCH 309/355] =?UTF-8?q?[bugfix]=20Main=20Window:=20UI=E5=B4=A9?= =?UTF-8?q?=E5=A3=8A=E4=BF=AE=E6=AD=A3=20Windows=E6=9C=AC=E4=BD=93?= =?UTF-8?q?=E3=81=A7=E3=82=B9=E3=82=B1=E3=83=BC=E3=83=AA=E3=83=B3=E3=82=B0?= =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=ABUI=E3=81=8C=E5=B4=A9=E3=82=8C?= =?UTF-8?q?=E3=82=8B=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82DPI?= =?UTF-8?q?=E3=82=92=E5=9B=BA=E5=AE=9A=E3=81=99=E3=82=8B=E3=81=9F=E3=82=81?= =?UTF-8?q?=E3=81=ABctypes=E3=82=92=E4=BD=BF=E3=81=A3=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=82=8B=E3=81=9F=E3=82=81=E3=80=81requirements.txt=E3=81=AB?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 3 +++ requirements.txt | 3 ++- view.py | 1 - vrct_gui/_changeMainWindowWidgetsStatus.py | 8 +++----- .../widgets/_create_sidebar/createSidebarFeatures.py | 11 +++-------- vrct_gui/main_window/widgets/create_sidebar.py | 7 +++---- vrct_gui/ui_managers/UiScalingManager.py | 8 ++++---- 7 files changed, 18 insertions(+), 23 deletions(-) diff --git a/main.py b/main.py index b3801625..12994131 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,6 @@ +import ctypes +ctypes.windll.shcore.SetProcessDpiAwareness(1) + from vrct_gui.splash_window import SplashWindow splash = SplashWindow() splash.showSplash() diff --git a/requirements.txt b/requirements.txt index a399733d..339ae06d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,4 +5,5 @@ customtkinter == 5.2.0 deepl == 1.15.0 flashtext == 2.7 pyyaml == 6.0.1 -python-i18n == 0.3.9 \ No newline at end of file +python-i18n == 0.3.9 +ctypes \ No newline at end of file diff --git a/view.py b/view.py index ba07425a..c3aea143 100644 --- a/view.py +++ b/view.py @@ -38,7 +38,6 @@ class View(): self.settings.main = SimpleNamespace( ctm=all_ctm.main, uism=all_uism.main, - COMPACT_MODE_ICON_SIZE=0, **common_args ) diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index dc303094..4910797d 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -1,8 +1,6 @@ from customtkinter import CTkImage def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, target_names): - COMPACT_MODE_ICON_SIZE_TUPLES = (settings.COMPACT_MODE_ICON_SIZE, settings.COMPACT_MODE_ICON_SIZE) - if target_names == "All": target_names = ["translation_switch", "transcription_send_switch", "transcription_receive_switch", "foreground_switch", "quick_language_settings", "config_button", "minimize_sidebar_button", "entry_message_box"] @@ -32,7 +30,7 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta widget_selected_mark.configure(fg_color=settings.ctm.SF__SELECTED_MARK_ACTIVE_BG_COLOR) icon_file = icon_name - image = CTkImage(icon_file, size=COMPACT_MODE_ICON_SIZE_TUPLES) + image = CTkImage(icon_file, size=settings.uism.SF__COMPACT_MODE_IMAGE_SIZE) widget_compact_mode_icon.configure(image=image) @@ -109,12 +107,12 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta if status == "disabled": vrct_gui.sidebar_config_button_wrapper.configure(cursor="") vrct_gui.sidebar_config_button.configure( - image=CTkImage(settings.image_file.CONFIGURATION_ICON_DISABLED, size=COMPACT_MODE_ICON_SIZE_TUPLES), + image=CTkImage(settings.image_file.CONFIGURATION_ICON_DISABLED, size=settings.uism.SF__COMPACT_MODE_IMAGE_SIZE), ) elif status == "normal": vrct_gui.sidebar_config_button_wrapper.configure(cursor="hand2") vrct_gui.sidebar_config_button.configure( - image=CTkImage(settings.image_file.CONFIGURATION_ICON, size=COMPACT_MODE_ICON_SIZE_TUPLES), + image=CTkImage(settings.image_file.CONFIGURATION_ICON, size=settings.uism.SF__COMPACT_MODE_IMAGE_SIZE), ) diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py index 0f8314f0..ad5e09a5 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py @@ -217,11 +217,6 @@ def createSidebarFeatures(settings, main_window, view_variable): setattr(main_window, switch_box_attr_name, switch_box_widget) - if settings.COMPACT_MODE_ICON_SIZE == 0: - label_widget.grid(row=row, column=0, pady=settings.uism.SF__LABELS_IPADY, padx=(settings.uism.SF__LABEL_LEFT_PAD,0), sticky="ew") - settings.COMPACT_MODE_ICON_SIZE = int(getLatestHeight(frame_widget) - settings.uism.SF__COMPACT_MODE_ICON_PADY*2) - label_widget.grid_remove() - # for compact mode compact_mode_icon_widget = CTkLabel( @@ -229,7 +224,7 @@ def createSidebarFeatures(settings, main_window, view_variable): text=None, height=0, corner_radius=0, - image=CTkImage((icon_file),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)), + image=CTkImage(icon_file, size=settings.uism.SF__COMPACT_MODE_IMAGE_SIZE), ) setattr(main_window, compact_mode_icon_attr_name, compact_mode_icon_widget) @@ -244,11 +239,11 @@ def createSidebarFeatures(settings, main_window, view_variable): # Arrange - compact_mode_icon_widget.grid(row=row, column=0, pady=settings.uism.SF__COMPACT_MODE_ICON_PADY) + compact_mode_icon_widget.grid(row=row, column=0, padx=settings.uism.SF__COMPACT_MODE_ICON_PADX, pady=settings.uism.SF__COMPACT_MODE_ICON_PADY) selected_mark_widget.place(relx=-1, rely=0.5, relheight=0.75, anchor="center") label_widget.grid(row=row, column=0, pady=settings.uism.SF__LABELS_IPADY, padx=(settings.uism.SF__LABEL_LEFT_PAD,0), sticky="ew") - switch_box_widget.grid(row=row, column=0, padx=(0,settings.uism.SF__SWITCH_BOX_RIGHT_PAD), sticky="e") + switch_box_widget.grid(row=row, column=1, padx=settings.uism.SF__SWITCH_BOX_PADX, sticky="e") # Unbind the event "" originally set up by the widget to manually control it. diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 763472a0..6e7fe42f 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -16,8 +16,8 @@ def createSidebar(settings, main_window, view_variable): main_window.sidebar_compact_mode_bg_container = CTkFrame(main_window.sidebar_bg_container_wrapper, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) - main_window.sidebar_bg_container.grid_columnconfigure(0, weight=0, minsize=settings.uism.SIDEBAR_MIN_WIDTH) - main_window.sidebar_compact_mode_bg_container.grid_columnconfigure(0, weight=0, minsize=settings.uism.COMPACT_MODE_SIDEBAR_MIN_WIDTH) + main_window.sidebar_bg_container.grid_columnconfigure(0, weight=1) + main_window.sidebar_compact_mode_bg_container.grid_columnconfigure(0, weight=1) createSidebarFeatures(settings, main_window, view_variable) @@ -45,12 +45,11 @@ def createSidebar(settings, main_window, view_variable): main_window.sidebar_config_button_wrapper.grid_columnconfigure(0, weight=1) - settings.uism.CONFIG_BUTTON_PADX = 0 main_window.sidebar_config_button = CTkLabel( main_window.sidebar_config_button_wrapper, text=None, height=0, - image=CTkImage((settings.image_file.CONFIGURATION_ICON),size=(settings.COMPACT_MODE_ICON_SIZE,settings.COMPACT_MODE_ICON_SIZE)) + image=CTkImage(settings.image_file.CONFIGURATION_ICON, size=settings.uism.SIDEBAR_CONFIG_BUTTON_IMAGE_SIZE) ) main_window.sidebar_config_button.grid(row=0, column=0, padx=0, pady=settings.uism.SIDEBAR_CONFIG_BUTTON_IPADY) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 5159bb81..a7e581ce 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -54,9 +54,6 @@ class UiScalingManager(): # Sidebar - self.main.SIDEBAR_MIN_WIDTH = self._calculateUiSize(230) - self.main.COMPACT_MODE_SIDEBAR_MIN_WIDTH = self._calculateUiSize(60) - # Sidebar Features self.main.SF__LOGO_MAX_SIZE = self._calculateUiSize(120) self.main.SF__LOGO_PADY = (self._calculateUiSize(12),self._calculateUiSize(8)) @@ -64,10 +61,12 @@ class UiScalingManager(): self.main.SF__LABELS_IPADY = self._calculateUiSize(16) self.main.SF__COMPACT_MODE_ICON_PADY = self.main.SF__LABELS_IPADY + self.main.SF__COMPACT_MODE_ICON_PADX = self.main.SF__COMPACT_MODE_ICON_PADY self.main.SF__LABEL_LEFT_PAD = self._calculateUiSize(20) self.main.SF__LABEL_FONT_SIZE = self._calculateUiSize(16) + self.main.SF__COMPACT_MODE_IMAGE_SIZE = (self._calculateUiSize(20), self._calculateUiSize(20)) - self.main.SF__SWITCH_BOX_RIGHT_PAD = self._calculateUiSize(10) + self.main.SF__SWITCH_BOX_PADX = (self.main.SF__LABEL_LEFT_PAD, self._calculateUiSize(10)) self.main.SF__SWITCH_BOX_WIDTH = self._calculateUiSize(40) self.main.SF__SWITCH_BOX_HEIGHT = self._calculateUiSize(16) @@ -98,6 +97,7 @@ class UiScalingManager(): self.main.SLS__BOX_TOP_PADY = self._calculateUiSize(16) self.main.SIDEBAR_CONFIG_BUTTON_CORNER_RADIUS = self._calculateUiSize(6) + self.main.SIDEBAR_CONFIG_BUTTON_IMAGE_SIZE = self.main.SF__COMPACT_MODE_IMAGE_SIZE self.main.SIDEBAR_CONFIG_BUTTON_PADX = self._calculateUiSize(10) self.main.SIDEBAR_CONFIG_BUTTON_PADY = self._calculateUiSize(10) self.main.SIDEBAR_CONFIG_BUTTON_IPADY = self._calculateUiSize(8) From 8e93080a8e9f3f95826c855c09ff4bd840dcdc3f Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 15 Oct 2023 16:25:36 +0900 Subject: [PATCH 310/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20?= =?UTF-8?q?=E3=82=B9=E3=83=94=E3=83=BC=E3=82=AB=E3=83=BC=E3=83=87=E3=83=90?= =?UTF-8?q?=E3=82=A4=E3=82=B9=E3=81=AE=E5=8F=96=E5=BE=97=E3=82=BF=E3=82=A4?= =?UTF-8?q?=E3=83=9F=E3=83=B3=E3=82=B0=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config windowでのデバイス選択が不要になりました --- controller.py | 12 ++++++------ model.py | 14 +++++--------- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/controller.py b/controller.py index dab14af3..df38e1ec 100644 --- a/controller.py +++ b/controller.py @@ -491,12 +491,12 @@ def callbackSetMicWordFilter(value): model.addKeywords() # Transcription Tab (Speaker) -def callbackSetSpeakerDevice(value): - print("callbackSetSpeakerDevice", value) - config.CHOICE_SPEAKER_DEVICE = value +# def callbackSetSpeakerDevice(value): +# print("callbackSetSpeakerDevice", value) +# config.CHOICE_SPEAKER_DEVICE = value - model.stopCheckSpeakerEnergy() - view.replaceSpeakerThresholdCheckButton_Passive() +# model.stopCheckSpeakerEnergy() +# view.replaceSpeakerThresholdCheckButton_Passive() def callbackSetSpeakerEnergyThreshold(value): print("callbackSetSpeakerEnergyThreshold", value) @@ -708,7 +708,7 @@ def createMainWindow(): "callback_set_mic_word_filter": callbackSetMicWordFilter, # Transcription Tab (Speaker) - "callback_set_speaker_device": callbackSetSpeakerDevice, + # "callback_set_speaker_device": callbackSetSpeakerDevice, "list_speaker_device": model.getListOutputDevice(), "callback_set_speaker_energy_threshold": callbackSetSpeakerEnergyThreshold, "callback_set_speaker_dynamic_energy_threshold": callbackSetSpeakerDynamicEnergyThreshold, diff --git a/model.py b/model.py index 9ba41389..93ad8882 100644 --- a/model.py +++ b/model.py @@ -13,6 +13,7 @@ from requests import get as requests_get import webbrowser from flashtext import KeywordProcessor +import pyaudiowpatch from models.translation.translation_translator import Translator from models.transcription.transcription_utils import getInputDevices, getOutputDevices, getDefaultInputDevice, getDefaultOutputDevice from models.osc.osc_tools import sendTyping, sendMessage, sendTestAction, receiveOscParameters @@ -311,13 +312,6 @@ class Model: def getListOutputDevice(): return [device["name"] for device in getOutputDevices()] - @staticmethod - def checkSpeakerStatus(choice=config.CHOICE_SPEAKER_DEVICE): - speaker_device = [device for device in getOutputDevices() if device["name"] == choice][0] - if getDefaultOutputDevice()["index"] == speaker_device["index"]: - return True - return False - def startMicTranscript(self, fnc): if config.CHOICE_MIC_HOST == "NoHost" or config.CHOICE_MIC_DEVICE == "NoDevice": return @@ -392,10 +386,11 @@ class Model: self.mic_energy_recorder = None def startSpeakerTranscript(self, fnc): + speaker_device = getDefaultOutputDevice() + config.CHOICE_SPEAKER_DEVICE = speaker_device["name"] if config.CHOICE_SPEAKER_DEVICE == "NoDevice": return speaker_audio_queue = Queue() - speaker_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] record_timeout = config.INPUT_SPEAKER_RECORD_TIMEOUT phase_timeout = config.INPUT_SPEAKER_PHRASE_TIMEOUT @@ -436,6 +431,8 @@ class Model: self.speaker_audio_recorder = None def startCheckSpeakerEnergy(self, fnc, end_fnc): + speaker_device = getDefaultOutputDevice() + config.CHOICE_SPEAKER_DEVICE = speaker_device["name"] if config.CHOICE_SPEAKER_DEVICE == "NoDevice": return @@ -448,7 +445,6 @@ class Model: pass # sleep(0.01) - speaker_device = [device for device in getOutputDevices() if device["name"] == config.CHOICE_SPEAKER_DEVICE][0] speaker_energy_queue = Queue() self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(speaker_device) self.speaker_energy_recorder.recordIntoQueue(speaker_energy_queue) From acbdfa501b7c6b042b8175ef70bdd14e6cc253e9 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sun, 15 Oct 2023 17:03:47 +0900 Subject: [PATCH 311/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20?= =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AApackage=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/model.py b/model.py index 93ad8882..f3ab0131 100644 --- a/model.py +++ b/model.py @@ -13,7 +13,6 @@ from requests import get as requests_get import webbrowser from flashtext import KeywordProcessor -import pyaudiowpatch from models.translation.translation_translator import Translator from models.transcription.transcription_utils import getInputDevices, getOutputDevices, getDefaultInputDevice, getDefaultOutputDevice from models.osc.osc_tools import sendTyping, sendMessage, sendTestAction, receiveOscParameters From f1f803f8e34dceb7e7c9eda85bf7396f3b3c90df Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 16 Oct 2023 00:05:28 +0900 Subject: [PATCH 312/355] =?UTF-8?q?[Remove]=20Model=20:=20DeepL=E3=81=AEAu?= =?UTF-8?q?th=E3=82=AD=E3=83=BC=E3=82=92=E5=BF=85=E8=A6=81=E3=81=A8?= =?UTF-8?q?=E3=81=99=E3=82=8B=E5=87=A6=E7=90=86=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 19 ---- controller.py | 41 --------- model.py | 49 ---------- models/translation/translation_languages.py | 96 +++----------------- models/translation/translation_translator.py | 75 +++++---------- view.py | 2 +- 6 files changed, 37 insertions(+), 245 deletions(-) diff --git a/config.py b/config.py index 6e1da88e..b0fdacc5 100644 --- a/config.py +++ b/config.py @@ -429,19 +429,6 @@ class Config: self._OSC_PORT = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property - @json_serializable('AUTH_KEYS') - def AUTH_KEYS(self): - return self._AUTH_KEYS - - @AUTH_KEYS.setter - def AUTH_KEYS(self, value): - if type(value) is dict and set(value.keys()) == set(self.AUTH_KEYS.keys()): - for key, value in value.items(): - if type(value) is str: - self._AUTH_KEYS[key] = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.AUTH_KEYS) - @property @json_serializable('MESSAGE_FORMAT') def MESSAGE_FORMAT(self): @@ -578,12 +565,6 @@ class Config: self._INPUT_SPEAKER_MAX_PHRASES = 10 self._OSC_IP_ADDRESS = "127.0.0.1" self._OSC_PORT = 9000 - self._AUTH_KEYS = { - "DeepL(web)": None, - "DeepL(auth)": None, - "Bing(web)": None, - "Google(web)": None, - } self._MESSAGE_FORMAT = "[message]([translation])" self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = True self._ENABLE_NOTICE_XSOVERLAY = False diff --git a/controller.py b/controller.py index 3a1f4ad4..cd77fd76 100644 --- a/controller.py +++ b/controller.py @@ -22,13 +22,10 @@ def sendMicMessage(message): return elif config.ENABLE_TRANSLATION is False: pass - elif model.getTranslatorStatus() is False: - view.printToTextbox_AuthenticationError() else: translation = model.getInputTranslate(message) if translation == None: - view.printToTextbox_AuthenticationError() translation = "" if config.ENABLE_TRANSCRIPTION_SEND is True: @@ -88,13 +85,10 @@ def receiveSpeakerMessage(message): translation = "" if config.ENABLE_TRANSLATION is False: pass - elif model.getTranslatorStatus() is False: - view.printToTextbox_AuthenticationError() else: translation = model.getOutputTranslate(message) if translation == None: - view.printToTextbox_AuthenticationError() translation = "" if config.ENABLE_TRANSCRIPTION_RECEIVE is True: @@ -155,13 +149,10 @@ def sendChatMessage(message): translation = "" if config.ENABLE_TRANSLATION is False: pass - elif model.getTranslatorStatus() is False: - view.printToTextbox_AuthenticationError() else: translation = model.getInputTranslate(message) if translation == None: - view.printToTextbox_AuthenticationError() translation = "" # send OSC message @@ -201,14 +192,11 @@ def initSetLanguageAndCountry(): language, country = model.getLanguageAndCountry(select) config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country - select = config.SELECTED_TAB_TARGET_LANGUAGES[config.SELECTED_TAB_NO] language, country = model.getLanguageAndCountry(select) config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) def setYourLanguageAndCountry(select): languages = config.SELECTED_TAB_YOUR_LANGUAGES @@ -218,7 +206,6 @@ def setYourLanguageAndCountry(select): config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) view.printToTextbox_selectedYourLanguages(select) def setTargetLanguageAndCountry(select): @@ -229,7 +216,6 @@ def setTargetLanguageAndCountry(select): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) view.printToTextbox_selectedTargetLanguages(select) def callbackSelectedLanguagePresetTab(selected_tab_no): @@ -246,11 +232,8 @@ def callbackSelectedLanguagePresetTab(selected_tab_no): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) view.printToTextbox_changedLanguagePresetTab(config.SELECTED_TAB_NO) -def callbackSetAuthKeys(keys): - config.AUTH_KEYS = keys # command func def callbackToggleTranslation(is_turned_on): @@ -370,21 +353,6 @@ def callbackSetUiLanguage(value): config.UI_LANGUAGE = value view.showRestartButton(locale=config.UI_LANGUAGE) -# Translation Tab -def callbackSetDeeplAuthkey(value): - print("callbackSetDeeplAuthkey", str(value)) - if len(value) > 0 and model.authenticationTranslator(callbackSetAuthKeys, choice_translator="DeepL(auth)", auth_key=value) is True: - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) - view.printToTextbox_AuthenticationSuccess() - elif len(value) == 0: - auth_keys = config.AUTH_KEYS - auth_keys["DeepL(auth)"] = None - config.AUTH_KEYS = auth_keys - model.authenticationTranslator(callbackSetAuthKeys) - else: - view.printToTextbox_AuthenticationError() - # Transcription Tab (Mic) def callbackSetMicHost(value): print("callbackSetMicHost", value) @@ -633,12 +601,6 @@ def createMainWindow(): # init config initSetLanguageAndCountry() - if model.authenticationTranslator(callbackSetAuthKeys) is False: - # error update Auth key - view.printToTextbox_AuthenticationError() - config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) - # set word filter model.addKeywords() @@ -696,9 +658,6 @@ def createMainWindow(): "callback_set_font_family": callbackSetFontFamily, "callback_set_ui_language": callbackSetUiLanguage, - # Translation Tab - "callback_set_deepl_authkey": callbackSetDeeplAuthkey, - # Transcription Tab (Mic) "callback_set_mic_host": callbackSetMicHost, "list_mic_host": model.getListInputHost(), diff --git a/model.py b/model.py index f3ab0131..6b8781cc 100644 --- a/model.py +++ b/model.py @@ -81,22 +81,6 @@ class Model: del self.keyword_processor self.keyword_processor = KeywordProcessor() - def authenticationTranslator(self, fnc, choice_translator=None, auth_key=None): - if choice_translator == None: - choice_translator = config.CHOICE_TRANSLATOR - if auth_key == None: - auth_key = config.AUTH_KEYS[choice_translator] - - result = self.translator.authentication(choice_translator, auth_key) - if result: - auth_keys = config.AUTH_KEYS - auth_keys[choice_translator] = auth_key - try: - fnc(auth_keys) - except: - pass - return result - def startLogger(self): os_makedirs(os_path.join(os_path.dirname(sys.argv[0]), "logs"), exist_ok=True) logger = getLogger() @@ -137,31 +121,10 @@ class Model: if source_lang in source_languages and target_lang in target_languages: compatible_engines.append(engine) engine_name = compatible_engines[0] - - if engine_name == "DeepL(web)" and config.AUTH_KEYS["DeepL(auth)"] != None: - engine_name = "DeepL(auth)" return engine_name - def getTranslatorStatus(self): - return self.translator.translator_status[config.CHOICE_TRANSLATOR] - - def getListTranslatorName(self): - return list(self.translator.translator_status.keys()) - def getInputTranslate(self, message): try: - if config.CHOICE_TRANSLATOR == "DeepL(auth)": - if config.TARGET_LANGUAGE == "English": - if config.TARGET_COUNTRY in ["United States", "Canada", "Philippines"]: - config.TARGET_LANGUAGE = "English American" - else: - config.TARGET_LANGUAGE = "English British" - elif config.TARGET_LANGUAGE in ["Portuguese"]: - if config.TARGET_COUNTRY == "Portugal": - config.TARGET_LANGUAGE = "Portuguese European" - else: - config.TARGET_LANGUAGE = "Portuguese Brazilian" - translation = self.translator.translate( translator_name=config.CHOICE_TRANSLATOR, source_language=config.SOURCE_LANGUAGE, @@ -174,18 +137,6 @@ class Model: def getOutputTranslate(self, message): try: - if config.CHOICE_TRANSLATOR == "DeepL(auth)": - if config.SOURCE_LANGUAGE == "English": - if config.SOURCE_COUNTRY in ["United States", "Canada", "Philippines"]: - config.SOURCE_LANGUAGE = "English American" - else: - config.SOURCE_LANGUAGE = "English British" - elif config.SOURCE_LANGUAGE in ["Portuguese"]: - if config.SOURCE_COUNTRY == "Portugal": - config.SOURCE_LANGUAGE = "Portuguese European" - else: - config.SOURCE_LANGUAGE = "Portuguese Brazilian" - translation = self.translator.translate( translator_name=config.CHOICE_TRANSLATOR, source_language=config.TARGET_LANGUAGE, diff --git a/models/translation/translation_languages.py b/models/translation/translation_languages.py index 1b68bf81..f51b12a1 100644 --- a/models/translation/translation_languages.py +++ b/models/translation/translation_languages.py @@ -1,6 +1,6 @@ -translatorEngine = ["DeepL(web)", "DeepL(auth)", "Google(web)", "Bing(web)"] +translatorEngine = ["DeepL", "Google", "Bing"] translation_lang = {} -dict_deepl_web_languages = { +dict_deepl_languages = { "Japanese":"JA", "English":"EN", "Korean":"KO", @@ -31,82 +31,12 @@ dict_deepl_web_languages = { "Turkish":"TR", "Norwegian":"NB", } -translation_lang["DeepL(web)"] = { - "source":dict_deepl_web_languages, - "target":dict_deepl_web_languages, +translation_lang["DeepL"] = { + "source":dict_deepl_languages, + "target":dict_deepl_languages, } -dict_deepl_auth_source_languages = { - "Japanese":"ja", - "English":"en", - "Bulgarian":"bg", - "Czech":"cs", - "Danish":"da", - "German":"de", - "Greek":"el", - "Spanish":"es", - "Estonian":"et", - "Finnish":"fi", - "French":"fr", - "Hungarian":"hu", - "Indonesian":"id", - "Italian":"it", - "Korean":"ko", - "Lithuanian":"lt", - "Latvian":"lv", - "Norwegian":"nb", - "Dutch":"nl", - "Polish":"pl", - "Portuguese":"pt", - "Romanian":"ro", - "Russian":"ru", - "Slovak":"sk", - "Slovenian":"sl", - "Swedish":"sv", - "Turkish":"tr", - "Ukrainian":"uk", - "Chinese":"zh" -} -dict_deepl_auth_target_languages = { - "Japanese":"ja", - "English American":"en-US", - "English British":"en-GB", - "Bulgarian":"bg", - "Czech":"cs", - "Danish":"da", - "German":"de", - "Greek":"el", - "English":"en", - "Spanish":"es", - "Estonian":"et", - "Finnish":"fi", - "French":"fr", - "Hungarian":"hu", - "Indonesian":"id", - "Italian":"it", - "Korean":"ko", - "Lithuanian":"lt", - "Latvian":"lv", - "Norwegian":"nb", - "Dutch":"nl", - "Polish":"pl", - "Portuguese Brazilian":"pt-BR", - "Portuguese European":"pt-PT", - "Romanian":"ro", - "Russian":"ru", - "Slovak":"sk", - "Slovenian":"sl", - "Swedish":"sv", - "Turkish":"tr", - "Ukrainian":"uk", - "Chinese":"zh" -} -translation_lang["DeepL(auth)"] = { - "source": dict_deepl_auth_source_languages, - "target": dict_deepl_auth_target_languages, -} - -dict_google_web_languages = { +dict_google_languages = { "Japanese":"ja", "English":"en", "Chinese":"zh", @@ -170,12 +100,12 @@ dict_google_web_languages = { "Basque":"eu", "Irish":"ga" } -translation_lang["Google(web)"] = { - "source":dict_google_web_languages, - "target":dict_google_web_languages, +translation_lang["Google"] = { + "source":dict_google_languages, + "target":dict_google_languages, } -dict_bing_web_languages = { +dict_bing_languages = { "Japanese":"ja", "English":"en", "Chinese":"zh", @@ -237,7 +167,7 @@ dict_bing_web_languages = { "Punjabi":"pa", "Irish":"ga" } -translation_lang["Bing(web)"] = { - "source":dict_bing_web_languages, - "target":dict_bing_web_languages, +translation_lang["Bing"] = { + "source":dict_bing_languages, + "target":dict_bing_languages, } \ No newline at end of file diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index da4f911f..f23941e5 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -6,63 +6,34 @@ from .translation_languages import translatorEngine, translation_lang # Translator class Translator(): def __init__(self): - self.translator_status = {} - for translator in translatorEngine: - self.translator_status[translator] = False - self.deepl_client = None - - def authentication(self, translator_name, authkey=None): - result = False - if translator_name == "DeepL(web)": - self.translator_status[translator_name] = True - result = True - elif translator_name == "DeepL(auth)": - try: - self.deepl_client = deepl_Translator(authkey) - self.deepl_client.translate_text(" ", target_lang="EN-US") - self.translator_status[translator_name] = True - result = True - except: - self.translator_status[translator_name] = False - elif translator_name == "Google(web)": - self.translator_status[translator_name] = True - result = True - elif translator_name == "Bing(web)": - self.translator_status[translator_name] = True - result = True - return result + pass def translate(self, translator_name, source_language, target_language, message): - result = "" try: + result = "" source_language=translation_lang[translator_name]["source"][source_language] target_language=translation_lang[translator_name]["target"][target_language] - if translator_name == "DeepL(web)": - result = deepl_web_Translator( - source_language=source_language, - target_language=target_language, - text=message - ) - elif translator_name == "DeepL(auth)": - result = self.deepl_client.translate_text( - message, - source_lang=source_language, - target_lang=target_language, - ).text - elif translator_name == "Google(web)": - result = other_web_Translator( - query_text=message, - translator="google", - from_language=source_language, - to_language=target_language, - ) - elif translator_name == "Bing(web)": - result = other_web_Translator( - query_text=message, - translator="bing", - from_language=source_language, - to_language=target_language, - ) + match translator_name: + case "DeepL": + result = deepl_web_Translator( + source_language=source_language, + target_language=target_language, + text=message + ) + case "Google": + result = other_web_Translator( + query_text=message, + translator="google", + from_language=source_language, + to_language=target_language, + ) + case "Bing": + result = other_web_Translator( + query_text=message, + translator="bing", + from_language=source_language, + to_language=target_language, + ) except: pass return result \ No newline at end of file diff --git a/view.py b/view.py index ba07425a..56f9f49d 100644 --- a/view.py +++ b/view.py @@ -198,7 +198,7 @@ class View(): VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value=i18n.t("config_window.deepl_auth_key.label")), VAR_DESC_DEEPL_AUTH_KEY=None, CALLBACK_SET_DEEPL_AUTH_KEY=None, - VAR_DEEPL_AUTH_KEY=StringVar(value=config.AUTH_KEYS["DeepL(auth)"]), + VAR_DEEPL_AUTH_KEY=StringVar(value=""), # Transcription Tab (Mic) From 7e416406f2d9cb75f73e712732ac9d49ac919a4f Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Mon, 16 Oct 2023 00:15:05 +0900 Subject: [PATCH 313/355] =?UTF-8?q?[Remove]=20Model=20:=20DeepL=E3=81=AEpa?= =?UTF-8?q?ckage=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/translation/translation_translator.py | 1 - requirements.txt | 1 - 2 files changed, 2 deletions(-) diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index f23941e5..872df21c 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -1,4 +1,3 @@ -from deepl import Translator as deepl_Translator from deepl_translate import translate as deepl_web_Translator from translators import translate_text as other_web_Translator from .translation_languages import translatorEngine, translation_lang diff --git a/requirements.txt b/requirements.txt index a399733d..386f2860 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,7 +2,6 @@ pillow == 10.0.0 PyAudioWPatch == 0.2.12.6 python-osc == 1.8.3 customtkinter == 5.2.0 -deepl == 1.15.0 flashtext == 2.7 pyyaml == 6.0.1 python-i18n == 0.3.9 \ No newline at end of file From 16cac0d557dbae5731663f2fcb96d1a14fbca9a3 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 16 Oct 2023 10:09:59 +0900 Subject: [PATCH 314/355] =?UTF-8?q?[bugfix]=20Main=20Window:=20Main=20Wind?= =?UTF-8?q?ow=20Cover:=20UI=E5=B4=A9=E5=A3=8A=E4=BF=AE=E6=AD=A3=E7=B6=9A?= =?UTF-8?q?=E3=81=8D=E3=80=82SetProcessDpiAwareness=201=20->=200=20[bugfix?= =?UTF-8?q?]=20=E8=A8=AD=E5=AE=9A=E7=94=BB=E9=9D=A2=E3=82=92=E9=96=8B?= =?UTF-8?q?=E3=81=84=E3=81=9F=E6=99=82=E3=81=AB=E3=82=AB=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=81=99=E3=82=8BWindow=E3=81=8C=E3=81=9A=E3=82=8C=E3=82=8B?= =?UTF-8?q?=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=81=99=E3=82=8B=E3=81=9F?= =?UTF-8?q?=E3=82=81=E3=80=81=E3=83=A1=E3=82=A4=E3=83=B3=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E3=82=92=E7=A7=BB=E5=8B=95=E3=81=97=E3=81=9F=E6=99=82=E3=81=AB?= =?UTF-8?q?=E6=AF=8E=E5=9B=9Elift=EF=BC=88=E6=9C=80=E5=89=8D=E9=9D=A2?= =?UTF-8?q?=E3=81=AB=E8=A1=A8=E7=A4=BA=EF=BC=89=E3=81=99=E3=82=8B=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=82=92=E5=89=8A=E9=99=A4=E3=80=82=20[Update]=20Main?= =?UTF-8?q?=20Window:=20=E3=82=A6=E3=82=A3=E3=83=B3=E3=83=89=E3=82=A6?= =?UTF-8?q?=E3=82=B5=E3=82=A4=E3=82=BA=E3=81=AE=E5=B9=85=E3=82=92=E7=B8=AE?= =?UTF-8?q?=E3=82=81=E3=81=9F=E6=99=82=E3=81=AB=E3=80=81=E3=83=86=E3=82=AD?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=83=9C=E3=83=83=E3=82=AF=E3=82=B9=E3=81=AE?= =?UTF-8?q?=E3=82=B5=E3=82=A4=E3=82=BA=E3=81=8C=E4=B8=80=E5=AE=9A=E4=BB=A5?= =?UTF-8?q?=E4=B8=8A=E7=B8=AE=E3=81=BE=E3=81=9A=E3=80=81=E3=83=AD=E3=82=B0?= =?UTF-8?q?=E3=81=8C=E6=8A=98=E3=82=8A=E8=BF=94=E3=81=95=E3=82=8C=E3=81=AA?= =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=9F=E3=81=AE=E3=81=A7=E3=80=81=E6=8A=98?= =?UTF-8?q?=E3=82=8A=E8=BF=94=E3=81=95=E3=82=8C=E3=82=8B=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E6=94=B9=E5=96=84=E3=80=82=E3=81=9D=E3=82=8C=E3=81=AB?= =?UTF-8?q?=E3=82=88=E3=82=8A=E5=B9=85=E5=9B=BA=E5=AE=9A=E3=82=92=E5=A4=96?= =?UTF-8?q?=E3=81=97=E3=81=9F=E3=81=AE=E3=81=A7=E3=80=81=E8=B5=B7=E5=8B=95?= =?UTF-8?q?=E6=99=82=E3=81=ABgeometry=E3=82=92=E6=8C=87=E5=AE=9A=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=81=AA=E3=81=A9=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B?= =?UTF-8?q?=E3=80=82=20=E2=80=BB=E4=B8=8A=E8=A8=98=E7=9B=B8=E4=BA=92?= =?UTF-8?q?=E3=81=AB=E5=BD=B1=E9=9F=BF=E3=81=97=E3=81=A6=E3=81=84=E3=82=8B?= =?UTF-8?q?=E3=81=9F=E3=82=81=E3=81=BE=E3=81=A8=E3=82=81=E3=81=A6=E7=B7=A8?= =?UTF-8?q?=E9=9B=86=E3=81=97=E3=81=BE=E3=81=97=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 2 +- .../main_window/createMainWindowWidgets.py | 12 +++++++++--- .../main_window/widgets/create_sidebar.py | 6 +++--- vrct_gui/ui_managers/UiScalingManager.py | 1 + vrct_gui/vrct_gui.py | 19 +++++-------------- 5 files changed, 19 insertions(+), 21 deletions(-) diff --git a/main.py b/main.py index 12994131..2c68e5d2 100644 --- a/main.py +++ b/main.py @@ -1,5 +1,5 @@ import ctypes -ctypes.windll.shcore.SetProcessDpiAwareness(1) +ctypes.windll.shcore.SetProcessDpiAwareness(0) from vrct_gui.splash_window import SplashWindow splash = SplashWindow() diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index c42ba7e5..94498bfa 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -11,17 +11,23 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): vrct_gui.iconbitmap(getImagePath("vrct_logo_mark_black.ico")) vrct_gui.title("VRCT") - vrct_gui.minsize(200, 200) + # vrct_gui.minsize(200, 200) # Main Container - vrct_gui.grid_columnconfigure(1, weight=1, minsize=settings.uism.MAIN_AREA_MIN_WIDTH) + vrct_gui.grid_columnconfigure(0, weight=1) + vrct_gui.grid_rowconfigure(0, weight=1) + # vrct_gui.grid_columnconfigure(0, weight=1, minsize=settings.uism.MAIN_AREA_MIN_WIDTH) vrct_gui.configure(fg_color="#ff7f50") + vrct_gui.toplevel_wrapper = CTkFrame(vrct_gui, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) + vrct_gui.toplevel_wrapper.grid(row=0, column=0, sticky="nsew") + vrct_gui.toplevel_wrapper.grid_columnconfigure(1, weight=1) + # Main Container - vrct_gui.main_bg_container = CTkFrame(vrct_gui, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) + vrct_gui.main_bg_container = CTkFrame(vrct_gui.toplevel_wrapper, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) vrct_gui.main_bg_container.grid(row=0, column=1, sticky="nsew") diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index 6e7fe42f..ac6efe14 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -6,9 +6,9 @@ from ._create_sidebar import createSidebarFeatures, createSidebarLanguagesSettin def createSidebar(settings, main_window, view_variable): # Side Bar Container - main_window.grid_rowconfigure(0, weight=1) + main_window.toplevel_wrapper.grid_rowconfigure(0, weight=1) - main_window.sidebar_bg_container_wrapper = CTkFrame(main_window, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) + main_window.sidebar_bg_container_wrapper = CTkFrame(main_window.toplevel_wrapper, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) main_window.sidebar_bg_container_wrapper.grid(row=0, column=0, sticky="nsew") @@ -16,7 +16,7 @@ def createSidebar(settings, main_window, view_variable): main_window.sidebar_compact_mode_bg_container = CTkFrame(main_window.sidebar_bg_container_wrapper, corner_radius=0, fg_color=settings.ctm.SIDEBAR_BG_COLOR, width=0, height=0) - main_window.sidebar_bg_container.grid_columnconfigure(0, weight=1) + main_window.sidebar_bg_container.grid_columnconfigure(0, weight=1, minsize=settings.uism.SIDEBAR_MIN_WIDTH) main_window.sidebar_compact_mode_bg_container.grid_columnconfigure(0, weight=1) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index a7e581ce..56d5cafd 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -29,6 +29,7 @@ class UiScalingManager(): # Main self.main.MAIN_AREA_MIN_WIDTH = self._calculateUiSize(640) + self.main.SIDEBAR_MIN_WIDTH = self._calculateUiSize(230) self.main.TEXTBOX_PADX = self._calculateUiSize(16) self.main.TEXTBOX_CORNER_RADIUS = self._calculateUiSize(6) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index fae93026..ae3b8fcb 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -19,7 +19,6 @@ class VRCT_GUI(CTk): def __init__(self): super().__init__() self.withdraw() - self.adjusted_event=None self.is_config_window_already_opened_once=False self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID=None self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID=None @@ -45,6 +44,10 @@ class VRCT_GUI(CTk): def _showGUI(self): self.attributes("-alpha", 0) self.deiconify() + self.geometry("{}x{}".format( + self.settings.main.uism.MAIN_AREA_MIN_WIDTH + self.settings.main.uism.SIDEBAR_MIN_WIDTH, + self.winfo_height() + )) setGeometryToCenterOfScreen(root_widget=self) if self._view_variable.IS_MAIN_WINDOW_SIDEBAR_COMPACT_MODE is True: self._enableMainWindowSidebarCompactMode() @@ -94,7 +97,7 @@ class VRCT_GUI(CTk): ) self.main_window_cover = _CreateWindowCover( - attach_window=self, + attach_window=self.toplevel_wrapper, settings=self.settings.main_window_cover, view_variable=self._view_variable ) @@ -266,18 +269,6 @@ class VRCT_GUI(CTk): self.main_window_cover.geometry("{}x{}+{}+{}".format(width_new, height_new, x_pos, y_pos)) self.main_window_cover.lift() - if self.adjusted_event == str(e): - self.after(150, lambda: self.config_window.lift()) - elif self.adjusted_event is None: - self.after(150, lambda: self.config_window.lift()) - else: - pass - - self.config_window.focus_set() - - if e is not None: - self.adjusted_event=str(e) - def _showErrorMessage(self, target_widget): self.error_message_window.show(target_widget=target_widget) From 91cc9802a3502edf1af29943d9bb07f5bee800a6 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 16 Oct 2023 10:29:25 +0900 Subject: [PATCH 315/355] [Chore] Remove the code ctypes from requirements.txt. --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 9e7cc4e6..386f2860 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,5 +4,4 @@ python-osc == 1.8.3 customtkinter == 5.2.0 flashtext == 2.7 pyyaml == 6.0.1 -python-i18n == 0.3.9 -ctypes \ No newline at end of file +python-i18n == 0.3.9 \ No newline at end of file From 0519a2bbfba6a584da4661ca24abe207ade3b0f6 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 16 Oct 2023 10:58:38 +0900 Subject: [PATCH 316/355] =?UTF-8?q?[Remove]=20DeepL=20Auth=E3=82=AD?= =?UTF-8?q?=E3=83=BC=E5=BB=83=E6=AD=A2=E3=81=AB=E3=82=88=E3=82=8AUI?= =?UTF-8?q?=E3=81=8B=E3=82=89=E5=89=8A=E9=99=A4=E3=80=82Translation?= =?UTF-8?q?=E3=82=BF=E3=83=96=E8=87=AA=E4=BD=93=E3=81=AF=E3=82=B3=E3=83=A1?= =?UTF-8?q?=E3=83=B3=E3=83=88=E3=82=A2=E3=82=A6=E3=83=88=E3=81=A7=E5=89=8A?= =?UTF-8?q?=E9=99=A4=EF=BC=88=E5=B0=86=E6=9D=A5=E7=9A=84=E3=81=AB=E3=81=BE?= =?UTF-8?q?=E3=81=9F=E5=BF=85=E8=A6=81=E3=81=AB=E3=81=AA=E3=82=8B=E3=81=A8?= =?UTF-8?q?=E4=BA=88=E6=83=B3=E3=81=97=E3=81=A6=EF=BC=89=E3=80=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 6 ++--- locales/ja.yml | 6 ++--- view.py | 13 +++++----- .../createSideMenuAndSettingsBoxContainers.py | 26 +++++++++---------- .../createSettingBox_Translation.py | 20 +------------- 5 files changed, 27 insertions(+), 44 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 122b7edf..bf153767 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -55,7 +55,7 @@ config_window: restart_message: Apply changes with a restart. side_menu_labels: appearance: Appearance - translation: Translation + # translation: Translation transcription: Transcription transcription_mic: Mic transcription_speaker: Speaker @@ -75,8 +75,8 @@ config_window: ui_language: label: UI Language - deepl_auth_key: - label: DeepL Auth Key + # deepl_auth_key: + # label: DeepL Auth Key mic_host: label: Mic Host/Driver diff --git a/locales/ja.yml b/locales/ja.yml index 9f4d3d74..a823c097 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -55,7 +55,7 @@ config_window: restart_message: 再起動して変更を適用する。 side_menu_labels: appearance: デザイン - translation: 翻訳 + # translation: 翻訳 transcription: 音声認識 transcription_mic: マイク transcription_speaker: スピーカー @@ -75,8 +75,8 @@ config_window: ui_language: label: UIの言語 / UI Language - deepl_auth_key: - label: DeepL 認証キー + # deepl_auth_key: + # label: DeepL 認証キー mic_host: label: マイク(ホスト/ドライバー) diff --git a/view.py b/view.py index abcd6b38..442c66e2 100644 --- a/view.py +++ b/view.py @@ -158,6 +158,7 @@ class View(): VAR_CURRENT_ACTIVE_CONFIG_TITLE=StringVar(value=""), + # Appearance Tab VAR_LABEL_TRANSPARENCY=StringVar(value=i18n.t("config_window.transparency.label")), VAR_DESC_TRANSPARENCY=StringVar(value=i18n.t("config_window.transparency.desc")), SLIDER_RANGE_TRANSPARENCY=(50, 100), @@ -193,11 +194,11 @@ class View(): VAR_UI_LANGUAGE=StringVar(value=selectable_languages[config.UI_LANGUAGE]), - - VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value=i18n.t("config_window.deepl_auth_key.label")), - VAR_DESC_DEEPL_AUTH_KEY=None, - CALLBACK_SET_DEEPL_AUTH_KEY=None, - VAR_DEEPL_AUTH_KEY=StringVar(value=""), + # Translation Tab + # VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value=i18n.t("config_window.deepl_auth_key.label")), + # VAR_DESC_DEEPL_AUTH_KEY=None, + # CALLBACK_SET_DEEPL_AUTH_KEY=None, + # VAR_DEEPL_AUTH_KEY=StringVar(value=""), # Transcription Tab (Mic) @@ -413,7 +414,7 @@ class View(): # Translation Tab - self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window_registers.get("callback_set_deepl_authkey", None) + # self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window_registers.get("callback_set_deepl_authkey", None) # Transcription Tab (Mic) self.view_variable.CALLBACK_SET_MIC_HOST = config_window_registers.get("callback_set_mic_host", None) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index f9694a1a..5fe0da61 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -10,7 +10,7 @@ from .setting_box_containers.setting_box_appearance import createSettingBox_Appe from .setting_box_containers.setting_box_transcription import createSettingBox_Mic, createSettingBox_Speaker from .setting_box_containers.setting_box_others import createSettingBox_Others from .setting_box_containers.setting_box_advanced_settings import createSettingBox_AdvancedSettings -from .setting_box_containers.setting_box_translation import createSettingBox_Translation +# from .setting_box_containers.setting_box_translation import createSettingBox_Translation def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variable): @@ -66,18 +66,18 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl ] }, }, - { - "side_menu_tab_attr_name": "side_menu_tab_translation", - "label_attr_name": "label_translation", - "selected_mark_attr_name": "selected_mark_translation", - "textvariable": view_variable.VAR_SIDE_MENU_LABEL_TRANSLATION, - "setting_box_container_settings": { - "setting_box_container_attr_name": "setting_box_container_translation", - "setting_boxes": [ - { "var_section_title": None, "setting_box": createSettingBox_Translation }, - ] - }, - }, + # { + # "side_menu_tab_attr_name": "side_menu_tab_translation", + # "label_attr_name": "label_translation", + # "selected_mark_attr_name": "selected_mark_translation", + # "textvariable": view_variable.VAR_SIDE_MENU_LABEL_TRANSLATION, + # "setting_box_container_settings": { + # "setting_box_container_attr_name": "setting_box_container_translation", + # "setting_boxes": [ + # { "var_section_title": None, "setting_box": createSettingBox_Translation }, + # ] + # }, + # }, { "side_menu_tab_attr_name": "side_menu_tab_transcription", "label_attr_name": "label_transcription", diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index d2975d0d..461b4aea 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -3,22 +3,4 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Translation(setting_box_wrapper, config_window, settings, view_variable): - sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) - createSettingBoxEntry = sbg.createSettingBoxEntry - - - def deepl_authkey_callback(value): - callFunctionIfCallable(view_variable.CALLBACK_SET_DEEPL_AUTHKEY, value) - - - row=0 - config_window.sb__deepl_authkey = createSettingBoxEntry( - for_var_label_text=view_variable.VAR_LABEL_DEEPL_AUTH_KEY, - for_var_desc_text=view_variable.VAR_DESC_DEEPL_AUTH_KEY, - entry_attr_name="sb__entry_deepl_authkey", - entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_300, - entry_bind__Any_KeyRelease=lambda value: deepl_authkey_callback(value), - entry_textvariable=view_variable.VAR_DEEPL_AUTH_KEY, - ) - config_window.sb__deepl_authkey.grid(row=row, pady=0) - row+=1 \ No newline at end of file + pass \ No newline at end of file From a054f05b1b9bd27b79c5f04eb8d793f002c3c76d Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 16 Oct 2023 11:40:57 +0900 Subject: [PATCH 317/355] =?UTF-8?q?[Remove]=20Config=20Window:=20Speaker?= =?UTF-8?q?=20Device=E8=87=AA=E5=8B=95=E8=A8=AD=E5=AE=9A=E3=81=AB=E3=82=88?= =?UTF-8?q?=E3=82=8A=E3=80=81UI=E3=81=8B=E3=82=89=E9=81=B8=E6=8A=9E?= =?UTF-8?q?=E9=A0=85=E7=9B=AE=E3=82=92=E5=89=8A=E9=99=A4=E3=80=82=E3=81=BE?= =?UTF-8?q?=E3=81=9F=E3=80=81config.CHOICE=5FSPEAKER=5FDEVICE=20=E3=81=8C?= =?UTF-8?q?=20"NoDevice"=E3=81=A0=E3=81=A3=E3=81=9F=E3=81=A8=E3=81=8D?= =?UTF-8?q?=E3=81=AE=E5=88=9D=E6=9C=9F=E5=87=A6=E7=90=86=E3=82=82=E5=BF=85?= =?UTF-8?q?=E8=A6=81=E3=81=AA=E3=81=8F=E3=81=AA=E3=81=A3=E3=81=9F=E3=81=AE?= =?UTF-8?q?=E3=81=A7view.py=E3=81=8B=E3=82=89=E3=82=82=E5=89=8A=E9=99=A4?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 2 -- locales/en.yml | 3 +- locales/ja.yml | 3 +- view.py | 33 +------------------ vrct_gui/_changeConfigWindowWidgetsStatus.py | 5 --- .../createSettingBox_Speaker.py | 16 --------- 6 files changed, 3 insertions(+), 59 deletions(-) diff --git a/controller.py b/controller.py index cd77fd76..e013a425 100644 --- a/controller.py +++ b/controller.py @@ -672,8 +672,6 @@ def createMainWindow(): "callback_set_mic_word_filter": callbackSetMicWordFilter, # Transcription Tab (Speaker) - # "callback_set_speaker_device": callbackSetSpeakerDevice, - "list_speaker_device": model.getListOutputDevice(), "callback_set_speaker_energy_threshold": callbackSetSpeakerEnergyThreshold, "callback_set_speaker_dynamic_energy_threshold": callbackSetSpeakerDynamicEnergyThreshold, "callback_check_speaker_threshold": callbackCheckSpeakerThreshold, diff --git a/locales/en.yml b/locales/en.yml index bf153767..85c2b5d7 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -102,8 +102,7 @@ config_window: label: Mic Word Filter desc: "It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC" - speaker_device: - label: Speaker Device + speaker_dynamic_energy_threshold: label_for_automatic: "Speaker Energy Threshold (Current Setting: Automatic)" desc_for_automatic: "Automatically determine speaker input sensitivity." diff --git a/locales/ja.yml b/locales/ja.yml index a823c097..6b6a5705 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -102,8 +102,7 @@ config_window: label: ワードフィルター desc: "設定された単語を検出すると、その文章は送信されません。\n設定の例: AAA,BBB,CCC" - speaker_device: - label: スピーカー(デバイス) + speaker_dynamic_energy_threshold: label_for_automatic: "スピーカー入力感度の調整 (現在の設定: 自動)" desc_for_automatic: スピーカーの入力感度を自動的に調節する。 diff --git a/view.py b/view.py index 442c66e2..a46018b0 100644 --- a/view.py +++ b/view.py @@ -253,13 +253,6 @@ class View(): # Transcription Tab (Speaker) - VAR_LABEL_SPEAKER_DEVICE=StringVar(value=i18n.t("config_window.speaker_device.label")), - VAR_DESC_SPEAKER_DEVICE=None, - LIST_SPEAKER_DEVICE=[], - CALLBACK_SET_SPEAKER_DEVICE=None, - VAR_SPEAKER_DEVICE=StringVar(value=config.CHOICE_SPEAKER_DEVICE), - - VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=""), VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=StringVar(value=""), CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD=None, @@ -432,9 +425,6 @@ class View(): self.view_variable.CALLBACK_SET_MIC_WORD_FILTER = config_window_registers.get("callback_set_mic_word_filter", None) # Transcription Tab (Speaker) - self.view_variable.CALLBACK_SET_SPEAKER_DEVICE = config_window_registers.get("callback_set_speaker_device", None) - config_window_registers.get("list_speaker_device", None) and self.updateList_SpeakerDevice(config_window_registers["list_speaker_device"]) - self.view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD = config_window_registers.get("callback_set_speaker_energy_threshold", None) self.view_variable.CALLBACK_SET_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = config_window_registers.get("callback_set_speaker_dynamic_energy_threshold", None) self.view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD = config_window_registers.get("callback_check_speaker_threshold", None) @@ -484,17 +474,6 @@ class View(): ) self.replaceMicThresholdCheckButton_Disabled() - if config.CHOICE_SPEAKER_DEVICE == "NoDevice": - self.view_variable.VAR_SPEAKER_DEVICE.set("No Speaker Device Detected") - vrct_gui._changeConfigWindowWidgetsStatus( - status="disabled", - target_names=[ - "sb__optionmenu_speaker_device", - ] - ) - self.replaceSpeakerThresholdCheckButton_Disabled() - - if config.INPUT_MIC_DYNAMIC_ENERGY_THRESHOLD is True: self.closeMicEnergyThresholdWidget() @@ -789,10 +768,7 @@ class View(): def initSpeakerThresholdCheckButton(self): - if config.CHOICE_SPEAKER_DEVICE == "NoDevice": - self.replaceSpeakerThresholdCheckButton_Disabled() - else: - self.replaceSpeakerThresholdCheckButton_Passive() + self.replaceSpeakerThresholdCheckButton_Passive() @staticmethod def replaceSpeakerThresholdCheckButton_Active(): @@ -844,13 +820,6 @@ class View(): vrct_gui.config_window.sb__progressbar_x_slider__progressbar_mic_energy_threshold.set(0) - def updateList_SpeakerDevice(self, new_speaker_device_list:list): - self.view_variable.LIST_SPEAKER_DEVICE = new_speaker_device_list - vrct_gui.dropdown_menu_window.updateDropdownMenuValues( - dropdown_menu_widget_id="sb__optionmenu_speaker_device", - dropdown_menu_values=new_speaker_device_list, - ) - @staticmethod def updateSetProgressBar_SpeakerEnergy(new_speaker_energy): vrct_gui.config_window.sb__progressbar_x_slider__progressbar_speaker_energy_threshold.set(new_speaker_energy/config.MAX_SPEAKER_ENERGY_THRESHOLD) diff --git a/vrct_gui/_changeConfigWindowWidgetsStatus.py b/vrct_gui/_changeConfigWindowWidgetsStatus.py index 0263837b..95ebb187 100644 --- a/vrct_gui/_changeConfigWindowWidgetsStatus.py +++ b/vrct_gui/_changeConfigWindowWidgetsStatus.py @@ -27,11 +27,6 @@ def _changeConfigWindowWidgetsStatus(config_window, settings, view_variable, sta target_widget = config_window.sb__widgets["sb__optionmenu_mic_device"] disableOptionmenuWidget(target_widget) - case "sb__optionmenu_speaker_device": - if status == "disabled": - target_widget = config_window.sb__widgets["sb__optionmenu_speaker_device"] - disableOptionmenuWidget(target_widget) - case "sb__optionmenu_appearance_theme": if status == "disabled": target_widget = config_window.sb__widgets["sb__optionmenu_appearance_theme"] diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py index 64ec1854..a9d1ad9b 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Speaker.py @@ -4,7 +4,6 @@ from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_variable): sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) - createSettingBoxDropdownMenu = sbg.createSettingBoxDropdownMenu createSettingBoxSwitch = sbg.createSettingBoxSwitch createSettingBoxProgressbarXSlider = sbg.createSettingBoxProgressbarXSlider createSettingBoxEntry = sbg.createSettingBoxEntry @@ -14,9 +13,6 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ callFunctionIfCallable(view_variable.CALLBACK_CHECK_SPEAKER_THRESHOLD, is_turned_on) - def optionmenu_input_speaker_device_callback(value): - callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_DEVICE, value) - def slider_input_speaker_energy_threshold_callback(value): callFunctionIfCallable(view_variable.CALLBACK_SET_SPEAKER_ENERGY_THRESHOLD, value) @@ -36,18 +32,6 @@ def createSettingBox_Speaker(setting_box_wrapper, config_window, settings, view_ row=0 - config_window.sb__speaker_device = createSettingBoxDropdownMenu( - for_var_label_text=view_variable.VAR_LABEL_SPEAKER_DEVICE, - for_var_desc_text=view_variable.VAR_DESC_SPEAKER_DEVICE, - optionmenu_attr_name="sb__optionmenu_speaker_device", - dropdown_menu_values=view_variable.LIST_SPEAKER_DEVICE, - dropdown_menu_width=settings.uism.RESPONSIVE_UI_SIZE_INT_500, - command=lambda value: optionmenu_input_speaker_device_callback(value), - variable=view_variable.VAR_SPEAKER_DEVICE, - ) - config_window.sb__speaker_device.grid(row=row) - row+=1 - config_window.sb__speaker_dynamic_energy_threshold = createSettingBoxSwitch( for_var_label_text=view_variable.VAR_LABEL_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, for_var_desc_text=view_variable.VAR_DESC_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, From bd71a96ccb1a660b9c6b76f7fbfda72c10194adf Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 16 Oct 2023 12:34:41 +0900 Subject: [PATCH 318/355] =?UTF-8?q?[Update]=20Main=20Window:=20=E3=83=A1?= =?UTF-8?q?=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=83=9C=E3=83=83=E3=82=AF?= =?UTF-8?q?=E3=82=B9=E3=81=8B=E3=82=89=E3=83=95=E3=82=A9=E3=83=BC=E3=82=AB?= =?UTF-8?q?=E3=82=B9=E3=81=8C=E5=A4=96=E3=82=8C=E3=82=8B=E3=81=A8=E3=80=81?= =?UTF-8?q?VRChat=E4=B8=8A=E3=81=A7=E3=81=A7=E3=82=8B=E3=83=81=E3=83=A3?= =?UTF-8?q?=E3=83=83=E3=83=88=E5=85=A5=E5=8A=9B=E4=B8=AD=E3=82=92stop?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=E3=80=82=20?= =?UTF-8?q?=E3=81=9D=E3=82=8C=E3=81=AB=E4=BC=B4=E3=81=A3=E3=81=A6focus=20i?= =?UTF-8?q?n/out=E3=82=A4=E3=83=99=E3=83=B3=E3=83=88=E3=82=92controller.py?= =?UTF-8?q?=E3=81=A7=E7=AE=A1=E7=90=86=E3=80=82=20[Refactor]=20=E9=96=A2?= =?UTF-8?q?=E6=95=B0=E5=90=8D=E3=82=92=E9=81=A9=E5=88=87=E3=81=AA=E5=90=8D?= =?UTF-8?q?=E5=89=8D=E3=81=B8=E5=A4=89=E6=9B=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 14 ++++++++++++-- view.py | 12 ++++++------ 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/controller.py b/controller.py index e013a425..a53bdc0f 100644 --- a/controller.py +++ b/controller.py @@ -186,6 +186,14 @@ def messageBoxPressKeyAny(e): else: model.oscStopSendTyping() +def messageBoxFocusIn(e): + view.foregroundOffIfForegroundEnabled() + +def messageBoxFocusOut(e): + view.foregroundOnIfForegroundEnabled() + if config.ENABLE_SEND_MESSAGE_TO_VRC is True: + model.oscStopSendTyping() + # func select languages def initSetLanguageAndCountry(): select = config.SELECTED_TAB_YOUR_LANGUAGES[config.SELECTED_TAB_NO] @@ -642,8 +650,10 @@ def createMainWindow(): "values": model.getListLanguageAndCountry(), "callback_selected_language_preset_tab": callbackSelectedLanguagePresetTab, - "bind_Return": messageBoxPressKeyEnter, - "bind_Any_KeyPress": messageBoxPressKeyAny, + "message_box_bind_Return": messageBoxPressKeyEnter, + "message_box_bind_Any_KeyPress": messageBoxPressKeyAny, + "message_box_bind_FocusIn": messageBoxFocusIn, + "message_box_bind_FocusOut": messageBoxFocusOut, }, config_window_registers={ diff --git a/view.py b/view.py index a46018b0..27b38138 100644 --- a/view.py +++ b/view.py @@ -372,12 +372,12 @@ class View(): entry_message_box = getattr(vrct_gui, "entry_message_box") - entry_message_box.bind("", main_window_registers.get("bind_Return")) - entry_message_box.bind("", main_window_registers.get("bind_Any_KeyPress")) + entry_message_box.bind("", main_window_registers.get("message_box_bind_Return")) + entry_message_box.bind("", main_window_registers.get("message_box_bind_Any_KeyPress")) - entry_message_box.bind("", self._foregroundOffForcefully) - entry_message_box.bind("", self._foregroundOnForcefully) + entry_message_box.bind("", main_window_registers.get("message_box_bind_FocusIn")) + entry_message_box.bind("", main_window_registers.get("message_box_bind_FocusOut")) self.updateGuiVariableByPresetTabNo(config.SELECTED_TAB_NO) @@ -523,11 +523,11 @@ class View(): - def _foregroundOnForcefully(self, _e): + def foregroundOnIfForegroundEnabled(self): if config.ENABLE_FOREGROUND: self.foregroundOn() - def _foregroundOffForcefully(self, _e): + def foregroundOffIfForegroundEnabled(self): if config.ENABLE_FOREGROUND: self.foregroundOff() From 29b30bba92c10ec479bb7ac862133cb8ff4d8766 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 16 Oct 2023 20:00:31 +0900 Subject: [PATCH 319/355] [Chore] Remove the code that is useless. and change the function getLongestText for somehow. --- vrct_gui/_CreateDropdownMenuWindow.py | 6 ------ vrct_gui/ui_utils/ui_utils.py | 11 +++++------ 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index b4a64775..43ce52f8 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -132,12 +132,6 @@ class _CreateDropdownMenuWindow(CTkToplevel): - - self.dropdown_menu_values_box = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.window_bg_color, width=0, height=0) - self.dropdown_menu_values_box.grid(row=0, column=0, sticky="nsew") - self.dropdown_menu_values_box.grid_columnconfigure(0, weight=1) - - self._createDropdownMenuValues(dropdown_menu_widget_id, dropdown_menu_values, command) applyUiScalingAndFixTheBugScrollBar( diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index 03c963a8..e1d4886a 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -32,15 +32,14 @@ def getLatestHeight(target_widget): target_widget.update_idletasks() return target_widget.winfo_height() -def getLongestText(settings): - max_length = max(len(item["text"]) for item in settings) +def getLongestText(text_list:list): max_length = 0 longest_text = "" - for item in settings: - if len(item["text"]) > max_length: - max_length = len(item["text"]) - longest_text = item["text"] + for text in text_list: + if len(text) > max_length: + max_length = len(text) + longest_text = text return longest_text def bindEnterAndLeaveColor(target_widgets, enter_color, leave_color): From 6714833b0fbaf94aa94c0d38d04a93824a20939a Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Mon, 16 Oct 2023 20:49:39 +0900 Subject: [PATCH 320/355] =?UTF-8?q?[Update]=20Config=20Window:=20showResta?= =?UTF-8?q?rtButton=E9=96=A2=E6=95=B0=E3=81=AE=E8=AA=BF=E6=95=B4=E3=80=82?= =?UTF-8?q?=E5=89=8D=E5=9B=9E=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=81=A8=E5=90=8C?= =?UTF-8?q?=E3=81=98=E3=81=A7=E3=80=81=E5=86=8D=E8=B5=B7=E5=8B=95=E3=81=8C?= =?UTF-8?q?=E5=BF=85=E8=A6=81=E3=81=AA=E3=81=91=E3=82=8C=E3=81=B0=E5=86=8D?= =?UTF-8?q?=E8=B5=B7=E5=8B=95=E3=83=9C=E3=82=BF=E3=83=B3=E3=82=92=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=97=E3=81=AA=E3=81=84=E3=82=88=E3=81=86=E3=81=AB?= =?UTF-8?q?=E3=80=82=E9=81=B8=E6=8A=9E=E4=B8=AD=E3=81=AE=E8=A8=80=E8=AA=9E?= =?UTF-8?q?=E3=81=A7=E3=81=AE=E8=A1=A8=E7=A4=BA=E3=82=82=E5=AF=BE=E5=BF=9C?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 8 ++++---- view.py | 29 +++++++++++++++++++++++++++-- 2 files changed, 31 insertions(+), 6 deletions(-) diff --git a/controller.py b/controller.py index a53bdc0f..bc0f0f68 100644 --- a/controller.py +++ b/controller.py @@ -340,26 +340,26 @@ def callbackSetTransparency(value): def callbackSetAppearance(value): print("callbackSetAppearance", value) config.APPEARANCE_THEME = value - view.showRestartButton() + view.showRestartButtonIfRequired() def callbackSetUiScaling(value): print("callbackSetUiScaling", value) config.UI_SCALING = value new_scaling_float = int(value.replace("%", "")) / 100 print("callbackSetUiScaling_new_scaling_float", new_scaling_float) - view.showRestartButton() + view.showRestartButtonIfRequired() def callbackSetFontFamily(value): print("callbackSetFontFamily", value) config.FONT_FAMILY = value - view.showRestartButton() + view.showRestartButtonIfRequired() def callbackSetUiLanguage(value): print("callbackSetUiLanguage", value) value = get_key_by_value(selectable_languages, value) print("callbackSetUiLanguage__after_get_key_by_value", value) config.UI_LANGUAGE = value - view.showRestartButton(locale=config.UI_LANGUAGE) + view.showRestartButtonIfRequired(locale=config.UI_LANGUAGE) # Transcription Tab (Mic) def callbackSetMicHost(value): diff --git a/view.py b/view.py index 27b38138..29b0c046 100644 --- a/view.py +++ b/view.py @@ -30,6 +30,14 @@ class View(): i18n.set("locale", config.UI_LANGUAGE) + self.restart_required_configs_pre_data = SimpleNamespace( + appearance_theme=config.APPEARANCE_THEME, + ui_scaling=config.UI_SCALING, + font_family=config.FONT_FAMILY, + ui_language=config.UI_LANGUAGE, + ) + + common_args = { "image_file": image_file, "FONT_FAMILY": config.FONT_FAMILY, @@ -698,10 +706,27 @@ class View(): # Config Window - def showRestartButton(self, locale:Union[None,str]=None): + def showRestartButtonIfRequired(self, locale:Union[None,str]=None): + is_restart_required = not ( + self.restart_required_configs_pre_data.appearance_theme == config.APPEARANCE_THEME and + self.restart_required_configs_pre_data.ui_scaling == config.UI_SCALING and + self.restart_required_configs_pre_data.font_family == config.FONT_FAMILY and + self.restart_required_configs_pre_data.ui_language == config.UI_LANGUAGE + ) + + if locale is None: + locale = config.UI_LANGUAGE + + if is_restart_required is True: + self._showRestartButton(locale) + else: + self._hideRestartButton() + + + def _showRestartButton(self, locale:Union[None,str]=None): self.view_variable.VAR_CONFIG_WINDOW_RESTART_BUTTON_LABEL.set(i18n.t("config_window.restart_message", locale=locale)) vrct_gui.config_window.restart_button_container.grid() - def hideRestartButton(self): + def _hideRestartButton(self): vrct_gui.config_window.restart_button_container.grid_remove() def _updateActiveSettingBoxTabNo(self, active_setting_box_tab_attr_name:str): From 8ebfb99401ec1cf9989e24f7e6c238d20223ed43 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 17 Oct 2023 04:01:01 +0900 Subject: [PATCH 321/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Config=20:=20NoD?= =?UTF-8?q?evice=E6=99=82=E3=81=ABCHOICE=5FSPEAKER=5FDEVICE=E3=82=92?= =?UTF-8?q?=E3=82=BB=E3=83=83=E3=83=88=E3=81=99=E3=82=8B=E3=81=A8=E3=82=A8?= =?UTF-8?q?=E3=83=A9=E3=83=BC=E3=81=AB=E3=81=AA=E3=82=8B=E5=95=8F=E9=A1=8C?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/config.py b/config.py index b0fdacc5..030da6bb 100644 --- a/config.py +++ b/config.py @@ -1,5 +1,4 @@ import sys -from json import load, dump import inspect from os import path as os_path from json import load as json_load @@ -7,9 +6,8 @@ from json import dump as json_dump import tkinter as tk from tkinter import font from languages import selectable_languages -from models.translation.translation_languages import translatorEngine, translation_lang -from models.transcription.transcription_languages import transcription_lang -from models.transcription.transcription_utils import getInputDevices, getOutputDevices, getDefaultInputDevice, getDefaultOutputDevice +from models.translation.translation_languages import translatorEngine +from models.transcription.transcription_utils import getInputDevices, getDefaultInputDevice, getDefaultOutputDevice json_serializable_vars = {} def json_serializable(var_name): @@ -20,7 +18,7 @@ def json_serializable(var_name): def saveJson(path, key, value): with open(path, "r", encoding="utf-8") as fp: - json_data = load(fp) + json_data = json_load(fp) json_data[key] = value with open(path, "w", encoding="utf-8") as fp: json_dump(json_data, fp, indent=4, ensure_ascii=False) @@ -346,11 +344,9 @@ class Config: @CHOICE_SPEAKER_DEVICE.setter def CHOICE_SPEAKER_DEVICE(self, value): - if value in [device["name"] for device in getOutputDevices()]: - speaker_device = [device for device in getOutputDevices() if device["name"] == value][0] - if getDefaultOutputDevice()["index"] == speaker_device["index"]: - self._CHOICE_SPEAKER_DEVICE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + if getDefaultOutputDevice()["name"] == value: + self._CHOICE_SPEAKER_DEVICE = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) @property @json_serializable('INPUT_SPEAKER_ENERGY_THRESHOLD') From fa1854f2d333a0106bb5f490c923f87fb82b2fc2 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 17 Oct 2023 04:02:40 +0900 Subject: [PATCH 322/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20NoDe?= =?UTF-8?q?vice=E6=99=82=E3=81=AB=E3=82=A8=E3=83=A9=E3=83=BC=E3=82=92?= =?UTF-8?q?=E8=A1=A8=E7=A4=BA=E3=81=99=E3=82=8B=E3=81=9F=E3=82=81=E3=81=AB?= =?UTF-8?q?error=5Ffnc=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 仮でlambda:print("[ERROR] Speaker NoDevice"を差し込んである --- controller.py | 13 +++++++++++-- model.py | 40 ++++++++++++++++++++++++++-------------- 2 files changed, 37 insertions(+), 16 deletions(-) diff --git a/controller.py b/controller.py index bc0f0f68..52c6af9c 100644 --- a/controller.py +++ b/controller.py @@ -108,7 +108,8 @@ def receiveSpeakerMessage(message): model.logger.info(f"[RECEIVED] {message}{translation}") def startTranscriptionReceiveMessage(): - model.startSpeakerTranscript(receiveSpeakerMessage) + config.CHOICE_SPEAKER_DEVICE = model.getOutputDefaultDevice() + model.startSpeakerTranscript(receiveSpeakerMessage, lambda:print("[ERROR] Speaker NoDevice")) view.setMainWindowAllWidgetsStatusToNormal() def stopTranscriptionReceiveMessage(): @@ -128,8 +129,10 @@ def stopThreadingTranscriptionReceiveMessage(): th_stopTranscriptionReceiveMessage.start() def startTranscriptionReceiveMessageOnCloseConfigWindow(): + config.CHOICE_SPEAKER_DEVICE = model.getOutputDefaultDevice() model.startSpeakerTranscript(receiveSpeakerMessage) + def stopTranscriptionReceiveMessageOnOpenConfigWindow(): model.stopSpeakerTranscript() @@ -509,7 +512,13 @@ def callbackCheckSpeakerThreshold(is_turned_on): print("callbackCheckSpeakerThreshold", is_turned_on) if is_turned_on is True: view.replaceSpeakerThresholdCheckButton_Disabled() - model.startCheckSpeakerEnergy(setProgressBarSpeakerEnergy, view.initProgressBar_SpeakerEnergy) + config.CHOICE_SPEAKER_DEVICE = model.getOutputDefaultDevice() + model.startCheckSpeakerEnergy( + setProgressBarSpeakerEnergy, + view.initProgressBar_SpeakerEnergy, + lambda:print("[ERROR] Speaker NoDevice") + ) + view.replaceSpeakerThresholdCheckButton_Active() else: view.replaceSpeakerThresholdCheckButton_Disabled() diff --git a/model.py b/model.py index 6b8781cc..08e0a528 100644 --- a/model.py +++ b/model.py @@ -14,7 +14,7 @@ import webbrowser from flashtext import KeywordProcessor from models.translation.translation_translator import Translator -from models.transcription.transcription_utils import getInputDevices, getOutputDevices, getDefaultInputDevice, getDefaultOutputDevice +from models.transcription.transcription_utils import getInputDevices, getDefaultOutputDevice from models.osc.osc_tools import sendTyping, sendMessage, sendTestAction, receiveOscParameters from models.transcription.transcription_recorder import SelectedMicRecorder, SelectedSpeakerRecorder from models.transcription.transcription_recorder import SelectedMicEnergyRecorder, SelectedSpeakeEnergyRecorder @@ -259,11 +259,15 @@ class Model: return [device["name"] for device in getInputDevices()[config.CHOICE_MIC_HOST]][0] @staticmethod - def getListOutputDevice(): - return [device["name"] for device in getOutputDevices()] + def getOutputDefaultDevice(): + return getDefaultOutputDevice()["name"] - def startMicTranscript(self, fnc): + def startMicTranscript(self, fnc, error_fnc=None): if config.CHOICE_MIC_HOST == "NoHost" or config.CHOICE_MIC_DEVICE == "NoDevice": + try: + error_fnc() + except: + pass return mic_audio_queue = Queue() @@ -306,8 +310,12 @@ class Model: self.mic_audio_recorder.stop() self.mic_audio_recorder = None - def startCheckMicEnergy(self, fnc, end_fnc): + def startCheckMicEnergy(self, fnc, end_fnc, error_fnc=None): if config.CHOICE_MIC_HOST == "NoHost" or config.CHOICE_MIC_DEVICE == "NoDevice": + try: + error_fnc() + except: + pass return def sendMicEnergy(): @@ -335,20 +343,22 @@ class Model: self.mic_energy_recorder.stop() self.mic_energy_recorder = None - def startSpeakerTranscript(self, fnc): - speaker_device = getDefaultOutputDevice() - config.CHOICE_SPEAKER_DEVICE = speaker_device["name"] + def startSpeakerTranscript(self, fnc, error_fnc=None): if config.CHOICE_SPEAKER_DEVICE == "NoDevice": + try: + error_fnc() + except: + pass return - speaker_audio_queue = Queue() + speaker_audio_queue = Queue() record_timeout = config.INPUT_SPEAKER_RECORD_TIMEOUT phase_timeout = config.INPUT_SPEAKER_PHRASE_TIMEOUT if record_timeout > phase_timeout: record_timeout = phase_timeout self.speaker_audio_recorder = SelectedSpeakerRecorder( - device=speaker_device, + device=config.CHOICE_SPEAKER_DEVICE , energy_threshold=config.INPUT_SPEAKER_ENERGY_THRESHOLD, dynamic_energy_threshold=config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, record_timeout=record_timeout, @@ -380,10 +390,12 @@ class Model: self.speaker_audio_recorder.stop() self.speaker_audio_recorder = None - def startCheckSpeakerEnergy(self, fnc, end_fnc): - speaker_device = getDefaultOutputDevice() - config.CHOICE_SPEAKER_DEVICE = speaker_device["name"] + def startCheckSpeakerEnergy(self, fnc, end_fnc, error_fnc=None): if config.CHOICE_SPEAKER_DEVICE == "NoDevice": + try: + error_fnc() + except: + pass return def sendSpeakerEnergy(): @@ -396,7 +408,7 @@ class Model: # sleep(0.01) speaker_energy_queue = Queue() - self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(speaker_device) + self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(config.CHOICE_SPEAKER_DEVICE) self.speaker_energy_recorder.recordIntoQueue(speaker_energy_queue) self.speaker_energy_plot_progressbar = threadFnc(sendSpeakerEnergy, end_fnc=end_fnc) self.speaker_energy_plot_progressbar.daemon = True From 3b6222c6f79464f910e14f01034201e22d970eb8 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 17 Oct 2023 04:03:30 +0900 Subject: [PATCH 323/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20:=20Model=20:=20?= =?UTF-8?q?=E4=B8=8D=E8=A6=81=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=9F=E9=96=A2?= =?UTF-8?q?=E6=95=B0getOutputDevices=E3=82=92=E5=89=8A=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- models/transcription/transcription_utils.py | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/models/transcription/transcription_utils.py b/models/transcription/transcription_utils.py index fead4fc4..f40defeb 100644 --- a/models/transcription/transcription_utils.py +++ b/models/transcription/transcription_utils.py @@ -16,17 +16,6 @@ def getInputDevices(): devices = {"NoHost": [{"name": "NoDevice"}]} return devices -def getOutputDevices(): - devices =[] - with PyAudio() as p: - wasapi_info = p.get_host_api_info_by_type(paWASAPI) - for device in p.get_loopback_device_info_generator(): - if device["hostApi"] == wasapi_info["index"] and device["isLoopbackDevice"] is True: - devices.append(device) - if len(devices) == 0: - devices = [{'name':"NoDevice"}] - return devices - def getDefaultInputDevice(): with PyAudio() as p: api_info = p.get_default_host_api_info() From 314e0c86b6b2c8d42436c18c6d5369817d4fd1c3 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 17 Oct 2023 05:36:29 +0900 Subject: [PATCH 324/355] =?UTF-8?q?[Update]=20No=20Device=E6=99=82?= =?UTF-8?q?=E3=81=AE=E5=87=A6=E7=90=86=E3=80=82=E3=82=B9=E3=83=94=E3=83=BC?= =?UTF-8?q?=E3=82=AB=E3=83=BC/=E3=83=9E=E3=82=A4=E3=82=AF=E3=82=92?= =?UTF-8?q?=E4=BD=BF=E3=81=86=E5=87=A6=E7=90=86=E6=99=82=E3=81=AB=E8=A1=A8?= =?UTF-8?q?=E7=A4=BA=E3=81=99=E3=82=8B=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=A1?= =?UTF-8?q?=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=81=AE=E5=AE=9F=E8=A3=85?= =?UTF-8?q?=E3=80=82=E8=A1=A8=E7=A4=BA=E6=96=B9=E6=B3=95=E3=81=AF=E6=94=B9?= =?UTF-8?q?=E5=96=84=E4=BA=88=E5=AE=9A=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 10 +++++----- locales/en.yml | 3 +++ locales/ja.yml | 3 +++ view.py | 12 ++++++++++++ 4 files changed, 23 insertions(+), 5 deletions(-) diff --git a/controller.py b/controller.py index 52c6af9c..8717746b 100644 --- a/controller.py +++ b/controller.py @@ -44,7 +44,7 @@ def sendMicMessage(message): model.logger.info(f"[SENT] {message}{translation}") def startTranscriptionSendMessage(): - model.startMicTranscript(sendMicMessage) + model.startMicTranscript(sendMicMessage, view.printToTextbox_TranscriptionSendNoDeviceError) view.setMainWindowAllWidgetsStatusToNormal() def stopTranscriptionSendMessage(): @@ -64,7 +64,7 @@ def stopThreadingTranscriptionSendMessage(): th_stopTranscriptionSendMessage.start() def startTranscriptionSendMessageOnCloseConfigWindow(): - model.startMicTranscript(sendMicMessage) + model.startMicTranscript(sendMicMessage, view.printToTextbox_TranscriptionSendNoDeviceError) def stopTranscriptionSendMessageOnOpenConfigWindow(): model.stopMicTranscript() @@ -109,7 +109,7 @@ def receiveSpeakerMessage(message): def startTranscriptionReceiveMessage(): config.CHOICE_SPEAKER_DEVICE = model.getOutputDefaultDevice() - model.startSpeakerTranscript(receiveSpeakerMessage, lambda:print("[ERROR] Speaker NoDevice")) + model.startSpeakerTranscript(receiveSpeakerMessage, view.printToTextbox_TranscriptionReceiveNoDeviceError) view.setMainWindowAllWidgetsStatusToNormal() def stopTranscriptionReceiveMessage(): @@ -130,7 +130,7 @@ def stopThreadingTranscriptionReceiveMessage(): def startTranscriptionReceiveMessageOnCloseConfigWindow(): config.CHOICE_SPEAKER_DEVICE = model.getOutputDefaultDevice() - model.startSpeakerTranscript(receiveSpeakerMessage) + model.startSpeakerTranscript(receiveSpeakerMessage, view.printToTextbox_TranscriptionReceiveNoDeviceError) def stopTranscriptionReceiveMessageOnOpenConfigWindow(): @@ -516,7 +516,7 @@ def callbackCheckSpeakerThreshold(is_turned_on): model.startCheckSpeakerEnergy( setProgressBarSpeakerEnergy, view.initProgressBar_SpeakerEnergy, - lambda:print("[ERROR] Speaker NoDevice") + view.showErrorMessage_CheckSpeakerThreshold_NoDevice ) view.replaceSpeakerThresholdCheckButton_Active() diff --git a/locales/en.yml b/locales/en.yml index 85c2b5d7..1b182fec 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -27,6 +27,9 @@ main_window: auth_key_success: Auth key update completed. auth_key_error: Auth Key is incorrect or Usage limit reached. + no_mic_device_detected_error: No mic device detected. + no_speaker_device_detected_error: No speaker device detected. + detected_by_word_filter: The word %{detected_message} has not been sent due to detection by the word filter. selected_your_language: "\"Your Language\" has set to %{your_language}." diff --git a/locales/ja.yml b/locales/ja.yml index 6b6a5705..251edf09 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -27,6 +27,9 @@ main_window: auth_key_success: 認証キーの更新が完了しました。 auth_key_error: 認証キーが間違っているか、API使用制限が上限に達しています. + no_mic_device_detected_error: マイクデバイスが検出されませんでした。 + no_speaker_device_detected_error: スピーカーデバイスが検出されませんでした。 + detected_by_word_filter: ワードフィルターに登録されている単語 %{detected_message} が検出されたため送信しませんでした。 selected_your_language: 「あなたの言語」 を %{your_language} に設定しました。 diff --git a/view.py b/view.py index 29b0c046..a9100c77 100644 --- a/view.py +++ b/view.py @@ -609,6 +609,14 @@ class View(): def printToTextbox_AuthenticationError(self): self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.auth_key_error")) + + def printToTextbox_TranscriptionSendNoDeviceError(self): + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.no_mic_device_detected_error")) + + def printToTextbox_TranscriptionReceiveNoDeviceError(self): + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.no_speaker_device_detected_error")) + + # def printToTextbox_OSCError(self): [Deprecated] # self._printToTextbox_Info("OSC is not enabled, please enable OSC and rejoin. or turn off the \"Send Message To VRChat\" setting") @@ -959,6 +967,10 @@ class View(): def showErrorMessage_SpeakerMaxPhrases(self): self._showErrorMessage(vrct_gui.config_window.sb__entry_speaker_max_phrases, "Speaker Max Phrases Error Message") + + def showErrorMessage_CheckSpeakerThreshold_NoDevice(self): + self._showErrorMessage(vrct_gui.config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold, "No speaker device detected") + def _showErrorMessage(self, target_widget, message): self.view_variable.VAR_ERROR_MESSAGE.set(message) vrct_gui._showErrorMessage(target_widget=target_widget) From 80c043f707ff0847866ad7d1d86863d2acf21a1a Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 17 Oct 2023 10:51:42 +0900 Subject: [PATCH 325/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Config=20:=20ver?= =?UTF-8?q?sion=E3=82=92=202.0.0=20alpha=204=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 030da6bb..e00f73f0 100644 --- a/config.py +++ b/config.py @@ -505,7 +505,7 @@ class Config: def init_config(self): # Read Only - self._VERSION = "2.0.0 alpha 3" + self._VERSION = "2.0.0 alpha 4" self._PATH_CONFIG = os_path.join(os_path.dirname(sys.argv[0]), "config.json") self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" self._BOOTH_URL = "https://misyaguziya.booth.pm/" From ae986b215ec68e5898fe5cad9fc581aca7d5ffbd Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 17 Oct 2023 13:16:04 +0900 Subject: [PATCH 326/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20spea?= =?UTF-8?q?ker=5Fdevice=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=82=92=E9=96=93?= =?UTF-8?q?=E9=81=95=E3=81=88=E3=81=9F=E3=81=AE=E3=81=A7=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 14 +------------- controller.py | 11 ----------- model.py | 10 ++++++---- 3 files changed, 7 insertions(+), 28 deletions(-) diff --git a/config.py b/config.py index e00f73f0..b3761a9d 100644 --- a/config.py +++ b/config.py @@ -7,7 +7,7 @@ import tkinter as tk from tkinter import font from languages import selectable_languages from models.translation.translation_languages import translatorEngine -from models.transcription.transcription_utils import getInputDevices, getDefaultInputDevice, getDefaultOutputDevice +from models.transcription.transcription_utils import getInputDevices, getDefaultInputDevice json_serializable_vars = {} def json_serializable(var_name): @@ -337,17 +337,6 @@ class Config: self._INPUT_MIC_WORD_FILTER = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property - @json_serializable('CHOICE_SPEAKER_DEVICE') - def CHOICE_SPEAKER_DEVICE(self): - return self._CHOICE_SPEAKER_DEVICE - - @CHOICE_SPEAKER_DEVICE.setter - def CHOICE_SPEAKER_DEVICE(self, value): - if getDefaultOutputDevice()["name"] == value: - self._CHOICE_SPEAKER_DEVICE = value - saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) - @property @json_serializable('INPUT_SPEAKER_ENERGY_THRESHOLD') def INPUT_SPEAKER_ENERGY_THRESHOLD(self): @@ -553,7 +542,6 @@ class Config: self._INPUT_MIC_PHRASE_TIMEOUT = 3 self._INPUT_MIC_MAX_PHRASES = 10 self._INPUT_MIC_WORD_FILTER = [] - self._CHOICE_SPEAKER_DEVICE = getDefaultOutputDevice()["name"] self._INPUT_SPEAKER_ENERGY_THRESHOLD = 300 self._INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD = False self._INPUT_SPEAKER_RECORD_TIMEOUT = 3 diff --git a/controller.py b/controller.py index 8717746b..228e55ed 100644 --- a/controller.py +++ b/controller.py @@ -108,7 +108,6 @@ def receiveSpeakerMessage(message): model.logger.info(f"[RECEIVED] {message}{translation}") def startTranscriptionReceiveMessage(): - config.CHOICE_SPEAKER_DEVICE = model.getOutputDefaultDevice() model.startSpeakerTranscript(receiveSpeakerMessage, view.printToTextbox_TranscriptionReceiveNoDeviceError) view.setMainWindowAllWidgetsStatusToNormal() @@ -129,7 +128,6 @@ def stopThreadingTranscriptionReceiveMessage(): th_stopTranscriptionReceiveMessage.start() def startTranscriptionReceiveMessageOnCloseConfigWindow(): - config.CHOICE_SPEAKER_DEVICE = model.getOutputDefaultDevice() model.startSpeakerTranscript(receiveSpeakerMessage, view.printToTextbox_TranscriptionReceiveNoDeviceError) @@ -474,14 +472,6 @@ def callbackSetMicWordFilter(value): model.resetKeywordProcessor() model.addKeywords() -# Transcription Tab (Speaker) -# def callbackSetSpeakerDevice(value): -# print("callbackSetSpeakerDevice", value) -# config.CHOICE_SPEAKER_DEVICE = value - -# model.stopCheckSpeakerEnergy() -# view.replaceSpeakerThresholdCheckButton_Passive() - def callbackSetSpeakerEnergyThreshold(value): print("callbackSetSpeakerEnergyThreshold", value) if value == "": return @@ -512,7 +502,6 @@ def callbackCheckSpeakerThreshold(is_turned_on): print("callbackCheckSpeakerThreshold", is_turned_on) if is_turned_on is True: view.replaceSpeakerThresholdCheckButton_Disabled() - config.CHOICE_SPEAKER_DEVICE = model.getOutputDefaultDevice() model.startCheckSpeakerEnergy( setProgressBarSpeakerEnergy, view.initProgressBar_SpeakerEnergy, diff --git a/model.py b/model.py index 08e0a528..da78a9a5 100644 --- a/model.py +++ b/model.py @@ -344,7 +344,8 @@ class Model: self.mic_energy_recorder = None def startSpeakerTranscript(self, fnc, error_fnc=None): - if config.CHOICE_SPEAKER_DEVICE == "NoDevice": + speaker_device = getDefaultOutputDevice() + if speaker_device["name"] == "NoDevice": try: error_fnc() except: @@ -358,7 +359,7 @@ class Model: record_timeout = phase_timeout self.speaker_audio_recorder = SelectedSpeakerRecorder( - device=config.CHOICE_SPEAKER_DEVICE , + device=speaker_device, energy_threshold=config.INPUT_SPEAKER_ENERGY_THRESHOLD, dynamic_energy_threshold=config.INPUT_SPEAKER_DYNAMIC_ENERGY_THRESHOLD, record_timeout=record_timeout, @@ -391,7 +392,8 @@ class Model: self.speaker_audio_recorder = None def startCheckSpeakerEnergy(self, fnc, end_fnc, error_fnc=None): - if config.CHOICE_SPEAKER_DEVICE == "NoDevice": + speaker_device = getDefaultOutputDevice() + if speaker_device["name"] == "NoDevice": try: error_fnc() except: @@ -408,7 +410,7 @@ class Model: # sleep(0.01) speaker_energy_queue = Queue() - self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(config.CHOICE_SPEAKER_DEVICE) + self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(speaker_device) self.speaker_energy_recorder.recordIntoQueue(speaker_energy_queue) self.speaker_energy_plot_progressbar = threadFnc(sendSpeakerEnergy, end_fnc=end_fnc) self.speaker_energy_plot_progressbar.daemon = True From f4f5ef07e740757f93d8317827c9942732e6eb9d Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Tue, 17 Oct 2023 13:17:25 +0900 Subject: [PATCH 327/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Config=20:=20ver?= =?UTF-8?q?sion=E3=82=922.0.0=20alpha=204.1=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index b3761a9d..d7e6db4c 100644 --- a/config.py +++ b/config.py @@ -494,7 +494,7 @@ class Config: def init_config(self): # Read Only - self._VERSION = "2.0.0 alpha 4" + self._VERSION = "2.0.0 alpha 4.1" self._PATH_CONFIG = os_path.join(os_path.dirname(sys.argv[0]), "config.json") self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" self._BOOTH_URL = "https://misyaguziya.booth.pm/" From 239b0acce39f7cdf0fb0989b289a8032d28809a8 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 17 Oct 2023 15:03:17 +0900 Subject: [PATCH 328/355] =?UTF-8?q?[Update]=20Config=20Window:=20message?= =?UTF-8?q?=20format's=20description.=20add=20sentence=20for=20mentioning?= =?UTF-8?q?=20that=20will=20be=20used=20Notification=20XSOverlay=20too.=20?= =?UTF-8?q?[Update]=20Config=20Window:=20Notification=20XSOverlay's=20labe?= =?UTF-8?q?l.=20=E6=97=A5=E6=9C=AC=E8=AA=9E=E7=89=88=E3=81=A7=E3=81=AE?= =?UTF-8?q?=E6=96=87=E8=A8=80=E5=A4=89=E6=9B=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 2 +- locales/ja.yml | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 1b182fec..b18fbd5d 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -130,7 +130,7 @@ config_window: desc: Automatically export the conversation messages as a text file. message_format: label: Message Format - desc: "You can change the decoration of the message you want to send.\n[message] will be replaced with the message, and [translation] will be replaced with the translated message." + desc: "You can change the decoration of the message you want to send.\n[message] will be replaced with the message, and [translation] will be replaced with the translated message.\nIt will be used in Notification XSOverlay too." send_message_to_vrc: label: Send Message To VRChat desc: There is a way to use it without sending messages to VRChat, but it is not supported. Enable this feature when you intend to send a message to VRChat. diff --git a/locales/ja.yml b/locales/ja.yml index 251edf09..7733ddc0 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -123,14 +123,14 @@ config_window: auto_clear_the_message_box: label: 送信後はチャットボックスを空にする notice_xsoverlay: - label: XSOverlayの通知機能を有効 (VR限定) + label: XSOverlayでの通知受け取り機能を有効 (VR限定) desc: 文字起こし(受信)されたメッセージをXSOverlayの機能を使って通知として受け取れます。 auto_export_message_logs: label: 会話ログを自動的に保存する desc: テキストファイルとしてログがlogsフォルダ内に保存されます。 message_format: label: 送信するメッセージのフォーマット - desc: "VRChatで相手に実際に見えるフォーマットを変更できます。\n[message]がメッセージに置換され、\n[translation]が翻訳されたメッセージに置換されます。" + desc: "VRChatで相手に実際に見えるフォーマットを変更できます。\n[message]がメッセージに置換され、\n[translation]が翻訳されたメッセージに置換されます。\n※XSOverlayでの通知受け取り機能でも使われます。" send_message_to_vrc: label: VRChatにメッセージを送信する desc: "サポート対象外ですが、VRChatにメッセージを送信せずに使う方法があります。送信したい場合、この機能を有効にする事を忘れないでください。" From 7430bd25f80c11c88f90d84cc3e9368221e06b6e Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 17 Oct 2023 17:31:45 +0900 Subject: [PATCH 329/355] =?UTF-8?q?[Update]=20Config=20Window:=20Mic/Speak?= =?UTF-8?q?er=20Record=20Timeout=E3=81=AA=E3=81=A9=E3=81=AE=E8=AA=AC?= =?UTF-8?q?=E6=98=8E=E6=96=87=E8=BF=BD=E5=8A=A0=E3=81=A8=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 13 ++++++------- locales/ja.yml | 13 ++++++------- view.py | 4 ++-- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index b18fbd5d..e8c33dab 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -92,15 +92,13 @@ config_window: desc_for_manual: "Manually determine the microphone input sensitivity using the slider. Press the microphone icon to input your voice and adjust the sensitivity while monitoring the volume." mic_record_timeout: label: Mic Record Timeout - desc: (Second(s)) - # desc: Duration in seconds for detecting silence and determining the end of audio input. + desc: Detects silence and, when the specified number of seconds has passed, considers the mic input to have ended. (Second(s)) mic_phrase_timeout: label: Mic Phrase Timeout - desc: (Second(s)) - # desc: Duration in seconds for determining the end of audio input and transcribing it in one go. + desc: Transcription processing is performed at intervals of the specified number of seconds. mic_max_phrase: label: Mic Max Phrases - # desc: Once the minimum word count for transcription is reached, it will be send. + desc: It is the lower limit for the number of transcribed words, and only when this number is exceeded will the transcription results be displayed logs and send to VRChat. mic_word_filter: label: Mic Word Filter desc: "It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC" @@ -113,12 +111,13 @@ config_window: desc_for_manual: "Manually determine the speaker input sensitivity using the slider. Press the headphones icon to listen to the audio and adjust the sensitivity while monitoring the volume." speaker_record_timeout: label: Speaker Record Timeout - desc: (Second(s)) + desc: Detects silence and, when the specified number of seconds has passed, considers the speaker input to have ended. (Second(s)) speaker_phrase_timeout: label: Speaker Phrase Timeout - desc: (Second(s)) + desc: Transcription processing is performed at intervals of the specified number of seconds. speaker_max_phrase: label: Speaker Max Phrases + desc: It is the lower limit for the number of transcribed words, and only when this number is exceeded will the transcription results be displayed logs. auto_clear_the_message_box: label: Auto Clear The Message Box diff --git a/locales/ja.yml b/locales/ja.yml index 7733ddc0..b638374e 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -92,15 +92,13 @@ config_window: desc_for_manual: スライダーを調整して入力感度を手動で決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。 mic_record_timeout: label: 入力が終了したとみなす無音時間 - desc: 単位は秒です。 - # desc: 無音を検出し、音声入力が終了したとみなす時間の長さです。(秒) + desc: 無音を検出し、設定された秒数経過すると、音声入力が終了したとみなします。 mic_phrase_timeout: label: 一度に文字起こしする時間の長さ - desc: 単位は秒です。 - # desc: 一度に文字起こし処理をする音声時間の長さです。(秒) + desc: 設定された秒数ごとに文字起こし処理が行われます。 mic_max_phrase: label: 送信するまでに保持する単語数 - # desc: 文字起こしされた単語数を保持する最大値で、その数を超えると送信します。 + desc: 文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をVRChatへ送信し、ログに表示します。 mic_word_filter: label: ワードフィルター desc: "設定された単語を検出すると、その文章は送信されません。\n設定の例: AAA,BBB,CCC" @@ -113,12 +111,13 @@ config_window: desc_for_manual: スライダーを調整して入力感度を手動で決められます。ヘッドフォンのアイコンを押すと、実際に音声を聞き取り、音量を確認しながら調節できます。 speaker_record_timeout: label: 入力が終了したとみなす無音時間 - desc: 単位は秒です。 + desc: 無音を検出し、設定された秒数経過すると、音声入力が終了したとみなします。 speaker_phrase_timeout: label: 一度に文字起こしする時間の長さ - desc: 単位は秒です。 + desc: 設定された秒数ごとに文字起こし処理が行われます。 speaker_max_phrase: label: ログとして表示するまでに保持する単語数 + desc: 文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をログに表示します。 auto_clear_the_message_box: label: 送信後はチャットボックスを空にする diff --git a/view.py b/view.py index a9100c77..2a890af7 100644 --- a/view.py +++ b/view.py @@ -249,7 +249,7 @@ class View(): CALLBACK_FOCUS_OUT_MIC_PHRASE_TIMEOUT=self.setLatestConfigVariable_MicPhraseTimeout, VAR_LABEL_MIC_MAX_PHRASES=StringVar(value=i18n.t("config_window.mic_max_phrase.label")), - VAR_DESC_MIC_MAX_PHRASES=None, + VAR_DESC_MIC_MAX_PHRASES=StringVar(value=i18n.t("config_window.mic_max_phrase.desc")), CALLBACK_SET_MIC_MAX_PHRASES=None, VAR_MIC_MAX_PHRASES=StringVar(value=config.INPUT_MIC_MAX_PHRASES), CALLBACK_FOCUS_OUT_MIC_MAX_PHRASES=self.setLatestConfigVariable_MicMaxPhrases, @@ -286,7 +286,7 @@ class View(): CALLBACK_FOCUS_OUT_SPEAKER_PHRASE_TIMEOUT=self.setLatestConfigVariable_SpeakerPhraseTimeout, VAR_LABEL_SPEAKER_MAX_PHRASES=StringVar(value=i18n.t("config_window.speaker_max_phrase.label")), - VAR_DESC_SPEAKER_MAX_PHRASES=None, + VAR_DESC_SPEAKER_MAX_PHRASES=StringVar(value=i18n.t("config_window.speaker_max_phrase.desc")), CALLBACK_SET_SPEAKER_MAX_PHRASES=None, VAR_SPEAKER_MAX_PHRASES=StringVar(value=config.INPUT_SPEAKER_MAX_PHRASES), CALLBACK_FOCUS_OUT_SPEAKER_MAX_PHRASES=self.setLatestConfigVariable_SpeakerMaxPhrases, From 98ac2232a3aa7c88fa9a67206a54b6d07e5b85a9 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Tue, 17 Oct 2023 23:24:41 +0900 Subject: [PATCH 330/355] =?UTF-8?q?[Refactor]=20Main=20Window:=20=E3=83=A1?= =?UTF-8?q?=E3=82=A4=E3=83=B3=E7=94=BB=E9=9D=A2=E3=82=AB=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=81=AEgeometry=E8=A8=88=E7=AE=97=E3=82=92show=E6=99=82?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E6=9B=B4=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateWindowCover.py | 16 ++++++---------- vrct_gui/vrct_gui.py | 1 - 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/vrct_gui/_CreateWindowCover.py b/vrct_gui/_CreateWindowCover.py index f86929c1..d2fc22ce 100644 --- a/vrct_gui/_CreateWindowCover.py +++ b/vrct_gui/_CreateWindowCover.py @@ -22,16 +22,6 @@ class _CreateWindowCover(CTkToplevel): self.settings = settings self._view_variable = view_variable - - self.attach_window.update_idletasks() - self.x_pos = self.attach_window.winfo_rootx() - self.y_pos = self.attach_window.winfo_rooty() - self.width_new = self.attach_window.winfo_width() - self.height_new = self.attach_window.winfo_height() - - - self.geometry("{}x{}+{}+{}".format(self.width_new, self.height_new, self.x_pos, self.y_pos)) - self.grid_rowconfigure(0,weight=1) self.grid_columnconfigure(0,weight=1) self.cover_container = CTkFrame(self, corner_radius=0, fg_color="black", width=0, height=0) @@ -53,4 +43,10 @@ class _CreateWindowCover(CTkToplevel): def show(self): self.attributes("-alpha", 0) self.deiconify() + self.attach_window.update_idletasks() + self.x_pos = self.attach_window.winfo_rootx() + self.y_pos = self.attach_window.winfo_rooty() + self.width_new = self.attach_window.winfo_width() + self.height_new = self.attach_window.winfo_height() + self.geometry("{}x{}+{}+{}".format(self.width_new, self.height_new, self.x_pos, self.y_pos)) fadeInAnimation(self, steps=5, interval=0.005, max_alpha=0.5) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index ae3b8fcb..0c3446d6 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -132,7 +132,6 @@ class VRCT_GUI(CTk): def _openConfigWindow(self, _e): callFunctionIfCallable(self._view_variable.CALLBACK_OPEN_CONFIG_WINDOW) - self._adjustToMainWindowGeometry() self.main_window_cover.show() self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID = self.bind("", self._adjustToMainWindowGeometry, "+") From 5abfe3c20bb59b318b29c20a77f336df6bf3bb52 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 18 Oct 2023 14:54:18 +0900 Subject: [PATCH 331/355] [Update] add Update Software Confirmation Modal Window. --- locales/en.yml | 7 + locales/ja.yml | 7 + view.py | 70 ++++++- vrct_gui/_CreateConfirmationModal.py | 173 ++++++++++++++++++ vrct_gui/_CreateWindowCover.py | 4 + vrct_gui/config_window/ConfigWindow.py | 8 +- .../main_window/widgets/create_sidebar.py | 3 +- vrct_gui/ui_managers/ColorThemeManager.py | 17 +- vrct_gui/ui_managers/UiScalingManager.py | 13 ++ vrct_gui/ui_utils/ui_utils.py | 16 ++ vrct_gui/vrct_gui.py | 17 +- 11 files changed, 316 insertions(+), 19 deletions(-) create mode 100644 vrct_gui/_CreateConfirmationModal.py diff --git a/locales/en.yml b/locales/en.yml index e8c33dab..457ea069 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -44,6 +44,13 @@ main_window: cover_message: The functionality is temporarily disabled until the settings window is closed. + confirmation_message: + update_software: "Download new version and restart automatically.\nIt'll take a while. Do it now?" + deny_update_software: Do it later + accept_update_software: Update and Restart + updating: Now updating... + + selectable_language_window: title_your_language: Select Your Language diff --git a/locales/ja.yml b/locales/ja.yml index b638374e..529e893c 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -44,6 +44,13 @@ main_window: cover_message: 設定画面が閉じられるまで、一時的に機能を停止しています。 + confirmation_message: + update_software: "新しいバージョンをダウンロードして再起動します。\n少し時間がかかるかもしれません。今すぐ行いますか?" + deny_update_software: 後でする + accept_update_software: アップデートして再起動 + updating: アップデート中... + + selectable_language_window: title_your_language: あなたの言語 diff --git a/view.py b/view.py index 2a890af7..ff57f89f 100644 --- a/view.py +++ b/view.py @@ -10,6 +10,7 @@ from languages import selectable_languages from customtkinter import StringVar, IntVar, BooleanVar, END as CTK_END, get_appearance_mode from vrct_gui.ui_managers import ColorThemeManager, ImageFileManager, UiScalingManager from vrct_gui import vrct_gui +from utils import callFunctionIfCallable from config import config @@ -73,20 +74,37 @@ class View(): **common_args ) + self.settings.update_confirmation_modal = SimpleNamespace( + ctm=all_ctm.update_confirmation_modal, + uism=all_uism.update_confirmation_modal, + **common_args + ) + self.view_variable = SimpleNamespace( # Common CALLBACK_RESTART_SOFTWARE=None, # Open Config Window + CALLBACK_CLICKED_OPEN_CONFIG_WINDOW_BUTTON=self._openConfigWindow, + CALLBACK_CLICKED_CLOSE_CONFIG_WINDOW_BUTTON=self._closeConfigWindow, CALLBACK_OPEN_CONFIG_WINDOW=None, CALLBACK_CLOSE_CONFIG_WINDOW=None, # Open Help and Information Page CALLBACK_CLICKED_HELP_AND_INFO=self.openWebPage_VrctDocuments, - # Open Update Page - CALLBACK_CLICKED_UPDATE_AVAILABLE=None, + # For Update Software + # Open Update Confirmation Modal + CALLBACK_CLICKED_UPDATE_AVAILABLE=self._showUpdateSoftwareConfirmationModal, + + CALLBACK_UPDATE_SOFTWARE=None, + CALLBACK_ACCEPT_UPDATE=self._startUpdateSoftware, + CALLBACK_DENY_UPDATE=self._deniedUpdateSoftware, + CALLBACK_HIDE_UPDATE_CONFIRMATION_MODAL=self._hideUpdateSoftwareConfirmationModal, + VAR_MESSAGE_CONFIRMATION_MODAL=StringVar(value=""), + VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON=StringVar(value=""), + VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON=StringVar(value=""), # Main Window @@ -137,7 +155,7 @@ class View(): # Main Window Cover - VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE=StringVar(value=i18n.t("main_window.cover_message")), + VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE=StringVar(value=""), # Selectable Language Window VAR_TITLE_LABEL_SELECTABLE_LANGUAGE=StringVar(value=""), @@ -353,7 +371,7 @@ class View(): if common_registers is not None: - self.view_variable.CALLBACK_CLICKED_UPDATE_AVAILABLE=common_registers.get("callback_update_software", None) + self.view_variable.CALLBACK_UPDATE_SOFTWARE=common_registers.get("callback_update_software", None) self.view_variable.CALLBACK_RESTART_SOFTWARE=common_registers.get("callback_restart_software", None) @@ -548,9 +566,49 @@ class View(): def foregroundOff(): vrct_gui.attributes("-topmost", False) + def _showUpdateSoftwareConfirmationModal(self): - @staticmethod - def _openTheCoverOfMainWindow(): + self.foregroundOffIfForegroundEnabled() + + + self.view_variable.VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE.set("") + vrct_gui.main_window_cover.show() + + self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.update_software")) + self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON.set(i18n.t("main_window.confirmation_message.deny_update_software")) + self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON.set(i18n.t("main_window.confirmation_message.accept_update_software")) + vrct_gui.update_confirmation_modal.show() + vrct_gui.update_confirmation_modal.focus_set() + + def _hideUpdateSoftwareConfirmationModal(self): + self._deniedUpdateSoftware() + self.foregroundOnIfForegroundEnabled() + + + def _startUpdateSoftware(self): + self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.updating")) + vrct_gui.update_confirmation_modal.hide_buttons() + vrct_gui.update() + vrct_gui.update_confirmation_modal.update() + callFunctionIfCallable(self.view_variable.CALLBACK_UPDATE_SOFTWARE) + + def _deniedUpdateSoftware(self): + vrct_gui.update_confirmation_modal.hide() + vrct_gui.main_window_cover.hide() + + + def _openConfigWindow(self): + self.view_variable.VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE.set(i18n.t("main_window.cover_message")) + callFunctionIfCallable(self.view_variable.CALLBACK_OPEN_CONFIG_WINDOW) + vrct_gui._openConfigWindow() + + def _closeConfigWindow(self): + callFunctionIfCallable(self.view_variable.CALLBACK_CLOSE_CONFIG_WINDOW) + vrct_gui._closeConfigWindow() + + + + def _openTheCoverOfMainWindow(self): vrct_gui.main_window_cover.show() vrct_gui.config_window.lift() diff --git a/vrct_gui/_CreateConfirmationModal.py b/vrct_gui/_CreateConfirmationModal.py new file mode 100644 index 00000000..bc6fbd3c --- /dev/null +++ b/vrct_gui/_CreateConfirmationModal.py @@ -0,0 +1,173 @@ +from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont + +from .ui_utils import fadeInAnimation, setGeometryToCenterOfTheWidget, bindButtonFunctionAndColor + +from utils import callFunctionIfCallable + +class _CreateConfirmationModal(CTkToplevel): + def __init__(self, attach_window, settings, view_variable): + super().__init__() + self.withdraw() + + + self.title("") + self.overrideredirect(True) + + self.wm_attributes("-toolwindow", True) + + self.attach_window = attach_window + self.settings = settings + self._view_variable = view_variable + + + # self.configure(fg_color="#ff7f50") + self.configure(fg_color=self.settings.ctm.FAKE_BORDER_COLOR) + self.protocol("WM_DELETE_WINDOW", lambda _e: callFunctionIfCallable(self._view_variable.CALLBACK_HIDE_UPDATE_CONFIRMATION_MODAL)) + + def fucusOutFunction(e): + if str(e.widget) != ".!_createconfirmationmodal": return + callFunctionIfCallable(self._view_variable.CALLBACK_HIDE_UPDATE_CONFIRMATION_MODAL) + + self.bind("", fucusOutFunction, "+") + + + self.grid_rowconfigure(0,weight=1) + self.grid_columnconfigure(0,weight=1) + self.modal_container = CTkFrame(self, corner_radius=0, fg_color=self.settings.ctm.BG_COLOR) + self.modal_container.grid(row=0, column=0, padx=self.settings.uism.FAKE_BORDER_SIZE, pady=self.settings.uism.FAKE_BORDER_SIZE) + + + self.modal_contents_wrapper = CTkFrame(self.modal_container, corner_radius=0, fg_color=self.settings.ctm.BG_COLOR) + self.modal_contents_wrapper.grid(row=0, column=0, padx=self.settings.uism.CONTENTS_WRAPPER, pady=self.settings.uism.CONTENTS_WRAPPER) + + + + self.modal_contents_wrapper.grid_rowconfigure(1, minsize=self.settings.uism.MARGIN_BETWEEN_MESSAGE_AND_BUTTONS) + + self.modal_message_label_wrapper = CTkFrame(self.modal_contents_wrapper, corner_radius=0, fg_color=self.settings.ctm.BG_COLOR) + self.modal_message_label_wrapper.grid(row=0, column=0) + + self.modal_message_label_wrapper.grid_rowconfigure((0,2),weight=1) + self.modal_message_label_wrapper.grid_columnconfigure((0,2),weight=1) + + self.modal_message_label = CTkLabel( + self.modal_message_label_wrapper, + textvariable=self._view_variable.VAR_MESSAGE_CONFIRMATION_MODAL, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.MESSAGE_FONT_SIZE, weight="normal"), + anchor="w", + text_color=self.settings.ctm.MESSAGE_TEXT_COLOR, + ) + self.modal_message_label.grid(row=1, column=1) + + + + self.modal_buttons_container = CTkFrame(self.modal_contents_wrapper, corner_radius=0, fg_color=self.settings.ctm.BG_COLOR) + self.modal_buttons_container.grid(row=2, column=0, sticky="nsew") + + self.modal_buttons_container.grid_rowconfigure((0,2),weight=1) + self.modal_buttons_container.grid_columnconfigure(0,weight=1) + + self.modal_buttons_wrapper = CTkFrame(self.modal_buttons_container, corner_radius=0, fg_color=self.settings.ctm.BG_COLOR) + self.modal_buttons_wrapper.grid(row=1, column=0, sticky="ew") + + + self.modal_buttons_wrapper.grid_columnconfigure(1, weight=1, minsize=self.settings.uism.BUTTONS_BETWEEN_PADDING) + self.modal_buttons_wrapper.grid_columnconfigure((0,2), weight=0, uniform="button_wrapper") + + + + + + self.deny_button = CTkFrame(self.modal_buttons_wrapper, corner_radius=self.settings.uism.BUTTONS_CORNER_RADIUS, fg_color=self.settings.ctm.DENY_BUTTON_BG_COLOR, cursor="hand2") + self.deny_button.grid(row=0, column=0, sticky="ew") + + + self.deny_button.grid_columnconfigure(0, weight=1) + self.deny_button_label_wrapper = CTkFrame(self.deny_button, corner_radius=0, fg_color=self.settings.ctm.DENY_BUTTON_BG_COLOR) + self.deny_button_label_wrapper.grid(row=0, column=0, padx=self.settings.uism.BUTTONS_IPADX, pady=self.settings.uism.BUTTONS_IPADY, sticky="ew") + + self.deny_button_label_wrapper.grid_columnconfigure((0,2), weight=1) + + + self.deny_button_label_wrapper.grid_columnconfigure(0, weight=1) + self.deny_button_label = CTkLabel( + self.deny_button_label_wrapper, + textvariable=self._view_variable.VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.CONFIRMATION_BUTTONS_TEXT_FONT_SIZE, weight="normal"), + anchor="w", + text_color=self.settings.ctm.CONFIRMATION_BUTTONS_TEXT_COLOR, + ) + self.deny_button_label.grid(row=0, column=1) + + + + bindButtonFunctionAndColor( + target_widgets=[ + self.deny_button, + self.deny_button_label_wrapper, + self.deny_button_label, + ], + enter_color=settings.ctm.DENY_BUTTON_HOVERED_BG_COLOR, + leave_color=settings.ctm.DENY_BUTTON_BG_COLOR, + clicked_color=settings.ctm.DENY_BUTTON_CLICKED_BG_COLOR, + buttonReleasedFunction=lambda _e: callFunctionIfCallable(view_variable.CALLBACK_DENY_UPDATE), + ) + + + + self.accept_button = CTkFrame(self.modal_buttons_wrapper, corner_radius=self.settings.uism.BUTTONS_CORNER_RADIUS, fg_color=self.settings.ctm.ACCEPT_BUTTON_BG_COLOR, cursor="hand2") + self.accept_button.grid(row=0, column=2, sticky="ew") + + + self.accept_button.grid_columnconfigure(0, weight=1) + self.accept_button_label_wrapper = CTkFrame(self.accept_button, corner_radius=0, fg_color=self.settings.ctm.ACCEPT_BUTTON_BG_COLOR) + self.accept_button_label_wrapper.grid(row=0, column=0, padx=self.settings.uism.BUTTONS_IPADX, pady=self.settings.uism.BUTTONS_IPADY, sticky="ew") + + self.accept_button_label_wrapper.grid_columnconfigure((0,2), weight=1) + self.accept_button_label = CTkLabel( + self.accept_button_label_wrapper, + textvariable=self._view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.CONFIRMATION_BUTTONS_TEXT_FONT_SIZE, weight="normal"), + anchor="w", + text_color=self.settings.ctm.CONFIRMATION_BUTTONS_TEXT_COLOR, + ) + self.accept_button_label.grid(row=0, column=1) + + + + bindButtonFunctionAndColor( + target_widgets=[ + self.accept_button, + self.accept_button_label_wrapper, + self.accept_button_label, + ], + enter_color=settings.ctm.ACCEPT_BUTTON_HOVERED_BG_COLOR, + leave_color=settings.ctm.ACCEPT_BUTTON_BG_COLOR, + clicked_color=settings.ctm.ACCEPT_BUTTON_CLICKED_BG_COLOR, + buttonReleasedFunction=lambda _e: callFunctionIfCallable(view_variable.CALLBACK_ACCEPT_UPDATE), + ) + + + def hide_buttons(self): + self.modal_buttons_wrapper.grid_remove() + + + def show(self): + self.attributes("-alpha", 0) + self.deiconify() + self.focus_set() + setGeometryToCenterOfTheWidget( + attach_widget=self.attach_window, + target_widget=self + ) + fadeInAnimation(self, steps=5, interval=0.005, max_alpha=1) + + + def hide(self): + self.withdraw() diff --git a/vrct_gui/_CreateWindowCover.py b/vrct_gui/_CreateWindowCover.py index d2fc22ce..caae57bd 100644 --- a/vrct_gui/_CreateWindowCover.py +++ b/vrct_gui/_CreateWindowCover.py @@ -50,3 +50,7 @@ class _CreateWindowCover(CTkToplevel): self.height_new = self.attach_window.winfo_height() self.geometry("{}x{}+{}+{}".format(self.width_new, self.height_new, self.x_pos, self.y_pos)) fadeInAnimation(self, steps=5, interval=0.005, max_alpha=0.5) + + + def hide(self): + self.withdraw() diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 9e57aa3c..1e9d776d 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -11,17 +11,17 @@ class ConfigWindow(CTkToplevel): super().__init__() self.withdraw() + self.settings = settings + self._view_variable = view_variable # configure window self.after(200, lambda: self.iconbitmap(getImagePath("vrct_logo_mark_black.ico"))) - self.geometry(f"{settings.uism.DEFAULT_WIDTH}x{settings.uism.DEFAULT_HEIGHT}") + self.geometry(f"{self.settings.uism.DEFAULT_WIDTH}x{self.settings.uism.DEFAULT_HEIGHT}") self.configure(fg_color="#ff7f50") - self.protocol("WM_DELETE_WINDOW", vrct_gui._closeConfigWindow) + self.protocol("WM_DELETE_WINDOW", self._view_variable.CALLBACK_CLICKED_CLOSE_CONFIG_WINDOW_BUTTON) - self.settings = settings - self._view_variable = view_variable self.title(self._view_variable.VAR_CONFIG_WINDOW_TITLE.get()) # When the configuration window's compact mode is turned on, it will call `grid_remove()` on each widget appended to this array. In the opposite case, `grid()` will be called. diff --git a/vrct_gui/main_window/widgets/create_sidebar.py b/vrct_gui/main_window/widgets/create_sidebar.py index ac6efe14..9f30d269 100644 --- a/vrct_gui/main_window/widgets/create_sidebar.py +++ b/vrct_gui/main_window/widgets/create_sidebar.py @@ -1,6 +1,7 @@ from customtkinter import CTkFrame, CTkLabel, CTkImage from ...ui_utils import bindButtonFunctionAndColor +from utils import callFunctionIfCallable from ._create_sidebar import createSidebarFeatures, createSidebarLanguagesSettings @@ -59,5 +60,5 @@ def createSidebar(settings, main_window, view_variable): enter_color=settings.ctm.CONFIG_BUTTON_HOVERED_BG_COLOR, leave_color=settings.ctm.CONFIG_BUTTON_BG_COLOR, clicked_color=settings.ctm.CONFIG_BUTTON_CLICKED_BG_COLOR, - buttonReleasedFunction=main_window._openConfigWindow, + buttonReleasedFunction=lambda _e: callFunctionIfCallable(view_variable.CALLBACK_CLICKED_OPEN_CONFIG_WINDOW_BUTTON), ) \ No newline at end of file diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index bdded267..6c3ffdb9 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -7,6 +7,7 @@ class ColorThemeManager(): self.selectable_language_window = SimpleNamespace() self.main_window_cover = SimpleNamespace() self.error_message_window = SimpleNamespace() + self.update_confirmation_modal = SimpleNamespace() # old one. But leave it here for now. # self.PRIMARY_100_COLOR = "#c4eac1" @@ -26,6 +27,7 @@ class ColorThemeManager(): self.PRIMARY_200_COLOR = "#8acac0" self.PRIMARY_300_COLOR = "#61b4a7" self.PRIMARY_400_COLOR = "#48a495" + self.PRIMARY_450_COLOR = "#429c8c" self.PRIMARY_500_COLOR = "#3b9483" self.PRIMARY_600_COLOR = "#368777" self.PRIMARY_650_COLOR = "#347f6f" @@ -42,7 +44,7 @@ class ColorThemeManager(): self.DARK_450_COLOR = "#b8b9bd" self.DARK_500_COLOR = "#a9aaae" self.DARK_600_COLOR = "#7f8084" - # self.DARK_650_COLOR = "#75767a" + self.DARK_650_COLOR = "#75767a" self.DARK_700_COLOR = "#6a6c6f" self.DARK_725_COLOR = "#636467" self.DARK_750_COLOR = "#5b5c5f" @@ -216,6 +218,19 @@ class ColorThemeManager(): self.main_window_cover.TEXT_COLOR = self.LIGHT_100_COLOR + self.update_confirmation_modal.MESSAGE_TEXT_COLOR = self.LIGHT_100_COLOR + self.update_confirmation_modal.FAKE_BORDER_COLOR = self.DARK_600_COLOR + self.update_confirmation_modal.BG_COLOR = self.DARK_800_COLOR + self.update_confirmation_modal.CONFIRMATION_BUTTONS_TEXT_COLOR = self.LIGHT_100_COLOR + + self.update_confirmation_modal.ACCEPT_BUTTON_BG_COLOR = self.PRIMARY_600_COLOR + self.update_confirmation_modal.ACCEPT_BUTTON_HOVERED_BG_COLOR = self.PRIMARY_450_COLOR + self.update_confirmation_modal.ACCEPT_BUTTON_CLICKED_BG_COLOR = self.PRIMARY_750_COLOR + self.update_confirmation_modal.DENY_BUTTON_BG_COLOR = self.DARK_750_COLOR + self.update_confirmation_modal.DENY_BUTTON_HOVERED_BG_COLOR = self.DARK_700_COLOR + self.update_confirmation_modal.DENY_BUTTON_CLICKED_BG_COLOR = self.DARK_825_COLOR + + # Common self.config_window.BASIC_TEXT_COLOR = self.main.BASIC_TEXT_COLOR self.config_window.LABELS_TEXT_COLOR = self.config_window.BASIC_TEXT_COLOR diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 56d5cafd..488b91ed 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -10,6 +10,7 @@ class UiScalingManager(): self.selectable_language_window = SimpleNamespace() self.main_window_cover = SimpleNamespace() self.error_message_window = SimpleNamespace() + self.update_confirmation_modal = SimpleNamespace() self._calculatedUiSizes() @@ -142,6 +143,18 @@ class UiScalingManager(): self.main_window_cover.TEXT_FONT_SIZE = self._calculateUiSize(20) + + self.update_confirmation_modal.FAKE_BORDER_SIZE = self._calculateUiSize(1, is_allowed_odd=True) + self.update_confirmation_modal.CONTENTS_WRAPPER = self._calculateUiSize(20) + self.update_confirmation_modal.MARGIN_BETWEEN_MESSAGE_AND_BUTTONS = self._calculateUiSize(40) + self.update_confirmation_modal.MESSAGE_FONT_SIZE = self._calculateUiSize(20) + self.update_confirmation_modal.CONFIRMATION_BUTTONS_TEXT_FONT_SIZE = self._calculateUiSize(18) + self.update_confirmation_modal.BUTTONS_BETWEEN_PADDING = self._calculateUiSize(100) + self.update_confirmation_modal.BUTTONS_CORNER_RADIUS = self._calculateUiSize(6) + self.update_confirmation_modal.BUTTONS_IPADX = self._calculateUiSize(10) + self.update_confirmation_modal.BUTTONS_IPADY = self._calculateUiSize(6) + + # Config Window self.config_window.DEFAULT_WIDTH = self._calculateUiSize(1080) self.config_window.DEFAULT_HEIGHT = self._calculateUiSize(680) diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index e1d4886a..5bdcaebe 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -225,6 +225,22 @@ def setGeometryToCenterOfScreen(root_widget): root_widget.geometry(str(geometry_width)+"x"+str(geometry_height)+"+"+str((sw-geometry_width)//2)+"+"+str((sh-geometry_height)//2)) +def setGeometryToCenterOfTheWidget(attach_widget, target_widget): + target_widget.update() + target_widget.update() + current_window_x = attach_widget.winfo_rootx() + current_window_y = attach_widget.winfo_rooty() + current_window_width = attach_widget.winfo_width() + current_window_height = attach_widget.winfo_height() + desired_window_width = target_widget.winfo_width() + desired_window_height = target_widget.winfo_height() + + desired_window_x = int((current_window_x + current_window_width / 2) - (desired_window_width / 2)) + desired_window_y = int((current_window_y + current_window_height / 2) - (desired_window_height / 2)) + + target_widget.geometry(str(desired_window_width) + "x" + str(desired_window_height) + "+" + str(desired_window_x) + "+" + str(desired_window_y)) + + def fadeInAnimation(root_widget, steps:int=10, interval:float=0.1, max_alpha:float=1): alpha_steps = 100 alpha_steps*=max_alpha diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 0c3446d6..fbcb9043 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -7,6 +7,7 @@ from ._CreateErrorWindow import _CreateErrorWindow from ._CreateDropdownMenuWindow import _CreateDropdownMenuWindow from ._changeMainWindowWidgetsStatus import _changeMainWindowWidgetsStatus from ._changeConfigWindowWidgetsStatus import _changeConfigWindowWidgetsStatus +from ._CreateConfirmationModal import _CreateConfirmationModal from ._printToTextbox import _printToTextbox from .main_window import createMainWindowWidgets @@ -113,9 +114,15 @@ class VRCT_GUI(CTk): message_bg_color=self.settings.config_window.ctm.SB__ERROR_MESSAGE_BG_COLOR, message_text_color=self.settings.config_window.ctm.SB__ERROR_MESSAGE_TEXT_COLOR, - ) + self.update_confirmation_modal = _CreateConfirmationModal( + attach_window=self.toplevel_wrapper, + settings=self.settings.update_confirmation_modal, + view_variable=self._view_variable + ) + + # self.update() # self.geometry("{}x{}".format(self.winfo_width(), self.winfo_height())) @@ -129,9 +136,7 @@ class VRCT_GUI(CTk): self.destroy() - def _openConfigWindow(self, _e): - callFunctionIfCallable(self._view_variable.CALLBACK_OPEN_CONFIG_WINDOW) - + def _openConfigWindow(self): self.main_window_cover.show() self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID = self.bind("", self._adjustToMainWindowGeometry, "+") @@ -151,11 +156,9 @@ class VRCT_GUI(CTk): self.config_window.focus_set() def _closeConfigWindow(self): - callFunctionIfCallable(self._view_variable.CALLBACK_CLOSE_CONFIG_WINDOW) - self.config_window.withdraw() - self.main_window_cover.withdraw() + self.main_window_cover.hide() self.unbind("", self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID) self.unbind("", self.BIND_UNMAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID) self.unbind("", self.BIND_MAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID) From 7cc7b5a7c38c96d9f83574d00a583e59b8278aa7 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Wed, 18 Oct 2023 18:46:10 +0900 Subject: [PATCH 332/355] =?UTF-8?q?[Update]=20=E4=BD=BF=E7=94=A8=E3=83=87?= =?UTF-8?q?=E3=82=A3=E3=82=B9=E3=83=97=E3=83=AC=E3=82=A4=E3=82=B5=E3=82=A4?= =?UTF-8?q?=E3=82=BA=E3=81=AE=E5=A4=A7=E3=81=8D=E3=81=95(=E9=AB=98?= =?UTF-8?q?=E3=81=95)=E3=82=88=E3=82=8A=E3=80=81VRCT=E8=B5=B7=E5=8B=95?= =?UTF-8?q?=E6=99=82=E3=81=AE=E3=82=A6=E3=82=A3=E3=83=B3=E3=83=89=E3=82=A6?= =?UTF-8?q?=E3=81=AE=E6=96=B9=E3=81=8C=E5=A4=A7=E3=81=8D=E3=81=84=E5=A0=B4?= =?UTF-8?q?=E5=90=88=E3=81=AE=E5=AF=BE=E5=BF=9C=E3=80=82UI=20Scaling?= =?UTF-8?q?=E3=82=9220%=E4=B8=8B=E3=81=92=E3=81=A6=E5=86=8D=E8=B5=B7?= =?UTF-8?q?=E5=8B=95=E3=81=99=E3=82=8B=E3=81=8B=E3=81=A9=E3=81=86=E3=81=8B?= =?UTF-8?q?=E3=81=AE=E7=A2=BA=E8=AA=8D=E3=83=A2=E3=83=BC=E3=83=80=E3=83=AB?= =?UTF-8?q?=E3=82=92=E8=A1=A8=E7=A4=BA=E3=80=82=20=E3=81=9D=E3=82=8C?= =?UTF-8?q?=E3=81=AB=E3=81=A8=E3=82=82=E3=81=AA=E3=81=84=E7=A2=BA=E8=AA=8D?= =?UTF-8?q?=E3=83=A2=E3=83=BC=E3=83=80=E3=83=AB=E3=81=AE=E6=B1=8E=E7=94=A8?= =?UTF-8?q?=E5=8C=96=E3=80=82=20=E3=81=95=E3=82=89=E3=81=AB=E3=81=9D?= =?UTF-8?q?=E3=82=8C=E3=81=AB=E4=BC=B4=E3=81=84=E8=A8=88=E7=AE=97=E3=81=AE?= =?UTF-8?q?=E9=83=BD=E5=90=88=E4=B8=8AUI=20Scaling=E3=81=8C40%=E3=81=8B?= =?UTF-8?q?=E3=82=89200%=E3=81=A710%=E5=88=BB=E3=81=BF=E3=81=AB=E5=AF=BE?= =?UTF-8?q?=E5=BF=9C=EF=BC=88=E9=81=B8=E6=8A=9E=E3=81=A7=E3=81=8D=E3=82=8B?= =?UTF-8?q?=E3=82=88=E3=81=86=E3=81=AB=EF=BC=89=E3=81=97=E3=81=BE=E3=81=97?= =?UTF-8?q?=E3=81=9F=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 3 +- locales/en.yml | 3 ++ locales/ja.yml | 4 ++ utils.py | 8 ++- view.py | 75 +++++++++++++++++++++------- vrct_gui/_CreateConfirmationModal.py | 42 ++++++++++++---- vrct_gui/_CreateWindowCover.py | 2 +- vrct_gui/ui_utils/ui_utils.py | 2 +- vrct_gui/vrct_gui.py | 15 ++++++ 9 files changed, 121 insertions(+), 33 deletions(-) diff --git a/config.py b/config.py index d7e6db4c..f8953bd3 100644 --- a/config.py +++ b/config.py @@ -8,6 +8,7 @@ from tkinter import font from languages import selectable_languages from models.translation.translation_languages import translatorEngine from models.transcription.transcription_utils import getInputDevices, getDefaultInputDevice +from utils import generatePercentageStringsList json_serializable_vars = {} def json_serializable(var_name): @@ -220,7 +221,7 @@ class Config: @UI_SCALING.setter def UI_SCALING(self, value): - if value in ["40%", "60%", "80%", "90%", "100%", "110%", "120%", "150%", "200%"]: + if value in generatePercentageStringsList(start=40,end=200, step=10): self._UI_SCALING = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) diff --git a/locales/en.yml b/locales/en.yml index 457ea069..e1616fd8 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -50,6 +50,9 @@ main_window: accept_update_software: Update and Restart updating: Now updating... + detected_over_ui_size: "Current UI Size: %{current_ui_size}\nVRCT's window size may be larger than your display size.\n* Depending on your display size, you may need to adjust it multiple times." + deny_adjust_ui_size: "Keep it at this size" + accept_adjust_ui_size: "Set it smaller and restart" selectable_language_window: diff --git a/locales/ja.yml b/locales/ja.yml index 529e893c..6f807f52 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -50,6 +50,10 @@ main_window: accept_update_software: アップデートして再起動 updating: アップデート中... + detected_over_ui_size: "現在のUI サイズ: %{current_ui_size}\nVRCTのウィンドウサイズが、お使いのディスプレイサイズより大きい可能性があります。\n※ディスプレイサイズによっては、何度か再設定が必要な場合があります。" + deny_adjust_ui_size: このサイズのままで良い + accept_adjust_ui_size: 小さく設定して再起動 + selectable_language_window: diff --git a/utils.py b/utils.py index 5ed7bcd2..6efe6d0a 100644 --- a/utils.py +++ b/utils.py @@ -20,4 +20,10 @@ def isEven(number): def makeEven(number, minus:bool=False): if minus is True: return number if isEven(number) else number - 1 - return number if isEven(number) else number + 1 \ No newline at end of file + return number if isEven(number) else number + 1 + +def generatePercentageStringsList(start=40, end=200, step=10): + strings = [] + for percent in range(start, end + 1, step): + strings.append(f"{percent}%") + return strings \ No newline at end of file diff --git a/view.py b/view.py index ff57f89f..e6c0ed51 100644 --- a/view.py +++ b/view.py @@ -10,7 +10,7 @@ from languages import selectable_languages from customtkinter import StringVar, IntVar, BooleanVar, END as CTK_END, get_appearance_mode from vrct_gui.ui_managers import ColorThemeManager, ImageFileManager, UiScalingManager from vrct_gui import vrct_gui -from utils import callFunctionIfCallable +from utils import callFunctionIfCallable, generatePercentageStringsList from config import config @@ -83,7 +83,17 @@ class View(): self.view_variable = SimpleNamespace( # Common CALLBACK_RESTART_SOFTWARE=None, + CALLBACK_UPDATE_SOFTWARE=None, + CALLBACK_WHEN_DETECT_WINDOW_OVERED_SIZE=self._showDisplayOverUiSizeConfirmationModal, + + # Confirmation Modal + CALLBACK_HIDE_CONFIRMATION_MODAL=None, + CALLBACK_ACCEPTED_CONFIRMATION_MODAL=None, + CALLBACK_DENIED_CONFIRMATION_MODAL=None, + VAR_MESSAGE_CONFIRMATION_MODAL=StringVar(value=""), + VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON=StringVar(value=""), + VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON=StringVar(value=""), # Open Config Window CALLBACK_CLICKED_OPEN_CONFIG_WINDOW_BUTTON=self._openConfigWindow, @@ -94,17 +104,9 @@ class View(): # Open Help and Information Page CALLBACK_CLICKED_HELP_AND_INFO=self.openWebPage_VrctDocuments, - # For Update Software # Open Update Confirmation Modal CALLBACK_CLICKED_UPDATE_AVAILABLE=self._showUpdateSoftwareConfirmationModal, - CALLBACK_UPDATE_SOFTWARE=None, - CALLBACK_ACCEPT_UPDATE=self._startUpdateSoftware, - CALLBACK_DENY_UPDATE=self._deniedUpdateSoftware, - CALLBACK_HIDE_UPDATE_CONFIRMATION_MODAL=self._hideUpdateSoftwareConfirmationModal, - VAR_MESSAGE_CONFIRMATION_MODAL=StringVar(value=""), - VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON=StringVar(value=""), - VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON=StringVar(value=""), # Main Window @@ -203,7 +205,7 @@ class View(): VAR_LABEL_UI_SCALING=StringVar(value=i18n.t("config_window.ui_size.label")), VAR_DESC_UI_SCALING=None, - LIST_UI_SCALING=["40%", "60%", "80%", "90%", "100%", "110%", "120%", "150%", "200%"], + LIST_UI_SCALING=generatePercentageStringsList(start=40,end=200, step=10), CALLBACK_SET_UI_SCALING=None, VAR_UI_SCALING=StringVar(value=config.UI_SCALING), @@ -566,24 +568,64 @@ class View(): def foregroundOff(): vrct_gui.attributes("-topmost", False) - def _showUpdateSoftwareConfirmationModal(self): + def _showDisplayOverUiSizeConfirmationModal(self): self.foregroundOffIfForegroundEnabled() - self.view_variable.VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE.set("") vrct_gui.main_window_cover.show() + self.view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL=self._hideConfirmationModal + self.view_variable.CALLBACK_ACCEPTED_CONFIRMATION_MODAL=self._adjustUiSizeAndRestart + self.view_variable.CALLBACK_DENIED_CONFIRMATION_MODAL=self._hideConfirmationModal + + self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.detected_over_ui_size", current_ui_size=config.UI_SCALING)) + self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON.set(i18n.t("main_window.confirmation_message.deny_adjust_ui_size")) + self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON.set(i18n.t("main_window.confirmation_message.accept_adjust_ui_size")) + + vrct_gui.update_confirmation_modal.show(hide_title_bar=False, close_when_focusout=False) + + + def _adjustUiSizeAndRestart(self): + current_percentage = int(config.UI_SCALING.replace("%","")) + target_percentage = current_percentage - 20 + if target_percentage >= 40 and str(target_percentage) + "%" in self.view_variable.LIST_UI_SCALING: + index = self.view_variable.LIST_UI_SCALING.index(str(target_percentage) + "%") + callFunctionIfCallable(self.view_variable.CALLBACK_SET_UI_SCALING, self.view_variable.LIST_UI_SCALING[index]) + callFunctionIfCallable(self.view_variable.CALLBACK_RESTART_SOFTWARE) + + # ※Below 40% of the UI size is not supported, and we cannot handle it at this time. + + + + + + + + + + def _showUpdateSoftwareConfirmationModal(self): + self.foregroundOffIfForegroundEnabled() + + self.view_variable.VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE.set("") + vrct_gui.main_window_cover.show() + + self.view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL=self._hideConfirmationModal + self.view_variable.CALLBACK_ACCEPTED_CONFIRMATION_MODAL=self._startUpdateSoftware + self.view_variable.CALLBACK_DENIED_CONFIRMATION_MODAL=self._hideConfirmationModal + self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.update_software")) self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON.set(i18n.t("main_window.confirmation_message.deny_update_software")) self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON.set(i18n.t("main_window.confirmation_message.accept_update_software")) vrct_gui.update_confirmation_modal.show() - vrct_gui.update_confirmation_modal.focus_set() - def _hideUpdateSoftwareConfirmationModal(self): - self._deniedUpdateSoftware() + def _hideConfirmationModal(self): + vrct_gui.update_confirmation_modal.hide() + vrct_gui.main_window_cover.hide() self.foregroundOnIfForegroundEnabled() + # def _deniedUpdateSoftware(self): + # self._hideConfirmationModal() def _startUpdateSoftware(self): self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.updating")) @@ -592,9 +634,6 @@ class View(): vrct_gui.update_confirmation_modal.update() callFunctionIfCallable(self.view_variable.CALLBACK_UPDATE_SOFTWARE) - def _deniedUpdateSoftware(self): - vrct_gui.update_confirmation_modal.hide() - vrct_gui.main_window_cover.hide() def _openConfigWindow(self): diff --git a/vrct_gui/_CreateConfirmationModal.py b/vrct_gui/_CreateConfirmationModal.py index bc6fbd3c..ece22e2a 100644 --- a/vrct_gui/_CreateConfirmationModal.py +++ b/vrct_gui/_CreateConfirmationModal.py @@ -12,9 +12,11 @@ class _CreateConfirmationModal(CTkToplevel): self.title("") self.overrideredirect(True) - self.wm_attributes("-toolwindow", True) + self.BIND_FOCUS_OUT_FUNC_ID=None + + self.attach_window = attach_window self.settings = settings self._view_variable = view_variable @@ -22,13 +24,8 @@ class _CreateConfirmationModal(CTkToplevel): # self.configure(fg_color="#ff7f50") self.configure(fg_color=self.settings.ctm.FAKE_BORDER_COLOR) - self.protocol("WM_DELETE_WINDOW", lambda _e: callFunctionIfCallable(self._view_variable.CALLBACK_HIDE_UPDATE_CONFIRMATION_MODAL)) + self.protocol("WM_DELETE_WINDOW", lambda: callFunctionIfCallable(self._view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL)) - def fucusOutFunction(e): - if str(e.widget) != ".!_createconfirmationmodal": return - callFunctionIfCallable(self._view_variable.CALLBACK_HIDE_UPDATE_CONFIRMATION_MODAL) - - self.bind("", fucusOutFunction, "+") self.grid_rowconfigure(0,weight=1) @@ -114,7 +111,7 @@ class _CreateConfirmationModal(CTkToplevel): enter_color=settings.ctm.DENY_BUTTON_HOVERED_BG_COLOR, leave_color=settings.ctm.DENY_BUTTON_BG_COLOR, clicked_color=settings.ctm.DENY_BUTTON_CLICKED_BG_COLOR, - buttonReleasedFunction=lambda _e: callFunctionIfCallable(view_variable.CALLBACK_DENY_UPDATE), + buttonReleasedFunction=lambda _e: callFunctionIfCallable(self._view_variable.CALLBACK_DENIED_CONFIRMATION_MODAL), ) @@ -150,7 +147,7 @@ class _CreateConfirmationModal(CTkToplevel): enter_color=settings.ctm.ACCEPT_BUTTON_HOVERED_BG_COLOR, leave_color=settings.ctm.ACCEPT_BUTTON_BG_COLOR, clicked_color=settings.ctm.ACCEPT_BUTTON_CLICKED_BG_COLOR, - buttonReleasedFunction=lambda _e: callFunctionIfCallable(view_variable.CALLBACK_ACCEPT_UPDATE), + buttonReleasedFunction=lambda _e: callFunctionIfCallable(self._view_variable.CALLBACK_ACCEPTED_CONFIRMATION_MODAL), ) @@ -158,16 +155,39 @@ class _CreateConfirmationModal(CTkToplevel): self.modal_buttons_wrapper.grid_remove() - def show(self): + def show(self, hide_title_bar:bool=True, close_when_focusout:bool=True): + if hide_title_bar is False: + self.overrideredirect(False) + else: + self.overrideredirect(True) + + self.close_when_focusout = close_when_focusout + if self.close_when_focusout is True: + self.BIND_FOCUS_OUT_FUNC_ID = self.bind("", self.focusOutFunction, "+") + else: + self._grab_set() + + self.attributes("-alpha", 0) self.deiconify() - self.focus_set() setGeometryToCenterOfTheWidget( attach_widget=self.attach_window, target_widget=self ) fadeInAnimation(self, steps=5, interval=0.005, max_alpha=1) + self.focus_set() def hide(self): + if self.BIND_FOCUS_OUT_FUNC_ID is not None: + self.unbind("", self.BIND_FOCUS_OUT_FUNC_ID) + self.withdraw() + self.grab_release() + + def focusOutFunction(self, e): + if str(e.widget) != ".!_createconfirmationmodal": return + callFunctionIfCallable(self._view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL) + + def _grab_set(self): + self.grab_set() diff --git a/vrct_gui/_CreateWindowCover.py b/vrct_gui/_CreateWindowCover.py index caae57bd..9ab8c4ee 100644 --- a/vrct_gui/_CreateWindowCover.py +++ b/vrct_gui/_CreateWindowCover.py @@ -17,7 +17,7 @@ class _CreateWindowCover(CTkToplevel): self.configure(fg_color="#ff7f50") - self.protocol("WM_DELETE_WINDOW", lambda e: self.withdraw()) + self.protocol("WM_DELETE_WINDOW", lambda: self.withdraw()) self.settings = settings self._view_variable = view_variable diff --git a/vrct_gui/ui_utils/ui_utils.py b/vrct_gui/ui_utils/ui_utils.py index 5bdcaebe..bf64a985 100644 --- a/vrct_gui/ui_utils/ui_utils.py +++ b/vrct_gui/ui_utils/ui_utils.py @@ -226,7 +226,7 @@ def setGeometryToCenterOfScreen(root_widget): def setGeometryToCenterOfTheWidget(attach_widget, target_widget): - target_widget.update() + attach_widget.update() target_widget.update() current_window_x = attach_widget.winfo_rootx() current_window_y = attach_widget.winfo_rooty() diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index fbcb9043..2337e23c 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -54,6 +54,11 @@ class VRCT_GUI(CTk): self._enableMainWindowSidebarCompactMode() fadeInAnimation(self, steps=5, interval=0.008) + + if self._isOverWindowSizeCheck() is True: + callFunctionIfCallable(self._view_variable.CALLBACK_WHEN_DETECT_WINDOW_OVERED_SIZE) + + def _createGUI(self, settings, view_variable): self.settings = settings self._view_variable = view_variable @@ -282,4 +287,14 @@ class VRCT_GUI(CTk): pass + def _isOverWindowSizeCheck(self): + self.update() + screen_height = self.winfo_screenheight() + window_height = self.winfo_height() + print(screen_height, window_height) + if screen_height < window_height: + return True + else: + return False + vrct_gui = VRCT_GUI() \ No newline at end of file From 8e55a5426c29497360af08a9ee336d1963ae71d7 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 19 Oct 2023 08:53:44 +0900 Subject: [PATCH 333/355] =?UTF-8?q?[Update]=20Config=20Window:=20=E5=90=84?= =?UTF-8?q?=E9=A0=85=E7=9B=AE=E3=81=AE=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=A1?= =?UTF-8?q?=E3=83=83=E3=82=BB=E3=83=BC=E3=82=B8=E3=82=92=E8=BF=BD=E5=8A=A0?= =?UTF-8?q?=EF=BC=88=E4=BB=AE=E7=BD=AE=E3=81=8D=E3=81=98=E3=82=83=E3=81=AA?= =?UTF-8?q?=E3=81=8F=E3=81=AA=E3=82=8A=E3=81=BE=E3=81=97=E3=81=9F=E3=80=82?= =?UTF-8?q?=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 32 +++++++++++ locales/ja.yml | 31 +++++++++++ view.py | 69 ++++++++++++++++++++---- vrct_gui/ui_managers/UiScalingManager.py | 2 +- 4 files changed, 124 insertions(+), 10 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index e1616fd8..3c1dc124 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -66,6 +66,9 @@ config_window: compact_mode: Compact Mode version: version %{version} restart_message: Apply changes with a restart. + common_error_message: + invalid_value: Invalid value. + side_menu_labels: appearance: Appearance # translation: Translation @@ -75,16 +78,21 @@ config_window: others: Others advanced_settings: Advanced Settings + transparency: label: Transparency desc: Change the main window's transparency. + appearance_theme: label: Theme [Under development] desc: Change the color theme. Currently, only the Dark theme is supported. The Light theme is under development. + ui_size: label: UI Size + font_family: label: Font Family + ui_language: label: UI Language @@ -93,22 +101,32 @@ config_window: mic_host: label: Mic Host/Driver + mic_device: label: Mic Device + mic_dynamic_energy_threshold: label_for_automatic: "Mic Energy Threshold (Current Setting: Automatic)" desc_for_automatic: "Automatically determine microphone input sensitivity." label_for_manual: "Mic Energy Threshold (Current Setting: Manual)" desc_for_manual: "Manually determine the microphone input sensitivity using the slider. Press the microphone icon to input your voice and adjust the sensitivity while monitoring the volume." + error_message: You can set it with a value between 0 to %{max}. + mic_record_timeout: label: Mic Record Timeout desc: Detects silence and, when the specified number of seconds has passed, considers the mic input to have ended. (Second(s)) + error_message: It cannot be greater than '%{mic_phrase_timeout_label}' with a value of 0 or more. + mic_phrase_timeout: label: Mic Phrase Timeout desc: Transcription processing is performed at intervals of the specified number of seconds. + error_message: It cannot be set lower than '%{mic_record_timeout_label}' with a value of 0 or more. + mic_max_phrase: label: Mic Max Phrases desc: It is the lower limit for the number of transcribed words, and only when this number is exceeded will the transcription results be displayed logs and send to VRChat. + error_message: You can set a number equal to or greater than 0. + mic_word_filter: label: Mic Word Filter desc: "It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC" @@ -119,32 +137,46 @@ config_window: desc_for_automatic: "Automatically determine speaker input sensitivity." label_for_manual: "Speaker Energy Threshold (Current Setting: Manual)" desc_for_manual: "Manually determine the speaker input sensitivity using the slider. Press the headphones icon to listen to the audio and adjust the sensitivity while monitoring the volume." + error_message: You can set it with a value between 0 to %{max}. + no_device_error_message: No speaker device detected. + speaker_record_timeout: label: Speaker Record Timeout desc: Detects silence and, when the specified number of seconds has passed, considers the speaker input to have ended. (Second(s)) + error_message: It cannot be greater than '%{speaker_phrase_timeout_label}' with a value of 0 or more. + speaker_phrase_timeout: label: Speaker Phrase Timeout desc: Transcription processing is performed at intervals of the specified number of seconds. + error_message: It cannot be set lower than '%{speaker_record_timeout_label}' with a value of 0 or more. + speaker_max_phrase: label: Speaker Max Phrases desc: It is the lower limit for the number of transcribed words, and only when this number is exceeded will the transcription results be displayed logs. + error_message: You can set a number equal to or greater than 0. + auto_clear_the_message_box: label: Auto Clear The Message Box + notice_xsoverlay: label: Notification XSOverlay (VR Only) desc: Notify received messages by using XSOverlay's notification feature. + auto_export_message_logs: label: Auto Export Message Logs desc: Automatically export the conversation messages as a text file. + message_format: label: Message Format desc: "You can change the decoration of the message you want to send.\n[message] will be replaced with the message, and [translation] will be replaced with the translated message.\nIt will be used in Notification XSOverlay too." + send_message_to_vrc: label: Send Message To VRChat desc: There is a way to use it without sending messages to VRChat, but it is not supported. Enable this feature when you intend to send a message to VRChat. osc_ip_address: label: OSC IP Address + osc_port: label: OSC Port \ No newline at end of file diff --git a/locales/ja.yml b/locales/ja.yml index 6f807f52..dac3a0b2 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -67,6 +67,9 @@ config_window: compact_mode: コンパクトモード version: バージョン %{version} restart_message: 再起動して変更を適用する。 + common_error_message: + invalid_value: 無効な値です。 + side_menu_labels: appearance: デザイン # translation: 翻訳 @@ -76,16 +79,21 @@ config_window: others: その他 advanced_settings: 高度な設定 + transparency: label: 透明度 desc: メイン画面の透明度を変更できます。 + appearance_theme: label: 外観テーマ [開発中] desc: カラーテーマを変更できます。現在はDarkテーマのみ対応。Lightテーマは制作中です。 + ui_size: label: UIサイズ + font_family: label: 使用フォント + ui_language: label: UIの言語 / UI Language @@ -94,22 +102,32 @@ config_window: mic_host: label: マイク(ホスト/ドライバー) + mic_device: label: マイク (デバイス) + mic_dynamic_energy_threshold: label_for_automatic: "マイク入力感度の調整 (現在の設定: 自動)" desc_for_automatic: マイクの入力感度を自動的に調節する。 label_for_manual: "マイク入力感度の調整 (現在の設定: 手動)" desc_for_manual: スライダーを調整して入力感度を手動で決められます。マイクのアイコンを押すと、実際に声を入力し、音量を確認しながら調節できます。 + error_message: 0 から %{max} までの数値で設定できます。 + mic_record_timeout: label: 入力が終了したとみなす無音時間 desc: 無音を検出し、設定された秒数経過すると、音声入力が終了したとみなします。 + error_message: 0 以上で 「%{mic_phrase_timeout_label}」より大きくすることはできません。 + mic_phrase_timeout: label: 一度に文字起こしする時間の長さ desc: 設定された秒数ごとに文字起こし処理が行われます。 + error_message: 0 以上で 「%{mic_record_timeout_label}」より小さくすることはできません。 + mic_max_phrase: label: 送信するまでに保持する単語数 desc: 文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をVRChatへ送信し、ログに表示します。 + error_message: 0以上の数値を設定できます。 + mic_word_filter: label: ワードフィルター desc: "設定された単語を検出すると、その文章は送信されません。\n設定の例: AAA,BBB,CCC" @@ -120,32 +138,45 @@ config_window: desc_for_automatic: スピーカーの入力感度を自動的に調節する。 label_for_manual: "スピーカー入力感度の調整 (現在の設定: 手動)" desc_for_manual: スライダーを調整して入力感度を手動で決められます。ヘッドフォンのアイコンを押すと、実際に音声を聞き取り、音量を確認しながら調節できます。 + error_message: 0 から %{max} までの数値で設定できます。 + no_device_error_message: スピーカーデバイスが検出されませんでした。 + speaker_record_timeout: label: 入力が終了したとみなす無音時間 desc: 無音を検出し、設定された秒数経過すると、音声入力が終了したとみなします。 + error_message: 0 以上で 「%{speaker_phrase_timeout_label}」より大きくすることはできません。 + speaker_phrase_timeout: label: 一度に文字起こしする時間の長さ desc: 設定された秒数ごとに文字起こし処理が行われます。 + error_message: 0 以上で 「%{speaker_record_timeout_label}」より小さくすることはできません。 + speaker_max_phrase: label: ログとして表示するまでに保持する単語数 desc: 文字起こしされた単語数の下限値で、この数値を超えた場合のみ結果をログに表示します。 + error_message: 0以上の数値を設定できます。 auto_clear_the_message_box: label: 送信後はチャットボックスを空にする + notice_xsoverlay: label: XSOverlayでの通知受け取り機能を有効 (VR限定) desc: 文字起こし(受信)されたメッセージをXSOverlayの機能を使って通知として受け取れます。 + auto_export_message_logs: label: 会話ログを自動的に保存する desc: テキストファイルとしてログがlogsフォルダ内に保存されます。 + message_format: label: 送信するメッセージのフォーマット desc: "VRChatで相手に実際に見えるフォーマットを変更できます。\n[message]がメッセージに置換され、\n[translation]が翻訳されたメッセージに置換されます。\n※XSOverlayでの通知受け取り機能でも使われます。" + send_message_to_vrc: label: VRChatにメッセージを送信する desc: "サポート対象外ですが、VRChatにメッセージを送信せずに使う方法があります。送信したい場合、この機能を有効にする事を忘れないでください。" osc_ip_address: label: OSC IP Address + osc_port: label: OSC Port \ No newline at end of file diff --git a/view.py b/view.py index e6c0ed51..f16c5ee7 100644 --- a/view.py +++ b/view.py @@ -1040,38 +1040,89 @@ class View(): def showErrorMessage_MicEnergyThreshold(self): - self._showErrorMessage(vrct_gui.config_window.sb__progressbar_x_slider__entry_mic_energy_threshold, "Mic Energy Threshold Error Message") + self._showErrorMessage( + vrct_gui.config_window.sb__progressbar_x_slider__entry_mic_energy_threshold, + self._makeInvalidValueErrorMessage(i18n.t("config_window.mic_dynamic_energy_threshold.error_message", max=config.MAX_MIC_ENERGY_THRESHOLD)) + ) def showErrorMessage_MicRecordTimeout(self): - self._showErrorMessage(vrct_gui.config_window.sb__entry_mic_record_timeout, "Mic Record Timeout Error Message") + self._showErrorMessage( + vrct_gui.config_window.sb__entry_mic_record_timeout, + self._makeInvalidValueErrorMessage( + i18n.t( + "config_window.mic_record_timeout.error_message", + mic_phrase_timeout_label=i18n.t("config_window.mic_phrase_timeout.label") + ) + ) + ) def showErrorMessage_MicPhraseTimeout(self): - self._showErrorMessage(vrct_gui.config_window.sb__entry_mic_phrase_timeout, "Mic Phrase Timeout Error Message") + self._showErrorMessage( + vrct_gui.config_window.sb__entry_mic_phrase_timeout, + self._makeInvalidValueErrorMessage( + i18n.t( + "config_window.mic_phrase_timeout.error_message", + mic_record_timeout_label=i18n.t("config_window.mic_record_timeout.label") + ) + ) + ) def showErrorMessage_MicMaxPhrases(self): - self._showErrorMessage(vrct_gui.config_window.sb__entry_mic_max_phrases, "Mic Max Phrases Error Message") + self._showErrorMessage( + vrct_gui.config_window.sb__entry_mic_max_phrases, + self._makeInvalidValueErrorMessage(i18n.t("config_window.mic_max_phrase.error_message")) + ) def showErrorMessage_SpeakerEnergyThreshold(self): - self._showErrorMessage(vrct_gui.config_window.sb__progressbar_x_slider__entry_speaker_energy_threshold, "Speaker Energy Threshold Error Message") + self._showErrorMessage( + vrct_gui.config_window.sb__progressbar_x_slider__entry_speaker_energy_threshold, + self._makeInvalidValueErrorMessage(i18n.t("config_window.speaker_dynamic_energy_threshold.error_message", max=config.MAX_SPEAKER_ENERGY_THRESHOLD)) + ) def showErrorMessage_SpeakerRecordTimeout(self): - self._showErrorMessage(vrct_gui.config_window.sb__entry_speaker_record_timeout, "Speaker Record Timeout Error Message") + self._showErrorMessage( + vrct_gui.config_window.sb__entry_speaker_record_timeout, + self._makeInvalidValueErrorMessage( + i18n.t( + "config_window.speaker_record_timeout.error_message", + speaker_phrase_timeout_label=i18n.t("config_window.speaker_phrase_timeout.label") + ) + ) + ) def showErrorMessage_SpeakerPhraseTimeout(self): - self._showErrorMessage(vrct_gui.config_window.sb__entry_speaker_phrase_timeout, "Speaker Phrase Timeout Error Message") + self._showErrorMessage( + vrct_gui.config_window.sb__entry_speaker_phrase_timeout, + self._makeInvalidValueErrorMessage( + i18n.t( + "config_window.speaker_phrase_timeout.error_message", + speaker_record_timeout_label=i18n.t("config_window.speaker_record_timeout.label") + ) + ) + ) def showErrorMessage_SpeakerMaxPhrases(self): - self._showErrorMessage(vrct_gui.config_window.sb__entry_speaker_max_phrases, "Speaker Max Phrases Error Message") + self._showErrorMessage( + vrct_gui.config_window.sb__entry_speaker_max_phrases, + self._makeInvalidValueErrorMessage(i18n.t("config_window.speaker_max_phrase.error_message")) + ) def showErrorMessage_CheckSpeakerThreshold_NoDevice(self): - self._showErrorMessage(vrct_gui.config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold, "No speaker device detected") + self._showErrorMessage( + vrct_gui.config_window.sb__progressbar_x_slider__active_button_speaker_energy_threshold, + self._makeInvalidValueErrorMessage(i18n.t("config_window.speaker_dynamic_energy_threshold.no_device_error_message")) + ) def _showErrorMessage(self, target_widget, message): self.view_variable.VAR_ERROR_MESSAGE.set(message) vrct_gui._showErrorMessage(target_widget=target_widget) + @staticmethod + def _makeInvalidValueErrorMessage(error_message): + return i18n.t("config_window.common_error_message.invalid_value") + "\n" + error_message + def clearErrorMessage(self): vrct_gui._clearErrorMessage() diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 488b91ed..79961dd6 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -217,7 +217,7 @@ class UiScalingManager(): self.config_window.SB__ERROR_MESSAGE_IPADX = (self._calculateUiSize(10), self._calculateUiSize(10)) self.config_window.SB__ERROR_MESSAGE_IPADY = (self._calculateUiSize(6), self._calculateUiSize(6)) - self.config_window.SB__ERROR_MESSAGE_FONT_SIZE = self._calculateUiSize(12) + self.config_window.SB__ERROR_MESSAGE_FONT_SIZE = self._calculateUiSize(14) self.config_window.SB__SELECTOR_FONT_SIZE = self._calculateUiSize(14) From 7eee38f1070fafe356e70e1a60fe3fe04c9b4b7a Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 19 Oct 2023 08:58:47 +0900 Subject: [PATCH 334/355] [Chore] Remove the python print code that forgot to remove. --- vrct_gui/vrct_gui.py | 1 - 1 file changed, 1 deletion(-) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 2337e23c..683d4f06 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -291,7 +291,6 @@ class VRCT_GUI(CTk): self.update() screen_height = self.winfo_screenheight() window_height = self.winfo_height() - print(screen_height, window_height) if screen_height < window_height: return True else: From 2b0476c8b91e0ff3d8bc4e4d1a54dff005783d1a Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:06:40 +0900 Subject: [PATCH 335/355] =?UTF-8?q?[bugfix]=20VRCT=E3=82=A6=E3=82=A3?= =?UTF-8?q?=E3=83=B3=E3=83=89=E3=82=A6UI=20Size40%=E3=81=A7=E3=82=82OS?= =?UTF-8?q?=E3=82=A6=E3=82=A3=E3=83=B3=E3=83=89=E3=82=A6=E3=82=B5=E3=82=A4?= =?UTF-8?q?=E3=82=BA=E3=82=92=E8=B6=85=E3=81=88=E3=82=8B=E5=A0=B4=E5=90=88?= =?UTF-8?q?=E3=81=AF=E3=80=81=E7=A2=BA=E8=AA=8D=E3=83=A2=E3=83=BC=E3=83=80?= =?UTF-8?q?=E3=83=AB=E3=81=8C=E6=B0=B8=E9=81=A0=E5=87=BA=E3=81=A6=E3=81=97?= =?UTF-8?q?=E3=81=BE=E3=81=86=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3=E3=80=82?= =?UTF-8?q?=E3=81=9D=E3=81=86=E3=81=84=E3=81=86=E7=92=B0=E5=A2=83=E3=82=92?= =?UTF-8?q?=E4=BD=9C=E3=82=8C=E3=81=9A=E3=83=86=E3=82=B9=E3=83=88=E3=81=AF?= =?UTF-8?q?=E3=81=A7=E3=81=8D=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E3=81=8C?= =?UTF-8?q?=E3=80=81=E3=81=BE=E3=81=81=E6=83=B3=E5=AE=9A=E5=A4=96=E3=81=99?= =?UTF-8?q?=E3=81=8E=E3=82=8B=E3=83=87=E3=82=A3=E3=82=B9=E3=83=97=E3=83=AC?= =?UTF-8?q?=E3=82=A4=E3=82=B5=E3=82=A4=E3=82=BA=E3=81=AA=E3=81=AE=E3=81=A7?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/view.py b/view.py index f16c5ee7..21e07334 100644 --- a/view.py +++ b/view.py @@ -593,7 +593,8 @@ class View(): index = self.view_variable.LIST_UI_SCALING.index(str(target_percentage) + "%") callFunctionIfCallable(self.view_variable.CALLBACK_SET_UI_SCALING, self.view_variable.LIST_UI_SCALING[index]) callFunctionIfCallable(self.view_variable.CALLBACK_RESTART_SOFTWARE) - + else: + self._hideConfirmationModal() # ※Below 40% of the UI size is not supported, and we cannot handle it at this time. From b007fb1c5524428b1a6ed9c8a6d1426e2730415c Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 19 Oct 2023 09:32:06 +0900 Subject: [PATCH 336/355] =?UTF-8?q?[bugfix]=20Selectable=20Language=20Wind?= =?UTF-8?q?ow:=20Focus=E3=82=A4=E3=83=99=E3=83=B3=E3=83=88=E3=81=8C?= =?UTF-8?q?=E6=84=8F=E5=9B=B3=E3=81=97=E3=81=AA=E3=81=84=E3=81=A8=E3=81=93?= =?UTF-8?q?=E3=82=8D=E3=81=A7=E7=99=BA=E7=94=9F=E3=81=97=E3=81=A6=E3=82=A6?= =?UTF-8?q?=E3=82=A3=E3=83=B3=E3=83=89=E3=82=A6=E9=96=89=E3=81=98=E3=81=A6?= =?UTF-8?q?=E3=81=97=E3=81=BE=E3=81=86=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateSelectableLanguagesWindow.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index ca87c547..850c02cd 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -11,7 +11,6 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.withdraw() - # configure window self.title("_CreateSelectableLanguagesWindow") self.overrideredirect(True) @@ -24,7 +23,8 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): self.settings = settings self._view_variable = view_variable - self.bind("", lambda e: vrct_gui._closeSelectableLanguagesWindow()) + + self.bind("", self.focusOutFunction) @@ -179,4 +179,9 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): row+=1 - self.is_created = True \ No newline at end of file + self.is_created = True + + + def focusOutFunction(self, e): + if str(e.widget) != ".!_createselectablelanguageswindow": return + self.vrct_gui._closeSelectableLanguagesWindow() \ No newline at end of file From 40f999f5565e8f39139e89d16ab512b3ebff4b29 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:18:10 +0900 Subject: [PATCH 337/355] =?UTF-8?q?[bugfix]=20Config=20Window:=20Dropdown?= =?UTF-8?q?=20Menu=20Window.=20=E5=B9=85=E3=82=92=E5=86=85=E5=AE=B9?= =?UTF-8?q?=E3=81=AB=E5=90=88=E3=82=8F=E3=81=9B=E3=81=A6=E5=8F=AF=E5=A4=89?= =?UTF-8?q?=E7=9A=84=E3=81=AB=E3=80=82=E9=95=B7=E3=81=84=E3=83=86=E3=82=AD?= =?UTF-8?q?=E3=82=B9=E3=83=88=E3=81=8C=E5=85=A5=E3=81=A3=E3=81=A6=E3=82=82?= =?UTF-8?q?=E8=A6=8B=E3=81=8D=E3=82=8C=E3=81=AA=E3=81=84=E3=82=88=E3=81=86?= =?UTF-8?q?=E3=81=AB=E4=BF=AE=E6=AD=A3=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- vrct_gui/_CreateDropdownMenuWindow.py | 26 ++++++++++++------- .../_SettingBoxGenerator.py | 4 +-- .../createSettingBox_Appearance.py | 1 - .../createSettingBox_Mic.py | 1 - vrct_gui/ui_managers/UiScalingManager.py | 1 + vrct_gui/vrct_gui.py | 1 + 6 files changed, 21 insertions(+), 13 deletions(-) diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index 43ce52f8..9688ea4d 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -3,7 +3,7 @@ from types import SimpleNamespace from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont, CTkScrollableFrame from time import sleep -from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor, getLatestHeight, applyUiScalingAndFixTheBugScrollBar +from .ui_utils import bindButtonReleaseFunction, bindEnterAndLeaveColor, bindButtonPressColor, getLatestHeight, applyUiScalingAndFixTheBugScrollBar, getLatestWidth, getLongestText from functools import partial from utils import isEven, makeEven @@ -22,6 +22,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): value_ipady, value_pady, value_font_size, + dropdown_menu_default_min_width, window_bg_color, window_border_color, @@ -53,6 +54,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.value_ipady=value_ipady self.value_pady=value_pady self.value_font_size=value_font_size + self.dropdown_menu_default_min_width=dropdown_menu_default_min_width self.window_bg_color=window_bg_color self.window_border_color=window_border_color @@ -97,19 +99,19 @@ class _CreateDropdownMenuWindow(CTkToplevel): wrapper_widget=self.dropdown_menu_widgets[dropdown_menu_widget_id].wrapper_widget, attach_widget=self.dropdown_menu_widgets[dropdown_menu_widget_id].attach_widget, - dropdown_menu_width=self.dropdown_menu_widgets[dropdown_menu_widget_id].dropdown_menu_settings.dropdown_menu_width, + dropdown_menu_min_width=self.dropdown_menu_widgets[dropdown_menu_widget_id].dropdown_menu_settings.dropdown_menu_min_width, dropdown_menu_height=self.dropdown_menu_widgets[dropdown_menu_widget_id].dropdown_menu_settings.dropdown_menu_height, max_display_length=self.dropdown_menu_widgets[dropdown_menu_widget_id].dropdown_menu_settings.max_display_length, ) - def createDropdownMenuBox(self, dropdown_menu_widget_id, dropdown_menu_values, command, wrapper_widget, attach_widget, dropdown_menu_width=None, dropdown_menu_height=None, max_display_length=None): + def createDropdownMenuBox(self, dropdown_menu_widget_id, dropdown_menu_values, command, wrapper_widget, attach_widget, dropdown_menu_min_width=None, dropdown_menu_height=None, max_display_length=None): self.attach_widget = attach_widget self.wrapper_widget = wrapper_widget - self.update() - self.new_width = dropdown_menu_width if dropdown_menu_width is not None else self.attach_widget.winfo_width() + + self.new_width = dropdown_menu_min_width if dropdown_menu_min_width is not None else self.dropdown_menu_default_min_width self.new_height = dropdown_menu_height if dropdown_menu_height is not None else self.init_height self.max_display_length = max_display_length if max_display_length is not None else self.init_max_display_length @@ -151,7 +153,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): wrapper_widget=wrapper_widget, attach_widget=attach_widget, dropdown_menu_settings=SimpleNamespace( - dropdown_menu_width=dropdown_menu_width, + dropdown_menu_min_width=dropdown_menu_min_width, dropdown_menu_height=dropdown_menu_height, max_display_length=max_display_length, ), @@ -166,6 +168,7 @@ class _CreateDropdownMenuWindow(CTkToplevel): def _createDropdownMenuValues(self, dropdown_menu_widget_id, dropdown_menu_values, command): + longest_text = getLongestText(dropdown_menu_values) self.dropdown_menu_values_wrapper = CTkFrame(self.scroll_frame_container, corner_radius=0, fg_color=self.window_bg_color) self.dropdown_menu_values_wrapper.grid(row=0, column=0, sticky="nsew") self.dropdown_menu_values_wrapper.grid_columnconfigure(0, weight=1) @@ -177,10 +180,10 @@ class _CreateDropdownMenuWindow(CTkToplevel): __dropdown_menu_value_wrapper.grid_rowconfigure((0,2), weight=1) - __dropdown_menu_value_wrapper.grid_columnconfigure(0, weight=1) + # __dropdown_menu_value_wrapper.grid_columnconfigure(0, weight=1) __label_widget = CTkLabel( __dropdown_menu_value_wrapper, - text="Aa", + text=longest_text, height=0, corner_radius=0, font=CTkFont(family=self.settings.FONT_FAMILY, size=self.value_font_size, weight="normal"), @@ -190,7 +193,13 @@ class _CreateDropdownMenuWindow(CTkToplevel): # setattr(self, f"l", __label_widget) __label_widget.grid(row=1, column=0, padx=self.value_ipadx, pady=self.value_ipady, sticky="w") + label_height = getLatestHeight(__dropdown_menu_value_wrapper) + label_width = getLatestWidth(__label_widget) + label_width += self.scroll_frame_container._scrollbar.winfo_width() + (self.window_border_width*2) + (self.scrollbar_ipadx[0] + self.scrollbar_ipadx[1]) + if label_width > self.new_width: + additional_width = int(label_width - self.new_width) + self.new_width += additional_width # for fixing 1px bug if isEven(label_height) is False: @@ -223,7 +232,6 @@ class _CreateDropdownMenuWindow(CTkToplevel): dropdown_menu_value_wrapper.grid_rowconfigure((0,2), weight=1) - dropdown_menu_value_wrapper.grid_columnconfigure(0, weight=1) label_widget = CTkLabel( dropdown_menu_value_wrapper, text=dropdown_menu_value, diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index e5f7ecd7..ed7d28a8 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -96,7 +96,7 @@ class _SettingBoxGenerator(): for_var_label_text, for_var_desc_text, optionmenu_attr_name, command, - dropdown_menu_width=None, + dropdown_menu_min_width=None, dropdown_menu_values=None, variable=None, ): @@ -144,7 +144,7 @@ class _SettingBoxGenerator(): command=adjustedCommand, wrapper_widget=self.config_window.main_bg_container, attach_widget=option_menu_widget, - dropdown_menu_width=dropdown_menu_width, + dropdown_menu_min_width=dropdown_menu_min_width, ) return setting_box_frame diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py index b2058340..cc33684c 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_appearance/createSettingBox_Appearance.py @@ -70,7 +70,6 @@ def createSettingBox_Appearance(setting_box_wrapper, config_window, settings, vi for_var_desc_text=view_variable.VAR_DESC_FONT_FAMILY, optionmenu_attr_name="sb__optionmenu_font_family", dropdown_menu_values=view_variable.LIST_FONT_FAMILY, - dropdown_menu_width=settings.uism.RESPONSIVE_UI_SIZE_INT_300, command=lambda value: optionmenu_font_family_callback(value), variable=view_variable.VAR_FONT_FAMILY, ) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py index 405c8df6..472e5baa 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_transcription/createSettingBox_Mic.py @@ -58,7 +58,6 @@ def createSettingBox_Mic(setting_box_wrapper, config_window, settings, view_vari for_var_desc_text=view_variable.VAR_DESC_MIC_DEVICE, optionmenu_attr_name="sb__optionmenu_mic_device", dropdown_menu_values=view_variable.LIST_MIC_DEVICE, - dropdown_menu_width=settings.uism.RESPONSIVE_UI_SIZE_INT_500, command=lambda value: optionmenu_input_mic_device_callback(value), variable=view_variable.VAR_MIC_DEVICE, ) diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 79961dd6..9b543704 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -243,6 +243,7 @@ class UiScalingManager(): self.config_window.SB__DROPDOWN_MENU_VALUE_IPADY = (self._calculateUiSize(6), self._calculateUiSize(6)) self.config_window.SB__DROPDOWN_MENU_VALUE_PADY = (0, self._calculateUiSize(1, is_allowed_odd=True)) self.config_window.SB__DROPDOWN_MENU_VALUE_FONT_SIZE = self._calculateUiSize(14) + self.config_window.SB__DROPDOWN_MENU_VALUE_DEFAULT_MIN_WIDTH = self._calculateUiSize(200) self.config_window.SB__SWITCH_WIDTH = self._calculateUiSize(50) diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index 683d4f06..f9a10fec 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -81,6 +81,7 @@ class VRCT_GUI(CTk): value_ipady=self.settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_IPADY, value_pady=self.settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_PADY, value_font_size=self.settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_FONT_SIZE, + dropdown_menu_default_min_width=self.settings.config_window.uism.SB__DROPDOWN_MENU_VALUE_DEFAULT_MIN_WIDTH, window_bg_color=self.settings.config_window.ctm.SB__DROPDOWN_MENU_WINDOW_BG_COLOR, window_border_color=self.settings.config_window.ctm.SB__DROPDOWN_MENU_WINDOW_BORDER_COLOR, From 7aefe652e58f700af8edb5a605e6c9d899849507 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 19 Oct 2023 15:36:04 +0900 Subject: [PATCH 338/355] [Update] Config Window: Restore DeepL Auth Key UI --- locales/en.yml | 6 ++--- locales/ja.yml | 6 ++--- view.py | 10 +++---- .../createSideMenuAndSettingsBoxContainers.py | 26 +++++++++---------- .../createSettingBox_Translation.py | 20 +++++++++++++- 5 files changed, 43 insertions(+), 25 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 3c1dc124..4fed8935 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -71,7 +71,7 @@ config_window: side_menu_labels: appearance: Appearance - # translation: Translation + translation: Translation transcription: Transcription transcription_mic: Mic transcription_speaker: Speaker @@ -96,8 +96,8 @@ config_window: ui_language: label: UI Language - # deepl_auth_key: - # label: DeepL Auth Key + deepl_auth_key: + label: DeepL Auth Key mic_host: label: Mic Host/Driver diff --git a/locales/ja.yml b/locales/ja.yml index dac3a0b2..dff1b932 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -72,7 +72,7 @@ config_window: side_menu_labels: appearance: デザイン - # translation: 翻訳 + translation: 翻訳 transcription: 音声認識 transcription_mic: マイク transcription_speaker: スピーカー @@ -97,8 +97,8 @@ config_window: ui_language: label: UIの言語 / UI Language - # deepl_auth_key: - # label: DeepL 認証キー + deepl_auth_key: + label: DeepL 認証キー mic_host: label: マイク(ホスト/ドライバー) diff --git a/view.py b/view.py index 21e07334..c718eedf 100644 --- a/view.py +++ b/view.py @@ -223,10 +223,10 @@ class View(): # Translation Tab - # VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value=i18n.t("config_window.deepl_auth_key.label")), - # VAR_DESC_DEEPL_AUTH_KEY=None, - # CALLBACK_SET_DEEPL_AUTH_KEY=None, - # VAR_DEEPL_AUTH_KEY=StringVar(value=""), + VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value=i18n.t("config_window.deepl_auth_key.label")), + VAR_DESC_DEEPL_AUTH_KEY=None, + CALLBACK_SET_DEEPL_AUTH_KEY=None, + VAR_DEEPL_AUTH_KEY=StringVar(value=""), # Transcription Tab (Mic) @@ -435,7 +435,7 @@ class View(): # Translation Tab - # self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window_registers.get("callback_set_deepl_authkey", None) + self.view_variable.CALLBACK_SET_DEEPL_AUTHKEY = config_window_registers.get("callback_set_deepl_authkey", None) # Transcription Tab (Mic) self.view_variable.CALLBACK_SET_MIC_HOST = config_window_registers.get("callback_set_mic_host", None) diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py index 5fe0da61..f9694a1a 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/createSideMenuAndSettingsBoxContainers.py @@ -10,7 +10,7 @@ from .setting_box_containers.setting_box_appearance import createSettingBox_Appe from .setting_box_containers.setting_box_transcription import createSettingBox_Mic, createSettingBox_Speaker from .setting_box_containers.setting_box_others import createSettingBox_Others from .setting_box_containers.setting_box_advanced_settings import createSettingBox_AdvancedSettings -# from .setting_box_containers.setting_box_translation import createSettingBox_Translation +from .setting_box_containers.setting_box_translation import createSettingBox_Translation def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variable): @@ -66,18 +66,18 @@ def createSideMenuAndSettingsBoxContainers(config_window, settings, view_variabl ] }, }, - # { - # "side_menu_tab_attr_name": "side_menu_tab_translation", - # "label_attr_name": "label_translation", - # "selected_mark_attr_name": "selected_mark_translation", - # "textvariable": view_variable.VAR_SIDE_MENU_LABEL_TRANSLATION, - # "setting_box_container_settings": { - # "setting_box_container_attr_name": "setting_box_container_translation", - # "setting_boxes": [ - # { "var_section_title": None, "setting_box": createSettingBox_Translation }, - # ] - # }, - # }, + { + "side_menu_tab_attr_name": "side_menu_tab_translation", + "label_attr_name": "label_translation", + "selected_mark_attr_name": "selected_mark_translation", + "textvariable": view_variable.VAR_SIDE_MENU_LABEL_TRANSLATION, + "setting_box_container_settings": { + "setting_box_container_attr_name": "setting_box_container_translation", + "setting_boxes": [ + { "var_section_title": None, "setting_box": createSettingBox_Translation }, + ] + }, + }, { "side_menu_tab_attr_name": "side_menu_tab_transcription", "label_attr_name": "label_transcription", diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py index 461b4aea..d2975d0d 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/setting_box_translation/createSettingBox_Translation.py @@ -3,4 +3,22 @@ from utils import callFunctionIfCallable from .._SettingBoxGenerator import _SettingBoxGenerator def createSettingBox_Translation(setting_box_wrapper, config_window, settings, view_variable): - pass \ No newline at end of file + sbg = _SettingBoxGenerator(setting_box_wrapper, config_window, settings, view_variable) + createSettingBoxEntry = sbg.createSettingBoxEntry + + + def deepl_authkey_callback(value): + callFunctionIfCallable(view_variable.CALLBACK_SET_DEEPL_AUTHKEY, value) + + + row=0 + config_window.sb__deepl_authkey = createSettingBoxEntry( + for_var_label_text=view_variable.VAR_LABEL_DEEPL_AUTH_KEY, + for_var_desc_text=view_variable.VAR_DESC_DEEPL_AUTH_KEY, + entry_attr_name="sb__entry_deepl_authkey", + entry_width=settings.uism.RESPONSIVE_UI_SIZE_INT_300, + entry_bind__Any_KeyRelease=lambda value: deepl_authkey_callback(value), + entry_textvariable=view_variable.VAR_DEEPL_AUTH_KEY, + ) + config_window.sb__deepl_authkey.grid(row=row, pady=0) + row+=1 \ No newline at end of file From bfe177aa1706aad87fb58d73ab0978cd8a46f5e1 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 19 Oct 2023 15:39:39 +0900 Subject: [PATCH 339/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20Auth?= =?UTF-8?q?=20key=E3=81=AE=E5=BE=A9=E6=97=A7=E4=BD=9C=E6=A5=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 19 ++++++ controller.py | 36 ++++++++++ model.py | 50 ++++++++++++++ models/translation/translation_languages.py | 72 +++++++++++++++++++- models/translation/translation_translator.py | 34 ++++++++- requirements.txt | 1 + view.py | 2 +- 7 files changed, 211 insertions(+), 3 deletions(-) diff --git a/config.py b/config.py index f8953bd3..a684e649 100644 --- a/config.py +++ b/config.py @@ -415,6 +415,19 @@ class Config: self._OSC_PORT = value saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, value) + @property + @json_serializable('AUTH_KEYS') + def AUTH_KEYS(self): + return self._AUTH_KEYS + + @AUTH_KEYS.setter + def AUTH_KEYS(self, value): + if type(value) is dict and set(value.keys()) == set(self.AUTH_KEYS.keys()): + for key, value in value.items(): + if type(value) is str: + self._AUTH_KEYS[key] = value + saveJson(self.PATH_CONFIG, inspect.currentframe().f_code.co_name, self.AUTH_KEYS) + @property @json_serializable('MESSAGE_FORMAT') def MESSAGE_FORMAT(self): @@ -550,6 +563,12 @@ class Config: self._INPUT_SPEAKER_MAX_PHRASES = 10 self._OSC_IP_ADDRESS = "127.0.0.1" self._OSC_PORT = 9000 + self._AUTH_KEYS = { + "DeepL_API": None, + "DeepL": None, + "Bing": None, + "Google": None, + } self._MESSAGE_FORMAT = "[message]([translation])" self._ENABLE_AUTO_CLEAR_MESSAGE_BOX = True self._ENABLE_NOTICE_XSOVERLAY = False diff --git a/controller.py b/controller.py index 228e55ed..d6c6bdb4 100644 --- a/controller.py +++ b/controller.py @@ -22,6 +22,8 @@ def sendMicMessage(message): return elif config.ENABLE_TRANSLATION is False: pass + elif model.getTranslatorStatus() is False: + view.printToTextbox_AuthenticationError() else: translation = model.getInputTranslate(message) @@ -85,6 +87,8 @@ def receiveSpeakerMessage(message): translation = "" if config.ENABLE_TRANSLATION is False: pass + elif model.getTranslatorStatus() is False: + view.printToTextbox_AuthenticationError() else: translation = model.getOutputTranslate(message) @@ -150,6 +154,8 @@ def sendChatMessage(message): translation = "" if config.ENABLE_TRANSLATION is False: pass + elif model.getTranslatorStatus() is False: + view.printToTextbox_AuthenticationError() else: translation = model.getInputTranslate(message) @@ -206,6 +212,7 @@ def initSetLanguageAndCountry(): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) def setYourLanguageAndCountry(select): languages = config.SELECTED_TAB_YOUR_LANGUAGES @@ -215,6 +222,7 @@ def setYourLanguageAndCountry(select): config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) view.printToTextbox_selectedYourLanguages(select) def setTargetLanguageAndCountry(select): @@ -225,6 +233,7 @@ def setTargetLanguageAndCountry(select): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) view.printToTextbox_selectedTargetLanguages(select) def callbackSelectedLanguagePresetTab(selected_tab_no): @@ -241,8 +250,11 @@ def callbackSelectedLanguagePresetTab(selected_tab_no): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) view.printToTextbox_changedLanguagePresetTab(config.SELECTED_TAB_NO) +def callbackSetAuthKeys(keys): + config.AUTH_KEYS = keys # command func def callbackToggleTranslation(is_turned_on): @@ -362,6 +374,21 @@ def callbackSetUiLanguage(value): config.UI_LANGUAGE = value view.showRestartButtonIfRequired(locale=config.UI_LANGUAGE) +# Translation Tab +def callbackSetDeeplAuthkey(value): + print("callbackSetDeeplAuthkey", str(value)) + if len(value) > 0 and model.authenticationTranslator(callbackSetAuthKeys, choice_translator="DeepL(auth)", auth_key=value) is True: + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) + view.printToTextbox_AuthenticationSuccess() + elif len(value) == 0: + auth_keys = config.AUTH_KEYS + auth_keys["DeepL(auth)"] = None + config.AUTH_KEYS = auth_keys + model.authenticationTranslator(callbackSetAuthKeys) + else: + view.printToTextbox_AuthenticationError() + # Transcription Tab (Mic) def callbackSetMicHost(value): print("callbackSetMicHost", value) @@ -607,6 +634,12 @@ def createMainWindow(): # init config initSetLanguageAndCountry() + if model.authenticationTranslator(callbackSetAuthKeys) is False: + # error update Auth key + view.printToTextbox_AuthenticationError() + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) + model.authenticationTranslator(callbackSetAuthKeys) + # set word filter model.addKeywords() @@ -666,6 +699,9 @@ def createMainWindow(): "callback_set_font_family": callbackSetFontFamily, "callback_set_ui_language": callbackSetUiLanguage, + # Translation Tab + "callback_set_deepl_authkey": callbackSetDeeplAuthkey, + # Transcription Tab (Mic) "callback_set_mic_host": callbackSetMicHost, "list_mic_host": model.getListInputHost(), diff --git a/model.py b/model.py index da78a9a5..68b48e81 100644 --- a/model.py +++ b/model.py @@ -81,6 +81,22 @@ class Model: del self.keyword_processor self.keyword_processor = KeywordProcessor() + def authenticationTranslator(self, fnc, choice_translator=None, auth_key=None): + if choice_translator == None: + choice_translator = config.CHOICE_TRANSLATOR + if auth_key == None: + auth_key = config.AUTH_KEYS[choice_translator] + + result = self.translator.authentication(choice_translator, auth_key) + if result: + auth_keys = config.AUTH_KEYS + auth_keys[choice_translator] = auth_key + try: + fnc(auth_keys) + except: + pass + return result + def startLogger(self): os_makedirs(os_path.join(os_path.dirname(sys.argv[0]), "logs"), exist_ok=True) logger = getLogger() @@ -121,10 +137,32 @@ class Model: if source_lang in source_languages and target_lang in target_languages: compatible_engines.append(engine) engine_name = compatible_engines[0] + + if engine_name == "DeepL" and config.AUTH_KEYS["DeepL_API"] != None: + engine_name = "DeepL" + return engine_name + def getTranslatorStatus(self): + return self.translator.translator_status[config.CHOICE_TRANSLATOR] + + def getListTranslatorName(self): + return list(self.translator.translator_status.keys()) + def getInputTranslate(self, message): try: + if config.CHOICE_TRANSLATOR == "DeepL_API": + if config.TARGET_LANGUAGE == "English": + if config.TARGET_COUNTRY in ["United States", "Canada", "Philippines"]: + config.TARGET_LANGUAGE = "English American" + else: + config.TARGET_LANGUAGE = "English British" + elif config.TARGET_LANGUAGE in ["Portuguese"]: + if config.TARGET_COUNTRY == "Portugal": + config.TARGET_LANGUAGE = "Portuguese European" + else: + config.TARGET_LANGUAGE = "Portuguese Brazilian" + translation = self.translator.translate( translator_name=config.CHOICE_TRANSLATOR, source_language=config.SOURCE_LANGUAGE, @@ -137,6 +175,18 @@ class Model: def getOutputTranslate(self, message): try: + if config.CHOICE_TRANSLATOR == "DeepL_API": + if config.SOURCE_LANGUAGE == "English": + if config.SOURCE_COUNTRY in ["United States", "Canada", "Philippines"]: + config.SOURCE_LANGUAGE = "English American" + else: + config.SOURCE_LANGUAGE = "English British" + elif config.SOURCE_LANGUAGE in ["Portuguese"]: + if config.SOURCE_COUNTRY == "Portugal": + config.SOURCE_LANGUAGE = "Portuguese European" + else: + config.SOURCE_LANGUAGE = "Portuguese Brazilian" + translation = self.translator.translate( translator_name=config.CHOICE_TRANSLATOR, source_language=config.TARGET_LANGUAGE, diff --git a/models/translation/translation_languages.py b/models/translation/translation_languages.py index f51b12a1..ae57d4cc 100644 --- a/models/translation/translation_languages.py +++ b/models/translation/translation_languages.py @@ -1,4 +1,4 @@ -translatorEngine = ["DeepL", "Google", "Bing"] +translatorEngine = ["DeepL", "DeepL_API", "Google", "Bing"] translation_lang = {} dict_deepl_languages = { "Japanese":"JA", @@ -36,6 +36,76 @@ translation_lang["DeepL"] = { "target":dict_deepl_languages, } +dict_deepl_api_source_languages = { + "Japanese":"ja", + "English":"en", + "Bulgarian":"bg", + "Czech":"cs", + "Danish":"da", + "German":"de", + "Greek":"el", + "Spanish":"es", + "Estonian":"et", + "Finnish":"fi", + "French":"fr", + "Hungarian":"hu", + "Indonesian":"id", + "Italian":"it", + "Korean":"ko", + "Lithuanian":"lt", + "Latvian":"lv", + "Norwegian":"nb", + "Dutch":"nl", + "Polish":"pl", + "Portuguese":"pt", + "Romanian":"ro", + "Russian":"ru", + "Slovak":"sk", + "Slovenian":"sl", + "Swedish":"sv", + "Turkish":"tr", + "Ukrainian":"uk", + "Chinese":"zh" +} +dict_deepl_api_target_languages = { + "Japanese":"ja", + "English American":"en-US", + "English British":"en-GB", + "Bulgarian":"bg", + "Czech":"cs", + "Danish":"da", + "German":"de", + "Greek":"el", + "English":"en", + "Spanish":"es", + "Estonian":"et", + "Finnish":"fi", + "French":"fr", + "Hungarian":"hu", + "Indonesian":"id", + "Italian":"it", + "Korean":"ko", + "Lithuanian":"lt", + "Latvian":"lv", + "Norwegian":"nb", + "Dutch":"nl", + "Polish":"pl", + "Portuguese Brazilian":"pt-BR", + "Portuguese European":"pt-PT", + "Romanian":"ro", + "Russian":"ru", + "Slovak":"sk", + "Slovenian":"sl", + "Swedish":"sv", + "Turkish":"tr", + "Ukrainian":"uk", + "Chinese":"zh" +} +translation_lang["DeepL_API"] = { + "source": dict_deepl_api_source_languages, + "target": dict_deepl_api_target_languages, +} + dict_google_languages = { "Japanese":"ja", "English":"en", diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index 872df21c..4f4a1550 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -1,3 +1,4 @@ +from deepl import Translator as deepl_Translator from deepl_translate import translate as deepl_web_Translator from translators import translate_text as other_web_Translator from .translation_languages import translatorEngine, translation_lang @@ -5,7 +6,32 @@ from .translation_languages import translatorEngine, translation_lang # Translator class Translator(): def __init__(self): - pass + self.translator_status = {} + for translator in translatorEngine: + self.translator_status[translator] = False + self.deepl_client = None + + def authentication(self, translator_name, authkey=None): + result = False + match translator_name: + case "DeepL": + self.translator_status[translator_name] = True + result = True + case "DeepL_API": + try: + self.deepl_client = deepl_Translator(authkey) + self.deepl_client.translate_text(" ", target_lang="EN-US") + self.translator_status[translator_name] = True + result = True + except: + self.translator_status[translator_name] = False + case "Google": + self.translator_status[translator_name] = True + result = True + case "Bing": + self.translator_status[translator_name] = True + result = True + return result def translate(self, translator_name, source_language, target_language, message): try: @@ -19,6 +45,12 @@ class Translator(): target_language=target_language, text=message ) + case "DeepL_API": + result = self.deepl_client.translate_text( + message, + source_lang=source_language, + target_lang=target_language, + ).text case "Google": result = other_web_Translator( query_text=message, diff --git a/requirements.txt b/requirements.txt index 386f2860..a399733d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,6 +2,7 @@ pillow == 10.0.0 PyAudioWPatch == 0.2.12.6 python-osc == 1.8.3 customtkinter == 5.2.0 +deepl == 1.15.0 flashtext == 2.7 pyyaml == 6.0.1 python-i18n == 0.3.9 \ No newline at end of file diff --git a/view.py b/view.py index e6c0ed51..77dea02c 100644 --- a/view.py +++ b/view.py @@ -226,7 +226,7 @@ class View(): # VAR_LABEL_DEEPL_AUTH_KEY=StringVar(value=i18n.t("config_window.deepl_auth_key.label")), # VAR_DESC_DEEPL_AUTH_KEY=None, # CALLBACK_SET_DEEPL_AUTH_KEY=None, - # VAR_DEEPL_AUTH_KEY=StringVar(value=""), + # VAR_DEEPL_AUTH_KEY=StringVar(value="config.AUTH_KEYS["DeepL_API"]"), # Transcription Tab (Mic) From 780bd32cf49a95c6ba44d59cc542b664dfea1596 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Thu, 19 Oct 2023 17:33:31 +0900 Subject: [PATCH 340/355] =?UTF-8?q?[bugfix]=20=E8=89=B2=E3=81=AE=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E6=BC=8F=E3=82=8C=E4=BF=AE=E6=AD=A3=E3=80=82OS?= =?UTF-8?q?=E3=81=AE=E8=A8=AD=E5=AE=9A=E3=81=A7=E3=83=A9=E3=82=A4=E3=83=88?= =?UTF-8?q?=E3=83=86=E3=83=BC=E3=83=9E=E3=81=AB=E3=81=97=E3=81=A6=E3=81=84?= =?UTF-8?q?=E3=82=8B=E5=A0=B4=E5=90=88=E3=81=AB=E3=80=81=E8=89=B2=E6=8C=87?= =?UTF-8?q?=E5=AE=9A=E3=81=97=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E9=83=A8?= =?UTF-8?q?=E5=88=86=E3=81=8C=E8=87=AA=E5=8B=95=E7=9A=84=E3=81=AB=E3=83=A9?= =?UTF-8?q?=E3=82=A4=E3=83=88=E3=83=86=E3=83=BC=E3=83=9E=E7=94=A8(Tkinter?= =?UTF-8?q?=E5=81=B4=E3=81=A7)=E3=81=AB=E6=8C=87=E5=AE=9A=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=A6=E3=81=97=E3=81=BE=E3=81=86=E3=81=AE=E3=81=A7?= =?UTF-8?q?=E6=8C=87=E5=AE=9A=E6=BC=8F=E3=82=8C=E3=81=8C=E3=81=82=E3=81=A3?= =?UTF-8?q?=E3=81=9F=E9=83=A8=E5=88=86=E3=82=92=E6=8C=87=E5=AE=9A=E3=80=82?= =?UTF-8?q?=E3=81=9D=E3=81=AE=E9=9A=9B=E3=81=ABVRCT=E4=B8=8A=E3=81=AE?= =?UTF-8?q?=E3=83=80=E3=83=BC=E3=82=AF=E3=83=86=E3=83=BC=E3=83=9E=E3=81=A7?= =?UTF-8?q?=E3=81=AE=E8=89=B2=E3=81=8C=E4=B8=80=E9=83=A8=E5=BE=AE=E5=A6=99?= =?UTF-8?q?=E3=81=AB=E5=A4=89=E3=82=8F=E3=81=A3=E3=81=A6=E3=81=84=E3=82=8B?= =?UTF-8?q?=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../_createSettingBoxCompactModeButton.py | 4 +- .../_SettingBoxGenerator.py | 12 + .../_create_sidebar/createSidebarFeatures.py | 2 + vrct_gui/ui_managers/ColorThemeManager.py | 236 ++++++++++-------- 4 files changed, 145 insertions(+), 109 deletions(-) diff --git a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py index 7f56af52..80a5d152 100644 --- a/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py +++ b/vrct_gui/config_window/widgets/createSettingBoxTopBar/_createSettingBoxCompactModeButton.py @@ -56,9 +56,11 @@ def _createSettingBoxCompactModeButton(parent_widget, config_window, settings, v onvalue=True, offvalue=False, command=switchConfigWindowCompactMode, - # fg_color="", + fg_color=settings.ctm.COMPACT_MODE_SWITCH_BOX_BG_COLOR, # bg_color="red", progress_color=settings.ctm.COMPACT_MODE_SWITCH_BOX_ACTIVE_BG_COLOR, + button_color=settings.ctm.COMPACT_MODE_SWITCH_BOX_BUTTON_COLOR, + button_hover_color=settings.ctm.COMPACT_MODE_SWITCH_BOX_BUTTON_HOVERED_COLOR, ) config_window.setting_box_compact_mode_switch_box.grid(row=0, column=0) \ No newline at end of file diff --git a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py index ed7d28a8..f11c56b5 100644 --- a/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py +++ b/vrct_gui/config_window/widgets/createSideMenuAndSettingsBoxContainers/setting_box_containers/_SettingBoxGenerator.py @@ -177,6 +177,8 @@ class _SettingBoxGenerator(): fg_color=self.settings.ctm.SB__SWITCH_BOX_BG_COLOR, # bg_color="red", progress_color=self.settings.ctm.SB__SWITCH_BOX_ACTIVE_BG_COLOR, + button_color=self.settings.ctm.SB__SWITCH_BOX_BUTTON_COLOR, + button_hover_color=self.settings.ctm.SB__SWITCH_BOX_BUTTON_HOVERED_COLOR, ) setattr(self.config_window, switch_attr_name, switch_widget) @@ -249,6 +251,8 @@ class _SettingBoxGenerator(): from_=slider_range[0], to=slider_range[1], number_of_steps=slider_number_of_steps, + fg_color=self.settings.ctm.SB__SLIDER_BG_COLOR, + progress_color=self.settings.ctm.SB__SLIDER_PROGRESS_BG_COLOR, button_color=self.settings.ctm.SB__SLIDER_BUTTON_COLOR, button_hover_color=self.settings.ctm.SB__SLIDER_BUTTON_HOVERED_COLOR, command=command, @@ -302,6 +306,9 @@ class _SettingBoxGenerator(): setting_box_item_frame.grid_columnconfigure(1, weight=1) entry_widget = CTkEntry( setting_box_item_frame, + text_color=self.settings.ctm.SB__ENTRY_TEXT_COLOR, + fg_color=self.settings.ctm.SB__ENTRY_BG_COLOR, + border_color=self.settings.ctm.SB__ENTRY_BORDER_COLOR, width=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_WIDTH, height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT, textvariable=entry_variable, @@ -348,6 +355,8 @@ class _SettingBoxGenerator(): setting_box_item_frame, height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__PROGRESSBAR_HEIGHT, corner_radius=0, + fg_color=self.settings.ctm.SB__PROGRESSBAR_X_SLIDER__PROGRESSBAR_BG_COLOR, + progress_color=self.settings.ctm.SB__PROGRESSBAR_X_SLIDER__PROGRESSBAR_PROGRESS_BG_COLOR, ) setattr(self.config_window, progressbar_attr_name, progressbar_widget) progressbar_widget.grid(row=1, column=1, padx=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__BAR_PADX, sticky="ew") @@ -400,6 +409,9 @@ class _SettingBoxGenerator(): entry_widget = CTkEntry( setting_box_item_frame, + text_color=self.settings.ctm.SB__ENTRY_TEXT_COLOR, + fg_color=self.settings.ctm.SB__ENTRY_BG_COLOR, + border_color=self.settings.ctm.SB__ENTRY_BORDER_COLOR, width=entry_width, height=self.settings.uism.SB__PROGRESSBAR_X_SLIDER__ENTRY_HEIGHT, textvariable=entry_textvariable, diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py index ad5e09a5..9aaf7cef 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py @@ -213,6 +213,8 @@ def createSidebarFeatures(settings, main_window, view_variable): fg_color=settings.ctm.SF__SWITCH_BOX_BG_COLOR, bg_color=settings.ctm.SF__BG_COLOR, progress_color=settings.ctm.SF__SWITCH_BOX_ACTIVE_BG_COLOR, + button_color=settings.ctm.SF__SWITCH_BOX_BUTTON_COLOR, + # button_hover_color=settings.ctm.SF__SWITCH_BOX_BUTTON_HOVERED_COLOR, ) setattr(main_window, switch_box_attr_name, switch_box_widget) diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index 6c3ffdb9..f0a97a9e 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -40,6 +40,7 @@ class ColorThemeManager(): self.DARK_100_COLOR = "#f5f7fb" self.DARK_200_COLOR = "#f1f2f6" self.DARK_300_COLOR = "#e9eaee" + self.DARK_350_COLOR = "#d8d9dd" self.DARK_400_COLOR = "#c7c8cc" self.DARK_450_COLOR = "#b8b9bd" self.DARK_500_COLOR = "#a9aaae" @@ -52,6 +53,7 @@ class ColorThemeManager(): self.DARK_800_COLOR = "#4b4c4f" self.DARK_825_COLOR = "#434447" self.DARK_850_COLOR = "#3a3b3e" + self.DARK_863_COLOR = "#36373a" self.DARK_875_COLOR = "#323336" self.DARK_888_COLOR = "#2e2f32" self.DARK_900_COLOR = "#292a2d" @@ -140,6 +142,10 @@ class ColorThemeManager(): self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR = self.PRIMARY_700_COLOR self.main.SF__SWITCH_BOX_DISABLE_BG_COLOR = self.PRIMARY_800_COLOR + self.main.SF__SWITCH_BOX_BUTTON_COLOR = self.DARK_400_COLOR + # It's not working because It overrode internally. + self.main.SF__SWITCH_BOX_BUTTON_HOVERED_COLOR = self.DARK_350_COLOR + self.main.SF__SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR self.main.SF__SELECTED_MARK_ACTIVE_HOVERED_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR self.main.SF__SELECTED_MARK_ACTIVE_CLICKED_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR @@ -249,8 +255,10 @@ class ColorThemeManager(): # Compact Mode - self.config_window.COMPACT_MODE_SWITCH_BOX_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR - + self.config_window.COMPACT_MODE_SWITCH_BOX_BG_COLOR = self.DARK_775_COLOR + self.config_window.COMPACT_MODE_SWITCH_BOX_ACTIVE_BG_COLOR = self.PRIMARY_500_COLOR + self.config_window.COMPACT_MODE_SWITCH_BOX_BUTTON_COLOR = self.DARK_350_COLOR + self.config_window.COMPACT_MODE_SWITCH_BOX_BUTTON_HOVERED_COLOR = self.DARK_300_COLOR # Main self.config_window.MAIN_BG_COLOR = self.DARK_950_COLOR @@ -270,17 +278,29 @@ class ColorThemeManager(): self.config_window.SB__DROPDOWN_MENU_HOVERED_BG_COLOR = self.DARK_800_COLOR self.config_window.SB__DROPDOWN_MENU_CLICKED_BG_COLOR = self.DARK_900_COLOR + self.config_window.SB__SLIDER_BG_COLOR = self.DARK_700_COLOR + self.config_window.SB__SLIDER_PROGRESS_BG_COLOR = self.DARK_500_COLOR self.config_window.SB__SLIDER_BUTTON_COLOR = self.DARK_700_COLOR self.config_window.SB__SLIDER_BUTTON_HOVERED_COLOR = self.DARK_600_COLOR - self.config_window.SB__SWITCH_BOX_BG_COLOR = self.main.SF__SWITCH_BOX_BG_COLOR - self.config_window.SB__SWITCH_BOX_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR + self.config_window.SB__SWITCH_BOX_BG_COLOR = self.DARK_800_COLOR + self.config_window.SB__SWITCH_BOX_ACTIVE_BG_COLOR = self.PRIMARY_500_COLOR + self.config_window.SB__SWITCH_BOX_BUTTON_COLOR = self.DARK_400_COLOR + self.config_window.SB__SWITCH_BOX_BUTTON_HOVERED_COLOR = self.DARK_350_COLOR - self.config_window.SB__CHECKBOX_BORDER_COLOR = self.DARK_500_COLOR + self.config_window.SB__CHECKBOX_BORDER_COLOR = self.DARK_600_COLOR self.config_window.SB__CHECKBOX_HOVER_COLOR = self.DARK_800_COLOR self.config_window.SB__CHECKBOX_CHECKED_COLOR = self.PRIMARY_700_COLOR self.config_window.SB__CHECKBOX_CHECKMARK_COLOR = self.config_window.BASIC_TEXT_COLOR + self.config_window.SB__ENTRY_TEXT_COLOR = self.DARK_300_COLOR + self.config_window.SB__ENTRY_BG_COLOR = self.DARK_863_COLOR + self.config_window.SB__ENTRY_BORDER_COLOR = self.DARK_775_COLOR + + + self.config_window.SB__PROGRESSBAR_X_SLIDER__PROGRESSBAR_BG_COLOR = self.DARK_800_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__PROGRESSBAR_PROGRESS_BG_COLOR = self.PRIMARY_400_COLOR + self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_COLOR = self.PRIMARY_600_COLOR self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_HOVERED_COLOR = self.PRIMARY_400_COLOR self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR = self.DARK_800_COLOR @@ -322,144 +342,144 @@ class ColorThemeManager(): - def _createLightModeColor(self): - # Common - self.main.BASIC_TEXT_COLOR = self.DARK_1000_COLOR - self.main.LABELS_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + # def _createLightModeColor(self): + # # Common + # self.main.BASIC_TEXT_COLOR = self.DARK_1000_COLOR + # self.main.LABELS_TEXT_COLOR = self.main.BASIC_TEXT_COLOR - # Main - self.main.MAIN_BG_COLOR = self.LIGHT_300_COLOR + # # Main + # self.main.MAIN_BG_COLOR = self.LIGHT_300_COLOR - self.main.TEXTBOX_BG_COLOR = self.LIGHT_200_COLOR - self.main.TEXTBOX_TEXT_COLOR = self.main.BASIC_TEXT_COLOR - self.main.TEXTBOX_TAB_BG_PASSIVE_COLOR = self.LIGHT_350_COLOR - self.main.TEXTBOX_TAB_BG_ACTIVE_COLOR = self.main.TEXTBOX_BG_COLOR - self.main.TEXTBOX_TAB_BG_HOVERED_COLOR = self.LIGHT_300_COLOR - self.main.TEXTBOX_TAB_BG_CLICKED_COLOR = self.LIGHT_350_COLOR - self.main.TEXTBOX_TAB_TEXT_ACTIVE_COLOR = self.main.BASIC_TEXT_COLOR - self.main.TEXTBOX_TAB_TEXT_PASSIVE_COLOR = self.LIGHT_600_COLOR + # self.main.TEXTBOX_BG_COLOR = self.LIGHT_200_COLOR + # self.main.TEXTBOX_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + # self.main.TEXTBOX_TAB_BG_PASSIVE_COLOR = self.LIGHT_350_COLOR + # self.main.TEXTBOX_TAB_BG_ACTIVE_COLOR = self.main.TEXTBOX_BG_COLOR + # self.main.TEXTBOX_TAB_BG_HOVERED_COLOR = self.LIGHT_300_COLOR + # self.main.TEXTBOX_TAB_BG_CLICKED_COLOR = self.LIGHT_350_COLOR + # self.main.TEXTBOX_TAB_TEXT_ACTIVE_COLOR = self.main.BASIC_TEXT_COLOR + # self.main.TEXTBOX_TAB_TEXT_PASSIVE_COLOR = self.LIGHT_600_COLOR - self.main.TEXTBOX_ENTRY_TEXT_COLOR = self.LIGHT_800_COLOR - self.main.TEXTBOX_ENTRY_TEXT_DISABLED_COLOR = self.LIGHT_500_COLOR - self.main.TEXTBOX_ENTRY_BG_COLOR = self.LIGHT_325_COLOR - self.main.TEXTBOX_ENTRY_BORDER_COLOR = self.LIGHT_400_COLOR - self.main.TEXTBOX_ENTRY_PLACEHOLDER_COLOR = self.LIGHT_600_COLOR - self.main.TEXTBOX_ENTRY_PLACEHOLDER_DISABLED_COLOR = self.LIGHT_700_COLOR + # self.main.TEXTBOX_ENTRY_TEXT_COLOR = self.LIGHT_800_COLOR + # self.main.TEXTBOX_ENTRY_TEXT_DISABLED_COLOR = self.LIGHT_500_COLOR + # self.main.TEXTBOX_ENTRY_BG_COLOR = self.LIGHT_325_COLOR + # self.main.TEXTBOX_ENTRY_BORDER_COLOR = self.LIGHT_400_COLOR + # self.main.TEXTBOX_ENTRY_PLACEHOLDER_COLOR = self.LIGHT_600_COLOR + # self.main.TEXTBOX_ENTRY_PLACEHOLDER_DISABLED_COLOR = self.LIGHT_700_COLOR - # Sidebar - self.main.SIDEBAR_BG_COLOR = self.LIGHT_350_COLOR + # # Sidebar + # self.main.SIDEBAR_BG_COLOR = self.LIGHT_350_COLOR - # Sidebar Features - self.main.SF__BG_COLOR = self.LIGHT_375_COLOR - self.main.SF__HOVERED_BG_COLOR = self.LIGHT_300_COLOR - self.main.SF__CLICKED_BG_COLOR = self.LIGHT_200_COLOR - self.main.SF__TEXT_DISABLED_COLOR = self.LIGHT_500_COLOR + # # Sidebar Features + # self.main.SF__BG_COLOR = self.LIGHT_375_COLOR + # self.main.SF__HOVERED_BG_COLOR = self.LIGHT_300_COLOR + # self.main.SF__CLICKED_BG_COLOR = self.LIGHT_200_COLOR + # self.main.SF__TEXT_DISABLED_COLOR = self.LIGHT_500_COLOR - self.main.SF__SWITCH_BOX_BG_COLOR = self.LIGHT_300_COLOR - self.main.SF__SWITCH_BOX_HOVERED_BG_COLOR = self.LIGHT_450_COLOR - self.main.SF__SWITCH_BOX_CLICKED_BG_COLOR = self.LIGHT_350_COLOR - self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR = self.PRIMARY_650_COLOR - self.main.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR = self.PRIMARY_500_COLOR - self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR = self.PRIMARY_700_COLOR - self.main.SF__SWITCH_BOX_DISABLE_BG_COLOR = self.PRIMARY_900_COLOR + # self.main.SF__SWITCH_BOX_BG_COLOR = self.LIGHT_300_COLOR + # self.main.SF__SWITCH_BOX_HOVERED_BG_COLOR = self.LIGHT_450_COLOR + # self.main.SF__SWITCH_BOX_CLICKED_BG_COLOR = self.LIGHT_350_COLOR + # self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR = self.PRIMARY_650_COLOR + # self.main.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR = self.PRIMARY_500_COLOR + # self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR = self.PRIMARY_700_COLOR + # self.main.SF__SWITCH_BOX_DISABLE_BG_COLOR = self.PRIMARY_900_COLOR - self.main.SF__SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR - self.main.SF__SELECTED_MARK_ACTIVE_HOVERED_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR - self.main.SF__SELECTED_MARK_ACTIVE_CLICKED_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR - self.main.SF__SELECTED_MARK_DISABLE_BG_COLOR = self.main.SF__SWITCH_BOX_DISABLE_BG_COLOR + # self.main.SF__SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR + # self.main.SF__SELECTED_MARK_ACTIVE_HOVERED_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_HOVERED_BG_COLOR + # self.main.SF__SELECTED_MARK_ACTIVE_CLICKED_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_CLICKED_BG_COLOR + # self.main.SF__SELECTED_MARK_DISABLE_BG_COLOR = self.main.SF__SWITCH_BOX_DISABLE_BG_COLOR - # Sidebar quick settings - self.main.SLS__TITLE_TEXT_COLOR = self.LIGHT_800_COLOR + # # Sidebar quick settings + # self.main.SLS__TITLE_TEXT_COLOR = self.LIGHT_800_COLOR - self.main.SLS__BG_COLOR = self.LIGHT_300_COLOR + # self.main.SLS__BG_COLOR = self.LIGHT_300_COLOR - self.main.SLS__PRESETS_TAB_BG_HOVERED_COLOR = self.LIGHT_350_COLOR - self.main.SLS__PRESETS_TAB_BG_CLICKED_COLOR = self.LIGHT_800_COLOR - self.main.SLS__PRESETS_TAB_BG_PASSIVE_COLOR = self.main.SIDEBAR_BG_COLOR - self.main.SLS__PRESETS_TAB_BG_ACTIVE_COLOR = self.main.SLS__BG_COLOR - self.main.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE = self.LIGHT_600_COLOR - self.main.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + # self.main.SLS__PRESETS_TAB_BG_HOVERED_COLOR = self.LIGHT_350_COLOR + # self.main.SLS__PRESETS_TAB_BG_CLICKED_COLOR = self.LIGHT_800_COLOR + # self.main.SLS__PRESETS_TAB_BG_PASSIVE_COLOR = self.main.SIDEBAR_BG_COLOR + # self.main.SLS__PRESETS_TAB_BG_ACTIVE_COLOR = self.main.SLS__BG_COLOR + # self.main.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR_PASSIVE = self.LIGHT_600_COLOR + # self.main.SLS__PRESETS_TAB_ACTIVE_TEXT_COLOR = self.main.BASIC_TEXT_COLOR - self.main.SLS__BOX_BG_COLOR = self.LIGHT_350_COLOR - self.main.SLS__BOX_SECTION_TITLE_TEXT_COLOR = self.LIGHT_800_COLOR - self.main.SLS__BOX_ARROWS_TEXT_COLOR = self.LIGHT_700_COLOR + # self.main.SLS__BOX_BG_COLOR = self.LIGHT_350_COLOR + # self.main.SLS__BOX_SECTION_TITLE_TEXT_COLOR = self.LIGHT_800_COLOR + # self.main.SLS__BOX_ARROWS_TEXT_COLOR = self.LIGHT_700_COLOR - self.main.SLS__OPTIONMENU_BG_COLOR = self.LIGHT_500_COLOR + # self.main.SLS__OPTIONMENU_BG_COLOR = self.LIGHT_500_COLOR - self.main.CONFIG_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR - self.main.CONFIG_BUTTON_HOVERED_BG_COLOR = self.LIGHT_800_COLOR - self.main.CONFIG_BUTTON_CLICKED_BG_COLOR = self.LIGHT_900_COLOR - # self.main.CONFIG_BUTTON_DISABLE_COLOR = self.LIGHT_900_COLOR + # self.main.CONFIG_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR + # self.main.CONFIG_BUTTON_HOVERED_BG_COLOR = self.LIGHT_800_COLOR + # self.main.CONFIG_BUTTON_CLICKED_BG_COLOR = self.LIGHT_900_COLOR + # # self.main.CONFIG_BUTTON_DISABLE_COLOR = self.LIGHT_900_COLOR - self.main.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR - self.main.MINIMIZE_SIDEBAR_BUTTON_HOVERED_BG_COLOR = self.LIGHT_800_COLOR - self.main.MINIMIZE_SIDEBAR_BUTTON_CLICKED_BG_COLOR = self.LIGHT_900_COLOR - # self.main.MINIMIZE_SIDEBAR_BUTTON_DISABLE_COLOR = self.LIGHT_900_COLOR + # self.main.MINIMIZE_SIDEBAR_BUTTON_BG_COLOR = self.main.SIDEBAR_BG_COLOR + # self.main.MINIMIZE_SIDEBAR_BUTTON_HOVERED_BG_COLOR = self.LIGHT_800_COLOR + # self.main.MINIMIZE_SIDEBAR_BUTTON_CLICKED_BG_COLOR = self.LIGHT_900_COLOR + # # self.main.MINIMIZE_SIDEBAR_BUTTON_DISABLE_COLOR = self.LIGHT_900_COLOR - self.main.HELP_AND_INFO_BUTTON_BG_COLOR = self.main.MAIN_BG_COLOR - self.main.HELP_AND_INFO_BUTTON_HOVERED_BG_COLOR = self.LIGHT_350_COLOR - self.main.HELP_AND_INFO_BUTTON_CLICKED_BG_COLOR = self.LIGHT_450_COLOR - # self.main.HELP_AND_INFO_BUTTON_DISABLE_COLOR = self.LIGHT_900_COLOR + # self.main.HELP_AND_INFO_BUTTON_BG_COLOR = self.main.MAIN_BG_COLOR + # self.main.HELP_AND_INFO_BUTTON_HOVERED_BG_COLOR = self.LIGHT_350_COLOR + # self.main.HELP_AND_INFO_BUTTON_CLICKED_BG_COLOR = self.LIGHT_450_COLOR + # # self.main.HELP_AND_INFO_BUTTON_DISABLE_COLOR = self.LIGHT_900_COLOR - # Common - self.config_window.BASIC_TEXT_COLOR = self.main.BASIC_TEXT_COLOR - self.config_window.LABELS_TEXT_COLOR = self.config_window.BASIC_TEXT_COLOR - self.config_window.LABELS_DESC_TEXT_COLOR = self.DARK_500_COLOR + # # Common + # self.config_window.BASIC_TEXT_COLOR = self.main.BASIC_TEXT_COLOR + # self.config_window.LABELS_TEXT_COLOR = self.config_window.BASIC_TEXT_COLOR + # self.config_window.LABELS_DESC_TEXT_COLOR = self.DARK_500_COLOR - # Top bar - self.config_window.TOP_BAR_BG_COLOR = self.DARK_850_COLOR + # # Top bar + # self.config_window.TOP_BAR_BG_COLOR = self.DARK_850_COLOR - # Main - self.config_window.MAIN_BG_COLOR = self.DARK_950_COLOR + # # Main + # self.config_window.MAIN_BG_COLOR = self.DARK_950_COLOR - # This is for fake border color - self.config_window.SB__WRAPPER_BG_COLOR = self.DARK_750_COLOR + # # This is for fake border color + # self.config_window.SB__WRAPPER_BG_COLOR = self.DARK_750_COLOR - self.config_window.SB__BG_COLOR = self.DARK_888_COLOR + # self.config_window.SB__BG_COLOR = self.DARK_888_COLOR - self.config_window.SB__OPTIONMENU_BG_COLOR = self.DARK_925_COLOR - self.config_window.SB__OPTIONMENU_HOVERED_BG_COLOR = self.DARK_875_COLOR + # self.config_window.SB__OPTIONMENU_BG_COLOR = self.DARK_925_COLOR + # self.config_window.SB__OPTIONMENU_HOVERED_BG_COLOR = self.DARK_875_COLOR - self.config_window.SB__SLIDER_BUTTON_COLOR = self.DARK_700_COLOR - self.config_window.SB__SLIDER_BUTTON_HOVERED_COLOR = self.DARK_600_COLOR + # self.config_window.SB__SLIDER_BUTTON_COLOR = self.DARK_700_COLOR + # self.config_window.SB__SLIDER_BUTTON_HOVERED_COLOR = self.DARK_600_COLOR - self.config_window.SB__SWITCH_BOX_BG_COLOR = self.main.SF__SWITCH_BOX_BG_COLOR - self.config_window.SB__SWITCH_BOX_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR + # self.config_window.SB__SWITCH_BOX_BG_COLOR = self.main.SF__SWITCH_BOX_BG_COLOR + # self.config_window.SB__SWITCH_BOX_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR - self.config_window.SB__CHECKBOX_BORDER_COLOR = self.DARK_500_COLOR - self.config_window.SB__CHECKBOX_HOVER_COLOR = self.DARK_800_COLOR - self.config_window.SB__CHECKBOX_CHECKED_COLOR = self.PRIMARY_700_COLOR - self.config_window.SB__CHECKBOX_CHECKMARK_COLOR = self.config_window.BASIC_TEXT_COLOR + # self.config_window.SB__CHECKBOX_BORDER_COLOR = self.DARK_500_COLOR + # self.config_window.SB__CHECKBOX_HOVER_COLOR = self.DARK_800_COLOR + # self.config_window.SB__CHECKBOX_CHECKED_COLOR = self.PRIMARY_700_COLOR + # self.config_window.SB__CHECKBOX_CHECKMARK_COLOR = self.config_window.BASIC_TEXT_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_COLOR = self.PRIMARY_700_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_HOVERED_COLOR = self.PRIMARY_500_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR = self.DARK_800_COLOR + # self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_COLOR = self.PRIMARY_700_COLOR + # self.config_window.SB__PROGRESSBAR_X_SLIDER__SLIDER_BUTTON_HOVERED_COLOR = self.PRIMARY_500_COLOR + # self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR = self.DARK_800_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR = self.DARK_800_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_HOVERED_COLOR = self.DARK_700_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_CLICKED_COLOR = self.DARK_900_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR = self.DARK_850_COLOR + # self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_COLOR = self.DARK_800_COLOR + # self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_HOVERED_COLOR = self.DARK_700_COLOR + # self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_CLICKED_COLOR = self.DARK_900_COLOR + # self.config_window.SB__PROGRESSBAR_X_SLIDER__PASSIVE_BUTTON_DISABLED_COLOR = self.DARK_850_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_COLOR = self.PRIMARY_700_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_HOVERED_COLOR = self.PRIMARY_600_COLOR - self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_CLICKED_COLOR = self.PRIMARY_900_COLOR - # self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_DISABLED_COLOR = self.PRIMARY_900_COLOR + # self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_COLOR = self.PRIMARY_700_COLOR + # self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_HOVERED_COLOR = self.PRIMARY_600_COLOR + # self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_CLICKED_COLOR = self.PRIMARY_900_COLOR + # # self.config_window.SB__PROGRESSBAR_X_SLIDER__ACTIVE_BUTTON_DISABLED_COLOR = self.PRIMARY_900_COLOR - # Side menu - self.config_window.SIDE_MENU_BG_COLOR = self.config_window.MAIN_BG_COLOR + # # Side menu + # self.config_window.SIDE_MENU_BG_COLOR = self.config_window.MAIN_BG_COLOR - self.config_window.SIDE_MENU_LABELS_BG_COLOR = self.config_window.SIDE_MENU_BG_COLOR - self.config_window.SIDE_MENU_LABELS_BG_FOR_FAKE_BORDER_COLOR = self.config_window.SIDE_MENU_BG_COLOR - self.config_window.SIDE_MENU_LABELS_HOVERED_BG_COLOR = self.DARK_850_COLOR - self.config_window.SIDE_MENU_LABELS_CLICKED_BG_COLOR = self.PRIMARY_900_COLOR - self.config_window.SIDE_MENU_LABELS_SELECTED_TEXT_COLOR = self.PRIMARY_300_COLOR + # self.config_window.SIDE_MENU_LABELS_BG_COLOR = self.config_window.SIDE_MENU_BG_COLOR + # self.config_window.SIDE_MENU_LABELS_BG_FOR_FAKE_BORDER_COLOR = self.config_window.SIDE_MENU_BG_COLOR + # self.config_window.SIDE_MENU_LABELS_HOVERED_BG_COLOR = self.DARK_850_COLOR + # self.config_window.SIDE_MENU_LABELS_CLICKED_BG_COLOR = self.PRIMARY_900_COLOR + # self.config_window.SIDE_MENU_LABELS_SELECTED_TEXT_COLOR = self.PRIMARY_300_COLOR - self.config_window.SIDE_MENU_SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR + # self.config_window.SIDE_MENU_SELECTED_MARK_ACTIVE_BG_COLOR = self.main.SF__SWITCH_BOX_ACTIVE_BG_COLOR From 9b974b8632703a4419c75550896118232465dc35 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Thu, 19 Oct 2023 19:45:06 +0900 Subject: [PATCH 341/355] =?UTF-8?q?=F0=9F=90=9B[Update]=20Model=20:=20Auth?= =?UTF-8?q?=20key=E3=81=AE=E5=BE=A9=E6=97=A7=E4=BD=9C=E6=A5=AD2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 30 ++++++++++++++++-------------- model.py | 14 +++----------- 2 files changed, 19 insertions(+), 25 deletions(-) diff --git a/controller.py b/controller.py index d6c6bdb4..bb7fae7b 100644 --- a/controller.py +++ b/controller.py @@ -212,7 +212,7 @@ def initSetLanguageAndCountry(): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) + model.authenticationTranslator() def setYourLanguageAndCountry(select): languages = config.SELECTED_TAB_YOUR_LANGUAGES @@ -222,7 +222,7 @@ def setYourLanguageAndCountry(select): config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) + model.authenticationTranslator() view.printToTextbox_selectedYourLanguages(select) def setTargetLanguageAndCountry(select): @@ -233,7 +233,7 @@ def setTargetLanguageAndCountry(select): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) + model.authenticationTranslator() view.printToTextbox_selectedTargetLanguages(select) def callbackSelectedLanguagePresetTab(selected_tab_no): @@ -250,12 +250,9 @@ def callbackSelectedLanguagePresetTab(selected_tab_no): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) + model.authenticationTranslator() view.printToTextbox_changedLanguagePresetTab(config.SELECTED_TAB_NO) -def callbackSetAuthKeys(keys): - config.AUTH_KEYS = keys - # command func def callbackToggleTranslation(is_turned_on): config.ENABLE_TRANSLATION = is_turned_on @@ -377,17 +374,22 @@ def callbackSetUiLanguage(value): # Translation Tab def callbackSetDeeplAuthkey(value): print("callbackSetDeeplAuthkey", str(value)) - if len(value) > 0 and model.authenticationTranslator(callbackSetAuthKeys, choice_translator="DeepL(auth)", auth_key=value) is True: + if len(value) > 0: + result = model.authenticationTranslator(choice_translator="DeepL_API", auth_key=value) + if result is True: + auth_keys = config.AUTH_KEYS + auth_keys["DeepL_API"] = value + config.AUTH_KEYS = auth_keys + view.printToTextbox_AuthenticationSuccess() config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) - view.printToTextbox_AuthenticationSuccess() elif len(value) == 0: auth_keys = config.AUTH_KEYS - auth_keys["DeepL(auth)"] = None + auth_keys["DeepL_API"] = None config.AUTH_KEYS = auth_keys - model.authenticationTranslator(callbackSetAuthKeys) + config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) else: view.printToTextbox_AuthenticationError() + print(config.AUTH_KEYS, config.CHOICE_TRANSLATOR) # Transcription Tab (Mic) def callbackSetMicHost(value): @@ -634,11 +636,11 @@ def createMainWindow(): # init config initSetLanguageAndCountry() - if model.authenticationTranslator(callbackSetAuthKeys) is False: + if model.authenticationTranslator() is False: # error update Auth key view.printToTextbox_AuthenticationError() config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator(callbackSetAuthKeys) + model.authenticationTranslator() # set word filter model.addKeywords() diff --git a/model.py b/model.py index 68b48e81..91507df1 100644 --- a/model.py +++ b/model.py @@ -81,20 +81,13 @@ class Model: del self.keyword_processor self.keyword_processor = KeywordProcessor() - def authenticationTranslator(self, fnc, choice_translator=None, auth_key=None): + def authenticationTranslator(self, choice_translator=None, auth_key=None): if choice_translator == None: choice_translator = config.CHOICE_TRANSLATOR if auth_key == None: auth_key = config.AUTH_KEYS[choice_translator] result = self.translator.authentication(choice_translator, auth_key) - if result: - auth_keys = config.AUTH_KEYS - auth_keys[choice_translator] = auth_key - try: - fnc(auth_keys) - except: - pass return result def startLogger(self): @@ -139,6 +132,8 @@ class Model: engine_name = compatible_engines[0] if engine_name == "DeepL" and config.AUTH_KEYS["DeepL_API"] != None: + engine_name = "DeepL_API" + elif engine_name == "DeepL_API" and config.AUTH_KEYS["DeepL_API"] == None: engine_name = "DeepL" return engine_name @@ -146,9 +141,6 @@ class Model: def getTranslatorStatus(self): return self.translator.translator_status[config.CHOICE_TRANSLATOR] - def getListTranslatorName(self): - return list(self.translator.translator_status.keys()) - def getInputTranslate(self, message): try: if config.CHOICE_TRANSLATOR == "DeepL_API": From f668686daf2df8f42e5afedbaf3ee6023852dc9d Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 20 Oct 2023 04:30:30 +0900 Subject: [PATCH 342/355] [Update] add Information Modal. This is for displaying when reached translation limit that 429 too many requests. --- locales/en.yml | 5 + locales/ja.yml | 4 + view.py | 94 +++++--- vrct_gui/_CreateConfirmationModal.py | 204 +++++++++++------- vrct_gui/_changeMainWindowWidgetsStatus.py | 12 +- .../_create_sidebar/createSidebarFeatures.py | 17 +- vrct_gui/ui_managers/ColorThemeManager.py | 22 +- vrct_gui/ui_managers/UiScalingManager.py | 20 +- vrct_gui/vrct_gui.py | 14 +- 9 files changed, 258 insertions(+), 134 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 4fed8935..6f17aa3a 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -29,6 +29,7 @@ main_window: no_mic_device_detected_error: No mic device detected. no_speaker_device_detected_error: No speaker device detected. + translation_engine_limit_error: It has automatically disabled the translation feature. Access has been temporarily restricted due to an excessive number of requests to the translation engine. Please wait for a while, restart VRCT, and try again. detected_by_word_filter: The word %{detected_message} has not been sent due to detection by the word filter. @@ -55,6 +56,10 @@ main_window: accept_adjust_ui_size: "Set it smaller and restart" + translation_engine_limit_error: "It has automatically disabled the translation feature.\nAccess has been temporarily restricted\ndue to an excessive number of requests to the translation engine.\nPlease wait for a while, restart VRCT, and try again." + accept_translation_engine_limit_error: Accept and close + + selectable_language_window: title_your_language: Select Your Language title_target_language: Select Target Language diff --git a/locales/ja.yml b/locales/ja.yml index dff1b932..952dbfc3 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -29,6 +29,7 @@ main_window: no_mic_device_detected_error: マイクデバイスが検出されませんでした。 no_speaker_device_detected_error: スピーカーデバイスが検出されませんでした。 + translation_engine_limit_error: 翻訳機能を自動的に停止しました。翻訳エンジンへのリクエストが多すぎるため、一時的にアクセスが制限されています。しばらく待ってから、VRCTの再起動をしてもう一度試してみてください。 detected_by_word_filter: ワードフィルターに登録されている単語 %{detected_message} が検出されたため送信しませんでした。 @@ -55,6 +56,9 @@ main_window: accept_adjust_ui_size: 小さく設定して再起動 + translation_engine_limit_error: "翻訳機能を自動的に停止しました。\n翻訳エンジンへのリクエストが多すぎるため\n一時的にアクセスが制限されています。\nしばらく待ってから、VRCTの再起動をしてもう一度試してみてください。" + accept_translation_engine_limit_error: 了承して閉じる + selectable_language_window: title_your_language: あなたの言語 diff --git a/view.py b/view.py index c718eedf..80178321 100644 --- a/view.py +++ b/view.py @@ -74,9 +74,9 @@ class View(): **common_args ) - self.settings.update_confirmation_modal = SimpleNamespace( - ctm=all_ctm.update_confirmation_modal, - uism=all_uism.update_confirmation_modal, + self.settings.confirmation_modal = SimpleNamespace( + ctm=all_ctm.confirmation_modal, + uism=all_uism.confirmation_modal, **common_args ) @@ -569,23 +569,6 @@ class View(): vrct_gui.attributes("-topmost", False) - def _showDisplayOverUiSizeConfirmationModal(self): - self.foregroundOffIfForegroundEnabled() - - self.view_variable.VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE.set("") - vrct_gui.main_window_cover.show() - - self.view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL=self._hideConfirmationModal - self.view_variable.CALLBACK_ACCEPTED_CONFIRMATION_MODAL=self._adjustUiSizeAndRestart - self.view_variable.CALLBACK_DENIED_CONFIRMATION_MODAL=self._hideConfirmationModal - - self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.detected_over_ui_size", current_ui_size=config.UI_SCALING)) - self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON.set(i18n.t("main_window.confirmation_message.deny_adjust_ui_size")) - self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON.set(i18n.t("main_window.confirmation_message.accept_adjust_ui_size")) - - vrct_gui.update_confirmation_modal.show(hide_title_bar=False, close_when_focusout=False) - - def _adjustUiSizeAndRestart(self): current_percentage = int(config.UI_SCALING.replace("%","")) target_percentage = current_percentage - 20 @@ -601,7 +584,21 @@ class View(): + def _showDisplayOverUiSizeConfirmationModal(self): + self.foregroundOffIfForegroundEnabled() + self.view_variable.VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE.set("") + vrct_gui.main_window_cover.show() + + self.view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL=self._hideConfirmationModal + self.view_variable.CALLBACK_ACCEPTED_CONFIRMATION_MODAL=self._adjustUiSizeAndRestart + self.view_variable.CALLBACK_DENIED_CONFIRMATION_MODAL=self._hideConfirmationModal + + self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.detected_over_ui_size", current_ui_size=config.UI_SCALING)) + self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON.set(i18n.t("main_window.confirmation_message.deny_adjust_ui_size")) + self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON.set(i18n.t("main_window.confirmation_message.accept_adjust_ui_size")) + + vrct_gui.confirmation_modal.show(hide_title_bar=False, close_when_focusout=False) @@ -618,10 +615,55 @@ class View(): self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.update_software")) self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON.set(i18n.t("main_window.confirmation_message.deny_update_software")) self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON.set(i18n.t("main_window.confirmation_message.accept_update_software")) - vrct_gui.update_confirmation_modal.show() + vrct_gui.confirmation_modal.show() + + + + + + def showTheLimitOfTranslationEngineConfirmationModal(self): + self.foregroundOffIfForegroundEnabled() + + self.view_variable.VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE.set("") + vrct_gui.main_window_cover.show() + + # self.view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL=self._hideConfirmationModal + self.view_variable.CALLBACK_ACCEPTED_CONFIRMATION_MODAL=self._hideInformationModal + # self.view_variable.CALLBACK_DENIED_CONFIRMATION_MODAL=self._hideConfirmationModal + + self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.translation_engine_limit_error")) + # self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON.set(i18n.t("main_window.confirmation_message.deny_update_software")) + self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON.set(i18n.t("main_window.confirmation_message.accept_translation_engine_limit_error")) + vrct_gui.information_modal.show(close_when_focusout=False) + + + + def translationEngineLimitErrorProcess(self): + # turn off translation switch. + vrct_gui.translation_switch_box.deselect() + vrct_gui.translation_frame.markToggleManually(False) + + # disable translation feature. + vrct_gui._changeMainWindowWidgetsStatus("disabled", ["translation_switch"], to_hold_state=True) + + # print system message that mention to stopped translation feature. + view.printToTextbox_TranslationEngineLimitError() + view.showTheLimitOfTranslationEngineConfirmationModal() + + + + + + + + def _hideInformationModal(self): + vrct_gui.information_modal.hide() + vrct_gui.main_window_cover.hide() + self.foregroundOnIfForegroundEnabled() + def _hideConfirmationModal(self): - vrct_gui.update_confirmation_modal.hide() + vrct_gui.confirmation_modal.hide() vrct_gui.main_window_cover.hide() self.foregroundOnIfForegroundEnabled() @@ -630,9 +672,9 @@ class View(): def _startUpdateSoftware(self): self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.updating")) - vrct_gui.update_confirmation_modal.hide_buttons() + vrct_gui.confirmation_modal.hide_buttons() vrct_gui.update() - vrct_gui.update_confirmation_modal.update() + vrct_gui.confirmation_modal.update() callFunctionIfCallable(self.view_variable.CALLBACK_UPDATE_SOFTWARE) @@ -715,6 +757,10 @@ class View(): self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.no_speaker_device_detected_error")) + def printToTextbox_TranslationEngineLimitError(self): + self._printToTextbox_Info(i18n.t("main_window.textbox_system_message.translation_engine_limit_error")) + + # def printToTextbox_OSCError(self): [Deprecated] # self._printToTextbox_Info("OSC is not enabled, please enable OSC and rejoin. or turn off the \"Send Message To VRChat\" setting") diff --git a/vrct_gui/_CreateConfirmationModal.py b/vrct_gui/_CreateConfirmationModal.py index ece22e2a..747e9f9f 100644 --- a/vrct_gui/_CreateConfirmationModal.py +++ b/vrct_gui/_CreateConfirmationModal.py @@ -5,10 +5,14 @@ from .ui_utils import fadeInAnimation, setGeometryToCenterOfTheWidget, bindButto from utils import callFunctionIfCallable class _CreateConfirmationModal(CTkToplevel): - def __init__(self, attach_window, settings, view_variable): + def __init__(self, attach_window, settings, view_variable, modal_type=None): super().__init__() self.withdraw() + self.attach_window = attach_window + self.settings = settings + self._view_variable = view_variable + self.title("") self.overrideredirect(True) @@ -17,10 +21,6 @@ class _CreateConfirmationModal(CTkToplevel): self.BIND_FOCUS_OUT_FUNC_ID=None - self.attach_window = attach_window - self.settings = settings - self._view_variable = view_variable - # self.configure(fg_color="#ff7f50") self.configure(fg_color=self.settings.ctm.FAKE_BORDER_COLOR) @@ -70,85 +70,131 @@ class _CreateConfirmationModal(CTkToplevel): self.modal_buttons_wrapper.grid(row=1, column=0, sticky="ew") - self.modal_buttons_wrapper.grid_columnconfigure(1, weight=1, minsize=self.settings.uism.BUTTONS_BETWEEN_PADDING) - self.modal_buttons_wrapper.grid_columnconfigure((0,2), weight=0, uniform="button_wrapper") + if modal_type == "information": + # self.modal_buttons_wrapper.grid_columnconfigure(1, weight=1, minsize=self.settings.uism.BUTTONS_BETWEEN_PADDING) + self.modal_buttons_wrapper.grid_columnconfigure((0,2), weight=1) + self.accept_button = CTkFrame(self.modal_buttons_wrapper, corner_radius=self.settings.uism.BUTTONS_CORNER_RADIUS, fg_color=self.settings.ctm.ACCEPT_BUTTON_BG_COLOR, cursor="hand2") + self.accept_button.grid(row=0, column=1, sticky="ew") + self.accept_button.grid_columnconfigure(0, weight=1) + self.accept_button_label_wrapper = CTkFrame(self.accept_button, corner_radius=0, fg_color=self.settings.ctm.ACCEPT_BUTTON_BG_COLOR) + self.accept_button_label_wrapper.grid(row=0, column=0, padx=self.settings.uism.BUTTONS_IPADX, pady=self.settings.uism.BUTTONS_IPADY, sticky="ew") - self.deny_button = CTkFrame(self.modal_buttons_wrapper, corner_radius=self.settings.uism.BUTTONS_CORNER_RADIUS, fg_color=self.settings.ctm.DENY_BUTTON_BG_COLOR, cursor="hand2") - self.deny_button.grid(row=0, column=0, sticky="ew") - - - self.deny_button.grid_columnconfigure(0, weight=1) - self.deny_button_label_wrapper = CTkFrame(self.deny_button, corner_radius=0, fg_color=self.settings.ctm.DENY_BUTTON_BG_COLOR) - self.deny_button_label_wrapper.grid(row=0, column=0, padx=self.settings.uism.BUTTONS_IPADX, pady=self.settings.uism.BUTTONS_IPADY, sticky="ew") - - self.deny_button_label_wrapper.grid_columnconfigure((0,2), weight=1) - - - self.deny_button_label_wrapper.grid_columnconfigure(0, weight=1) - self.deny_button_label = CTkLabel( - self.deny_button_label_wrapper, - textvariable=self._view_variable.VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON, - height=0, - corner_radius=0, - font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.CONFIRMATION_BUTTONS_TEXT_FONT_SIZE, weight="normal"), - anchor="w", - text_color=self.settings.ctm.CONFIRMATION_BUTTONS_TEXT_COLOR, - ) - self.deny_button_label.grid(row=0, column=1) - - - - bindButtonFunctionAndColor( - target_widgets=[ - self.deny_button, - self.deny_button_label_wrapper, - self.deny_button_label, - ], - enter_color=settings.ctm.DENY_BUTTON_HOVERED_BG_COLOR, - leave_color=settings.ctm.DENY_BUTTON_BG_COLOR, - clicked_color=settings.ctm.DENY_BUTTON_CLICKED_BG_COLOR, - buttonReleasedFunction=lambda _e: callFunctionIfCallable(self._view_variable.CALLBACK_DENIED_CONFIRMATION_MODAL), - ) - - - - self.accept_button = CTkFrame(self.modal_buttons_wrapper, corner_radius=self.settings.uism.BUTTONS_CORNER_RADIUS, fg_color=self.settings.ctm.ACCEPT_BUTTON_BG_COLOR, cursor="hand2") - self.accept_button.grid(row=0, column=2, sticky="ew") - - - self.accept_button.grid_columnconfigure(0, weight=1) - self.accept_button_label_wrapper = CTkFrame(self.accept_button, corner_radius=0, fg_color=self.settings.ctm.ACCEPT_BUTTON_BG_COLOR) - self.accept_button_label_wrapper.grid(row=0, column=0, padx=self.settings.uism.BUTTONS_IPADX, pady=self.settings.uism.BUTTONS_IPADY, sticky="ew") - - self.accept_button_label_wrapper.grid_columnconfigure((0,2), weight=1) - self.accept_button_label = CTkLabel( - self.accept_button_label_wrapper, - textvariable=self._view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON, - height=0, - corner_radius=0, - font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.CONFIRMATION_BUTTONS_TEXT_FONT_SIZE, weight="normal"), - anchor="w", - text_color=self.settings.ctm.CONFIRMATION_BUTTONS_TEXT_COLOR, - ) - self.accept_button_label.grid(row=0, column=1) - - - - bindButtonFunctionAndColor( - target_widgets=[ - self.accept_button, + self.accept_button_label_wrapper.grid_columnconfigure((0,2), weight=1) + self.accept_button_label = CTkLabel( self.accept_button_label_wrapper, - self.accept_button_label, - ], - enter_color=settings.ctm.ACCEPT_BUTTON_HOVERED_BG_COLOR, - leave_color=settings.ctm.ACCEPT_BUTTON_BG_COLOR, - clicked_color=settings.ctm.ACCEPT_BUTTON_CLICKED_BG_COLOR, - buttonReleasedFunction=lambda _e: callFunctionIfCallable(self._view_variable.CALLBACK_ACCEPTED_CONFIRMATION_MODAL), - ) + textvariable=self._view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.CONFIRMATION_BUTTONS_TEXT_FONT_SIZE, weight="normal"), + anchor="w", + text_color=self.settings.ctm.CONFIRMATION_BUTTONS_TEXT_COLOR, + ) + self.accept_button_label.grid(row=0, column=1) + + + + bindButtonFunctionAndColor( + target_widgets=[ + self.accept_button, + self.accept_button_label_wrapper, + self.accept_button_label, + ], + enter_color=settings.ctm.ACCEPT_BUTTON_HOVERED_BG_COLOR, + leave_color=settings.ctm.ACCEPT_BUTTON_BG_COLOR, + clicked_color=settings.ctm.ACCEPT_BUTTON_CLICKED_BG_COLOR, + buttonReleasedFunction=lambda _e: callFunctionIfCallable(self._view_variable.CALLBACK_ACCEPTED_CONFIRMATION_MODAL), + ) + + + + + + + else: + self.modal_buttons_wrapper.grid_columnconfigure(1, weight=1, minsize=self.settings.uism.BUTTONS_BETWEEN_PADDING) + self.modal_buttons_wrapper.grid_columnconfigure((0,2), weight=0, uniform="button_wrapper") + + + + + + self.deny_button = CTkFrame(self.modal_buttons_wrapper, corner_radius=self.settings.uism.BUTTONS_CORNER_RADIUS, fg_color=self.settings.ctm.DENY_BUTTON_BG_COLOR, cursor="hand2") + self.deny_button.grid(row=0, column=0, sticky="ew") + + + self.deny_button.grid_columnconfigure(0, weight=1) + self.deny_button_label_wrapper = CTkFrame(self.deny_button, corner_radius=0, fg_color=self.settings.ctm.DENY_BUTTON_BG_COLOR) + self.deny_button_label_wrapper.grid(row=0, column=0, padx=self.settings.uism.BUTTONS_IPADX, pady=self.settings.uism.BUTTONS_IPADY, sticky="ew") + + self.deny_button_label_wrapper.grid_columnconfigure((0,2), weight=1) + + + self.deny_button_label_wrapper.grid_columnconfigure(0, weight=1) + self.deny_button_label = CTkLabel( + self.deny_button_label_wrapper, + textvariable=self._view_variable.VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.CONFIRMATION_BUTTONS_TEXT_FONT_SIZE, weight="normal"), + anchor="w", + text_color=self.settings.ctm.CONFIRMATION_BUTTONS_TEXT_COLOR, + ) + self.deny_button_label.grid(row=0, column=1) + + + + bindButtonFunctionAndColor( + target_widgets=[ + self.deny_button, + self.deny_button_label_wrapper, + self.deny_button_label, + ], + enter_color=settings.ctm.DENY_BUTTON_HOVERED_BG_COLOR, + leave_color=settings.ctm.DENY_BUTTON_BG_COLOR, + clicked_color=settings.ctm.DENY_BUTTON_CLICKED_BG_COLOR, + buttonReleasedFunction=lambda _e: callFunctionIfCallable(self._view_variable.CALLBACK_DENIED_CONFIRMATION_MODAL), + ) + + + + self.accept_button = CTkFrame(self.modal_buttons_wrapper, corner_radius=self.settings.uism.BUTTONS_CORNER_RADIUS, fg_color=self.settings.ctm.ACCEPT_BUTTON_BG_COLOR, cursor="hand2") + self.accept_button.grid(row=0, column=2, sticky="ew") + + + self.accept_button.grid_columnconfigure(0, weight=1) + self.accept_button_label_wrapper = CTkFrame(self.accept_button, corner_radius=0, fg_color=self.settings.ctm.ACCEPT_BUTTON_BG_COLOR) + self.accept_button_label_wrapper.grid(row=0, column=0, padx=self.settings.uism.BUTTONS_IPADX, pady=self.settings.uism.BUTTONS_IPADY, sticky="ew") + + self.accept_button_label_wrapper.grid_columnconfigure((0,2), weight=1) + self.accept_button_label = CTkLabel( + self.accept_button_label_wrapper, + textvariable=self._view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON, + height=0, + corner_radius=0, + font=CTkFont(family=self.settings.FONT_FAMILY, size=self.settings.uism.CONFIRMATION_BUTTONS_TEXT_FONT_SIZE, weight="normal"), + anchor="w", + text_color=self.settings.ctm.CONFIRMATION_BUTTONS_TEXT_COLOR, + ) + self.accept_button_label.grid(row=0, column=1) + + + + bindButtonFunctionAndColor( + target_widgets=[ + self.accept_button, + self.accept_button_label_wrapper, + self.accept_button_label, + ], + enter_color=settings.ctm.ACCEPT_BUTTON_HOVERED_BG_COLOR, + leave_color=settings.ctm.ACCEPT_BUTTON_BG_COLOR, + clicked_color=settings.ctm.ACCEPT_BUTTON_CLICKED_BG_COLOR, + buttonReleasedFunction=lambda _e: callFunctionIfCallable(self._view_variable.CALLBACK_ACCEPTED_CONFIRMATION_MODAL), + ) + def hide_buttons(self): @@ -156,6 +202,8 @@ class _CreateConfirmationModal(CTkToplevel): def show(self, hide_title_bar:bool=True, close_when_focusout:bool=True): + self.modal_buttons_wrapper.grid() + if hide_title_bar is False: self.overrideredirect(False) else: diff --git a/vrct_gui/_changeMainWindowWidgetsStatus.py b/vrct_gui/_changeMainWindowWidgetsStatus.py index 4910797d..53c29f2a 100644 --- a/vrct_gui/_changeMainWindowWidgetsStatus.py +++ b/vrct_gui/_changeMainWindowWidgetsStatus.py @@ -1,10 +1,14 @@ from customtkinter import CTkImage - -def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, target_names): +hold_state_list=[] +def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, target_names:list, to_hold_state:bool=False): + global hold_state_list if target_names == "All": target_names = ["translation_switch", "transcription_send_switch", "transcription_receive_switch", "foreground_switch", "quick_language_settings", "config_button", "minimize_sidebar_button", "entry_message_box"] + for item in hold_state_list: + if item in target_names: + target_names.remove(item) def update_switch_status( @@ -146,5 +150,9 @@ def _changeMainWindowWidgetsStatus(vrct_gui, settings, view_variable, status, ta raise ValueError(f"No matching case for target_name: {target_name}") + if to_hold_state is True: + for item in target_names: + if item not in hold_state_list: + hold_state_list.append(item) vrct_gui.update() \ No newline at end of file diff --git a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py index 9aaf7cef..b46ab3cc 100644 --- a/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py +++ b/vrct_gui/main_window/widgets/_create_sidebar/createSidebarFeatures.py @@ -1,3 +1,5 @@ +from functools import partial + from customtkinter import CTkFont, CTkFrame, CTkLabel, CTkSwitch, CTkImage from ....ui_utils import openImageKeepAspectRatio, retag, getLatestHeight, bindEnterAndLeaveFunction, bindButtonReleaseFunction, bindButtonPressAndReleaseFunction @@ -6,29 +8,29 @@ from utils import callFunctionIfCallable def createSidebarFeatures(settings, main_window, view_variable): - def toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, mark): - mark.place(relx=0.85) if is_turned_on else mark.place(relx=-1) + def toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, mark_widget): + mark_widget.place(relx=0.85) if is_turned_on else mark_widget.place(relx=-1) def toggleTranslationFeature(): is_turned_on = main_window.translation_switch_box.get() callFunctionIfCallable(view_variable.CALLBACK_TOGGLE_TRANSLATION, is_turned_on) - toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.translation_selected_mark) + main_window.translation_frame.markToggleManually(is_turned_on=is_turned_on) def toggleTranscriptionSendFeature(): is_turned_on = main_window.transcription_send_switch_box.get() callFunctionIfCallable(view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_SEND, is_turned_on) - toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_send_selected_mark) + main_window.transcription_send_frame.markToggleManually(is_turned_on=is_turned_on) def toggleTranscriptionReceiveFeature(): is_turned_on = main_window.transcription_receive_switch_box.get() callFunctionIfCallable(view_variable.CALLBACK_TOGGLE_TRANSCRIPTION_RECEIVE, is_turned_on) - toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.transcription_receive_selected_mark) + main_window.transcription_receive_frame.markToggleManually(is_turned_on=is_turned_on) def toggleForegroundFeature(): is_turned_on = main_window.foreground_switch_box.get() callFunctionIfCallable(view_variable.CALLBACK_TOGGLE_FOREGROUND, is_turned_on) - toggleSidebarFeatureSelectedMarkIfTurnedOn(is_turned_on, main_window.foreground_selected_mark) + main_window.foreground_frame.markToggleManually(is_turned_on=is_turned_on) @@ -279,4 +281,7 @@ def createSidebarFeatures(settings, main_window, view_variable): bindButtonPressAndReleaseFunction([compact_mode_icon_widget, frame_widget, compact_mode_frame_widget, label_widget, selected_mark_widget, switch_box_widget._canvas, switch_box_widget._bg_canvas], buttonPressFunction, buttonReleasedFunction) + callback = partial(toggleSidebarFeatureSelectedMarkIfTurnedOn, mark_widget=selected_mark_widget) + frame_widget.markToggleManually = callback + row+=1 \ No newline at end of file diff --git a/vrct_gui/ui_managers/ColorThemeManager.py b/vrct_gui/ui_managers/ColorThemeManager.py index f0a97a9e..ab55e996 100644 --- a/vrct_gui/ui_managers/ColorThemeManager.py +++ b/vrct_gui/ui_managers/ColorThemeManager.py @@ -7,7 +7,7 @@ class ColorThemeManager(): self.selectable_language_window = SimpleNamespace() self.main_window_cover = SimpleNamespace() self.error_message_window = SimpleNamespace() - self.update_confirmation_modal = SimpleNamespace() + self.confirmation_modal = SimpleNamespace() # old one. But leave it here for now. # self.PRIMARY_100_COLOR = "#c4eac1" @@ -224,17 +224,17 @@ class ColorThemeManager(): self.main_window_cover.TEXT_COLOR = self.LIGHT_100_COLOR - self.update_confirmation_modal.MESSAGE_TEXT_COLOR = self.LIGHT_100_COLOR - self.update_confirmation_modal.FAKE_BORDER_COLOR = self.DARK_600_COLOR - self.update_confirmation_modal.BG_COLOR = self.DARK_800_COLOR - self.update_confirmation_modal.CONFIRMATION_BUTTONS_TEXT_COLOR = self.LIGHT_100_COLOR + self.confirmation_modal.MESSAGE_TEXT_COLOR = self.LIGHT_100_COLOR + self.confirmation_modal.FAKE_BORDER_COLOR = self.DARK_600_COLOR + self.confirmation_modal.BG_COLOR = self.DARK_800_COLOR + self.confirmation_modal.CONFIRMATION_BUTTONS_TEXT_COLOR = self.LIGHT_100_COLOR - self.update_confirmation_modal.ACCEPT_BUTTON_BG_COLOR = self.PRIMARY_600_COLOR - self.update_confirmation_modal.ACCEPT_BUTTON_HOVERED_BG_COLOR = self.PRIMARY_450_COLOR - self.update_confirmation_modal.ACCEPT_BUTTON_CLICKED_BG_COLOR = self.PRIMARY_750_COLOR - self.update_confirmation_modal.DENY_BUTTON_BG_COLOR = self.DARK_750_COLOR - self.update_confirmation_modal.DENY_BUTTON_HOVERED_BG_COLOR = self.DARK_700_COLOR - self.update_confirmation_modal.DENY_BUTTON_CLICKED_BG_COLOR = self.DARK_825_COLOR + self.confirmation_modal.ACCEPT_BUTTON_BG_COLOR = self.PRIMARY_600_COLOR + self.confirmation_modal.ACCEPT_BUTTON_HOVERED_BG_COLOR = self.PRIMARY_450_COLOR + self.confirmation_modal.ACCEPT_BUTTON_CLICKED_BG_COLOR = self.PRIMARY_750_COLOR + self.confirmation_modal.DENY_BUTTON_BG_COLOR = self.DARK_750_COLOR + self.confirmation_modal.DENY_BUTTON_HOVERED_BG_COLOR = self.DARK_700_COLOR + self.confirmation_modal.DENY_BUTTON_CLICKED_BG_COLOR = self.DARK_825_COLOR # Common diff --git a/vrct_gui/ui_managers/UiScalingManager.py b/vrct_gui/ui_managers/UiScalingManager.py index 9b543704..dc385b34 100644 --- a/vrct_gui/ui_managers/UiScalingManager.py +++ b/vrct_gui/ui_managers/UiScalingManager.py @@ -10,7 +10,7 @@ class UiScalingManager(): self.selectable_language_window = SimpleNamespace() self.main_window_cover = SimpleNamespace() self.error_message_window = SimpleNamespace() - self.update_confirmation_modal = SimpleNamespace() + self.confirmation_modal = SimpleNamespace() self._calculatedUiSizes() @@ -144,15 +144,15 @@ class UiScalingManager(): self.main_window_cover.TEXT_FONT_SIZE = self._calculateUiSize(20) - self.update_confirmation_modal.FAKE_BORDER_SIZE = self._calculateUiSize(1, is_allowed_odd=True) - self.update_confirmation_modal.CONTENTS_WRAPPER = self._calculateUiSize(20) - self.update_confirmation_modal.MARGIN_BETWEEN_MESSAGE_AND_BUTTONS = self._calculateUiSize(40) - self.update_confirmation_modal.MESSAGE_FONT_SIZE = self._calculateUiSize(20) - self.update_confirmation_modal.CONFIRMATION_BUTTONS_TEXT_FONT_SIZE = self._calculateUiSize(18) - self.update_confirmation_modal.BUTTONS_BETWEEN_PADDING = self._calculateUiSize(100) - self.update_confirmation_modal.BUTTONS_CORNER_RADIUS = self._calculateUiSize(6) - self.update_confirmation_modal.BUTTONS_IPADX = self._calculateUiSize(10) - self.update_confirmation_modal.BUTTONS_IPADY = self._calculateUiSize(6) + self.confirmation_modal.FAKE_BORDER_SIZE = self._calculateUiSize(1, is_allowed_odd=True) + self.confirmation_modal.CONTENTS_WRAPPER = self._calculateUiSize(20) + self.confirmation_modal.MARGIN_BETWEEN_MESSAGE_AND_BUTTONS = self._calculateUiSize(40) + self.confirmation_modal.MESSAGE_FONT_SIZE = self._calculateUiSize(20) + self.confirmation_modal.CONFIRMATION_BUTTONS_TEXT_FONT_SIZE = self._calculateUiSize(18) + self.confirmation_modal.BUTTONS_BETWEEN_PADDING = self._calculateUiSize(100) + self.confirmation_modal.BUTTONS_CORNER_RADIUS = self._calculateUiSize(6) + self.confirmation_modal.BUTTONS_IPADX = self._calculateUiSize(10) + self.confirmation_modal.BUTTONS_IPADY = self._calculateUiSize(6) # Config Window diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index f9a10fec..a8f31f7d 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -122,12 +122,19 @@ class VRCT_GUI(CTk): message_text_color=self.settings.config_window.ctm.SB__ERROR_MESSAGE_TEXT_COLOR, ) - self.update_confirmation_modal = _CreateConfirmationModal( + self.confirmation_modal = _CreateConfirmationModal( attach_window=self.toplevel_wrapper, - settings=self.settings.update_confirmation_modal, + settings=self.settings.confirmation_modal, view_variable=self._view_variable ) + self.information_modal = _CreateConfirmationModal( + attach_window=self.toplevel_wrapper, + settings=self.settings.confirmation_modal, + view_variable=self._view_variable, + modal_type="information" + ) + # self.update() # self.geometry("{}x{}".format(self.winfo_width(), self.winfo_height())) @@ -219,13 +226,14 @@ class VRCT_GUI(CTk): - def _changeMainWindowWidgetsStatus(self, status, target_names): + def _changeMainWindowWidgetsStatus(self, status, target_names, to_hold_state:bool=False): _changeMainWindowWidgetsStatus( vrct_gui=self, settings=self.settings.main, view_variable=self._view_variable, status=status, target_names=target_names, + to_hold_state=to_hold_state, ) def _changeConfigWindowWidgetsStatus(self, status, target_names): From 55db1c2e750c4e2031b56754af847681c28bb707 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 20 Oct 2023 06:00:18 +0900 Subject: [PATCH 343/355] =?UTF-8?q?[bugfix]=20Main=20Window,=20Cover=20Win?= =?UTF-8?q?dow:=20=E3=83=A1=E3=82=A4=E3=83=B3=E7=94=BB=E9=9D=A2=E3=81=AB?= =?UTF-8?q?=E8=BF=BD=E5=BE=93=E3=81=97=E3=81=AA=E3=81=84=E3=83=90=E3=82=B0?= =?UTF-8?q?=E3=81=AA=E3=81=A9=E4=BF=AE=E6=AD=A3=E3=80=82=E8=BF=BD=E5=BE=93?= =?UTF-8?q?=E3=81=99=E3=82=8B=E9=96=A2=E6=95=B0=E3=82=84=E3=82=A4=E3=83=99?= =?UTF-8?q?=E3=83=B3=E3=83=88=E7=99=BB=E9=8C=B2=E3=82=82cover=20window?= =?UTF-8?q?=E3=82=AF=E3=83=A9=E3=82=B9=E5=86=85=E3=81=AB=E6=8A=BC=E3=81=97?= =?UTF-8?q?=E8=BE=BC=E3=81=BF=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- view.py | 4 ++-- vrct_gui/_CreateWindowCover.py | 29 ++++++++++++++++++++++++++++- vrct_gui/vrct_gui.py | 23 +++-------------------- 3 files changed, 33 insertions(+), 23 deletions(-) diff --git a/view.py b/view.py index 80178321..d2121530 100644 --- a/view.py +++ b/view.py @@ -627,14 +627,14 @@ class View(): self.view_variable.VAR_LABEL_MAIN_WINDOW_COVER_MESSAGE.set("") vrct_gui.main_window_cover.show() - # self.view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL=self._hideConfirmationModal + self.view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL=self._hideInformationModal self.view_variable.CALLBACK_ACCEPTED_CONFIRMATION_MODAL=self._hideInformationModal # self.view_variable.CALLBACK_DENIED_CONFIRMATION_MODAL=self._hideConfirmationModal self.view_variable.VAR_MESSAGE_CONFIRMATION_MODAL.set(i18n.t("main_window.confirmation_message.translation_engine_limit_error")) # self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_DENY_BUTTON.set(i18n.t("main_window.confirmation_message.deny_update_software")) self.view_variable.VAR_LABEL_CONFIRMATION_MODAL_ACCEPT_BUTTON.set(i18n.t("main_window.confirmation_message.accept_translation_engine_limit_error")) - vrct_gui.information_modal.show(close_when_focusout=False) + vrct_gui.information_modal.show(hide_title_bar=False, close_when_focusout=False) diff --git a/vrct_gui/_CreateWindowCover.py b/vrct_gui/_CreateWindowCover.py index 9ab8c4ee..878ca45b 100644 --- a/vrct_gui/_CreateWindowCover.py +++ b/vrct_gui/_CreateWindowCover.py @@ -1,12 +1,15 @@ from customtkinter import CTkToplevel, CTkFrame, CTkLabel, CTkFont from .ui_utils import fadeInAnimation +from utils import makeEven class _CreateWindowCover(CTkToplevel): def __init__(self, attach_window, settings, view_variable): super().__init__() self.withdraw() + self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID=None + self.BIND_FOCUS_IN_FUNC_ID=None self.title("") self.overrideredirect(True) @@ -40,7 +43,14 @@ class _CreateWindowCover(CTkToplevel): self.cover_container_label_wrapper.place(relx=0.5, rely=0.5, anchor="center") - def show(self): + def show(self, bind_focusin=None): + self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID = self.attach_window.bind("", self._adjustToMainWindowGeometry, "+") + if bind_focusin is not None: + self.BIND_FOCUS_IN_FUNC_ID = self.bind("", lambda _e: bind_focusin(), "+") + else: + self.BIND_FOCUS_IN_FUNC_ID = None + + self.attributes("-alpha", 0) self.deiconify() self.attach_window.update_idletasks() @@ -52,5 +62,22 @@ class _CreateWindowCover(CTkToplevel): fadeInAnimation(self, steps=5, interval=0.005, max_alpha=0.5) + def hide(self): + self.attach_window.unbind("", self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID) + if self.BIND_FOCUS_IN_FUNC_ID is not None: + self.unbind("", self.BIND_FOCUS_IN_FUNC_ID) + self.withdraw() + + + + def _adjustToMainWindowGeometry(self, e=None): + self.attach_window.update_idletasks() + x_pos = self.attach_window.winfo_rootx() + y_pos = self.attach_window.winfo_rooty() + width_new = makeEven(self.attach_window.winfo_width()) + height_new = makeEven(self.attach_window.winfo_height()) + self.geometry("{}x{}+{}+{}".format(width_new, height_new, x_pos, y_pos)) + + self.lift() \ No newline at end of file diff --git a/vrct_gui/vrct_gui.py b/vrct_gui/vrct_gui.py index a8f31f7d..73e3d805 100644 --- a/vrct_gui/vrct_gui.py +++ b/vrct_gui/vrct_gui.py @@ -14,15 +14,13 @@ from .main_window import createMainWindowWidgets from .config_window import ConfigWindow from .ui_utils import setDefaultActiveTab, setGeometryToCenterOfScreen, fadeInAnimation -from utils import callFunctionIfCallable, makeEven +from utils import callFunctionIfCallable class VRCT_GUI(CTk): def __init__(self): super().__init__() self.withdraw() self.is_config_window_already_opened_once=False - self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID=None - self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID=None self.BIND_UNMAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID = None self.BIND_MAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID = None @@ -104,7 +102,7 @@ class VRCT_GUI(CTk): ) self.main_window_cover = _CreateWindowCover( - attach_window=self.toplevel_wrapper, + attach_window=self, settings=self.settings.main_window_cover, view_variable=self._view_variable ) @@ -150,14 +148,11 @@ class VRCT_GUI(CTk): def _openConfigWindow(self): - self.main_window_cover.show() - - self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID = self.bind("", self._adjustToMainWindowGeometry, "+") + self.main_window_cover.show(bind_focusin=self.config_window.lift) self.BIND_UNMAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID = self.bind("", self.detectMainWindowState, "+") self.BIND_MAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID = self.bind("", self.detectMainWindowState, "+") - self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID = self.main_window_cover.bind("", lambda _e: self.config_window.lift(), "+") self.config_window.attributes("-alpha", 0) self.config_window.deiconify() @@ -172,10 +167,8 @@ class VRCT_GUI(CTk): self.config_window.withdraw() self.main_window_cover.hide() - self.unbind("", self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID) self.unbind("", self.BIND_UNMAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID) self.unbind("", self.BIND_MAP_DETECT_MAIN_WINDOW_STATE_FUNC_ID) - self.main_window_cover.unbind("", self.BIND_FOCUS_IN_MODAL_WINDOW_LIFT_CONFIG_WINDOW_FUNC_ID) self.adjusted_event=None @@ -276,16 +269,6 @@ class VRCT_GUI(CTk): self.minimize_sidebar_button_container__for_closing.grid() - def _adjustToMainWindowGeometry(self, e=None): - self.update_idletasks() - x_pos = self.winfo_rootx() - y_pos = self.winfo_rooty() - width_new = makeEven(self.winfo_width()) - height_new = makeEven(self.winfo_height()) - self.main_window_cover.geometry("{}x{}+{}+{}".format(width_new, height_new, x_pos, y_pos)) - - self.main_window_cover.lift() - def _showErrorMessage(self, target_widget): self.error_message_window.show(target_widget=target_widget) From e8ee8891b0067885f005a635099cea20c5009c48 Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 20 Oct 2023 06:19:53 +0900 Subject: [PATCH 344/355] [Update] Remove the background color that was for testing. --- vrct_gui/_CreateConfirmationModal.py | 1 - vrct_gui/_CreateDropdownMenuWindow.py | 20 +++++++++---------- vrct_gui/_CreateErrorWindow.py | 17 ++++++++-------- vrct_gui/_CreateSelectableLanguagesWindow.py | 19 +++++++----------- vrct_gui/_CreateWindowCover.py | 17 +++++++--------- vrct_gui/config_window/ConfigWindow.py | 2 +- .../main_window/createMainWindowWidgets.py | 2 +- 7 files changed, 35 insertions(+), 43 deletions(-) diff --git a/vrct_gui/_CreateConfirmationModal.py b/vrct_gui/_CreateConfirmationModal.py index 747e9f9f..1d7a5de4 100644 --- a/vrct_gui/_CreateConfirmationModal.py +++ b/vrct_gui/_CreateConfirmationModal.py @@ -22,7 +22,6 @@ class _CreateConfirmationModal(CTkToplevel): - # self.configure(fg_color="#ff7f50") self.configure(fg_color=self.settings.ctm.FAKE_BORDER_COLOR) self.protocol("WM_DELETE_WINDOW", lambda: callFunctionIfCallable(self._view_variable.CALLBACK_HIDE_CONFIRMATION_MODAL)) diff --git a/vrct_gui/_CreateDropdownMenuWindow.py b/vrct_gui/_CreateDropdownMenuWindow.py index 9688ea4d..948e00e1 100644 --- a/vrct_gui/_CreateDropdownMenuWindow.py +++ b/vrct_gui/_CreateDropdownMenuWindow.py @@ -36,16 +36,6 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.withdraw() self.hide = True - self.title("") - self.overrideredirect(True) - - self.wm_attributes("-alpha", 0) - self.wm_attributes("-toolwindow", True) - - self.configure(fg_color="#ff7f50") - self.resizable(width=False, height=False) - - self.window_additional_y_pos=window_additional_y_pos self.window_border_width=window_border_width self.scrollbar_ipadx=scrollbar_ipadx @@ -89,6 +79,16 @@ class _CreateDropdownMenuWindow(CTkToplevel): self.init_max_display_length = 8 self.max_display_length = self.init_max_display_length + self.title("") + self.overrideredirect(True) + + self.wm_attributes("-alpha", 0) + self.wm_attributes("-toolwindow", True) + + self.configure(fg_color=self.window_bg_color) + self.resizable(width=False, height=False) + + def updateDropdownMenuValues(self, dropdown_menu_widget_id, dropdown_menu_values): self.dropdown_menu_widgets[dropdown_menu_widget_id].widget.destroy() diff --git a/vrct_gui/_CreateErrorWindow.py b/vrct_gui/_CreateErrorWindow.py index f6091453..4b00047a 100644 --- a/vrct_gui/_CreateErrorWindow.py +++ b/vrct_gui/_CreateErrorWindow.py @@ -24,14 +24,6 @@ class _CreateErrorWindow(CTkToplevel): self.withdraw() self.hide = True - self.title("") - self.overrideredirect(True) - - self.wm_attributes("-alpha", 0) - self.wm_attributes("-toolwindow", True) - - self.configure(fg_color="#fff") - self.settings = settings self.attach_widget = None self._view_variable = view_variable @@ -53,6 +45,15 @@ class _CreateErrorWindow(CTkToplevel): self.x_pos = None self.y_pos = None + self.title("") + self.overrideredirect(True) + + self.wm_attributes("-alpha", 0) + self.wm_attributes("-toolwindow", True) + + self.configure(fg_color=self.message_bg_color) + + self.grid_rowconfigure(0,weight=1) diff --git a/vrct_gui/_CreateSelectableLanguagesWindow.py b/vrct_gui/_CreateSelectableLanguagesWindow.py index 850c02cd..daa369c0 100644 --- a/vrct_gui/_CreateSelectableLanguagesWindow.py +++ b/vrct_gui/_CreateSelectableLanguagesWindow.py @@ -10,28 +10,23 @@ class _CreateSelectableLanguagesWindow(CTkToplevel): super().__init__() self.withdraw() - - self.title("_CreateSelectableLanguagesWindow") - self.overrideredirect(True) - self.attach = vrct_gui.main_bg_container self.vrct_gui = vrct_gui - - self.configure(fg_color="#ff7f50") - self.protocol("WM_DELETE_WINDOW", vrct_gui._closeSelectableLanguagesWindow) - self.settings = settings self._view_variable = view_variable + self.is_created = False + self.selectable_language_window_type = None + + self.title("_CreateSelectableLanguagesWindow") + self.overrideredirect(True) + self.configure(fg_color=self.settings.ctm.TOP_BG_COLOR) + self.protocol("WM_DELETE_WINDOW", vrct_gui._closeSelectableLanguagesWindow) self.bind("", self.focusOutFunction) - self.is_created = False - - - self.selectable_language_window_type = None def createContainer(self, selectable_language_window_type): self.selectable_language_window_type = selectable_language_window_type diff --git a/vrct_gui/_CreateWindowCover.py b/vrct_gui/_CreateWindowCover.py index 878ca45b..0ee3b606 100644 --- a/vrct_gui/_CreateWindowCover.py +++ b/vrct_gui/_CreateWindowCover.py @@ -11,20 +11,17 @@ class _CreateWindowCover(CTkToplevel): self.BIND_CONFIGURE_ADJUSTED_GEOMETRY_FUNC_ID=None self.BIND_FOCUS_IN_FUNC_ID=None - self.title("") - self.overrideredirect(True) - - self.wm_attributes("-toolwindow", True) - self.attach_window = attach_window - - - self.configure(fg_color="#ff7f50") - self.protocol("WM_DELETE_WINDOW", lambda: self.withdraw()) - self.settings = settings self._view_variable = view_variable + self.title("") + self.overrideredirect(True) + self.wm_attributes("-toolwindow", True) + self.configure(fg_color="black") + self.protocol("WM_DELETE_WINDOW", lambda: self.withdraw()) + + self.grid_rowconfigure(0,weight=1) self.grid_columnconfigure(0,weight=1) self.cover_container = CTkFrame(self, corner_radius=0, fg_color="black", width=0, height=0) diff --git a/vrct_gui/config_window/ConfigWindow.py b/vrct_gui/config_window/ConfigWindow.py index 1e9d776d..fc56daec 100644 --- a/vrct_gui/config_window/ConfigWindow.py +++ b/vrct_gui/config_window/ConfigWindow.py @@ -19,7 +19,7 @@ class ConfigWindow(CTkToplevel): self.geometry(f"{self.settings.uism.DEFAULT_WIDTH}x{self.settings.uism.DEFAULT_HEIGHT}") - self.configure(fg_color="#ff7f50") + self.configure(fg_color=self.settings.ctm.MAIN_BG_COLOR) self.protocol("WM_DELETE_WINDOW", self._view_variable.CALLBACK_CLICKED_CLOSE_CONFIG_WINDOW_BUTTON) diff --git a/vrct_gui/main_window/createMainWindowWidgets.py b/vrct_gui/main_window/createMainWindowWidgets.py index 94498bfa..2bcea306 100644 --- a/vrct_gui/main_window/createMainWindowWidgets.py +++ b/vrct_gui/main_window/createMainWindowWidgets.py @@ -19,7 +19,7 @@ def createMainWindowWidgets(vrct_gui, settings, view_variable): vrct_gui.grid_rowconfigure(0, weight=1) # vrct_gui.grid_columnconfigure(0, weight=1, minsize=settings.uism.MAIN_AREA_MIN_WIDTH) - vrct_gui.configure(fg_color="#ff7f50") + vrct_gui.configure(fg_color=settings.ctm.MAIN_BG_COLOR) vrct_gui.toplevel_wrapper = CTkFrame(vrct_gui, corner_radius=0, fg_color=settings.ctm.MAIN_BG_COLOR, width=0, height=0) vrct_gui.toplevel_wrapper.grid(row=0, column=0, sticky="nsew") From 12175a078864c43287108fe83fa8215a41b963e3 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 20 Oct 2023 10:29:46 +0900 Subject: [PATCH 345/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Model=20:=20?= =?UTF-8?q?=E7=BF=BB=E8=A8=B3=E3=82=A8=E3=83=B3=E3=82=B8=E3=83=B3=E3=81=B8?= =?UTF-8?q?=E3=81=AErequest=E3=81=8C=E5=A4=B1=E6=95=97=E3=81=97=E3=81=9F?= =?UTF-8?q?=E5=A0=B4=E5=90=88=E3=81=AB=E3=82=A8=E3=83=A9=E3=83=BC=E3=83=AD?= =?UTF-8?q?=E3=82=B0=E3=82=92=E5=87=BA=E5=8A=9B=E3=81=99=E3=82=8B=E6=A9=9F?= =?UTF-8?q?=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 22 +++--- model.py | 74 +++++++++----------- models/translation/translation_translator.py | 2 +- 3 files changed, 47 insertions(+), 51 deletions(-) diff --git a/controller.py b/controller.py index bb7fae7b..2082840e 100644 --- a/controller.py +++ b/controller.py @@ -26,9 +26,10 @@ def sendMicMessage(message): view.printToTextbox_AuthenticationError() else: translation = model.getInputTranslate(message) - - if translation == None: - translation = "" + if translation == False: + config.ENABLE_TRANSLATION = False + translation = "" + view.translationEngineLimitErrorProcess() if config.ENABLE_TRANSCRIPTION_SEND is True: if config.ENABLE_SEND_MESSAGE_TO_VRC is True: @@ -91,9 +92,10 @@ def receiveSpeakerMessage(message): view.printToTextbox_AuthenticationError() else: translation = model.getOutputTranslate(message) - - if translation == None: - translation = "" + if translation == False: + config.ENABLE_TRANSLATION = False + translation = "" + view.translationEngineLimitErrorProcess() if config.ENABLE_TRANSCRIPTION_RECEIVE is True: if config.ENABLE_NOTICE_XSOVERLAY is True: @@ -158,9 +160,10 @@ def sendChatMessage(message): view.printToTextbox_AuthenticationError() else: translation = model.getInputTranslate(message) - - if translation == None: - translation = "" + if translation == False: + config.ENABLE_TRANSLATION = False + translation = "" + view.translationEngineLimitErrorProcess() # send OSC message if config.ENABLE_SEND_MESSAGE_TO_VRC is True: @@ -389,7 +392,6 @@ def callbackSetDeeplAuthkey(value): config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) else: view.printToTextbox_AuthenticationError() - print(config.AUTH_KEYS, config.CHOICE_TRANSLATOR) # Transcription Tab (Mic) def callbackSetMicHost(value): diff --git a/model.py b/model.py index 91507df1..8f25c706 100644 --- a/model.py +++ b/model.py @@ -142,51 +142,45 @@ class Model: return self.translator.translator_status[config.CHOICE_TRANSLATOR] def getInputTranslate(self, message): - try: - if config.CHOICE_TRANSLATOR == "DeepL_API": - if config.TARGET_LANGUAGE == "English": - if config.TARGET_COUNTRY in ["United States", "Canada", "Philippines"]: - config.TARGET_LANGUAGE = "English American" - else: - config.TARGET_LANGUAGE = "English British" - elif config.TARGET_LANGUAGE in ["Portuguese"]: - if config.TARGET_COUNTRY == "Portugal": - config.TARGET_LANGUAGE = "Portuguese European" - else: - config.TARGET_LANGUAGE = "Portuguese Brazilian" + if config.CHOICE_TRANSLATOR == "DeepL_API": + if config.TARGET_LANGUAGE == "English": + if config.TARGET_COUNTRY in ["United States", "Canada", "Philippines"]: + config.TARGET_LANGUAGE = "English American" + else: + config.TARGET_LANGUAGE = "English British" + elif config.TARGET_LANGUAGE in ["Portuguese"]: + if config.TARGET_COUNTRY == "Portugal": + config.TARGET_LANGUAGE = "Portuguese European" + else: + config.TARGET_LANGUAGE = "Portuguese Brazilian" - translation = self.translator.translate( - translator_name=config.CHOICE_TRANSLATOR, - source_language=config.SOURCE_LANGUAGE, - target_language=config.TARGET_LANGUAGE, - message=message - ) - except: - translation = None + translation = self.translator.translate( + translator_name=config.CHOICE_TRANSLATOR, + source_language=config.SOURCE_LANGUAGE, + target_language=config.TARGET_LANGUAGE, + message=message + ) return translation def getOutputTranslate(self, message): - try: - if config.CHOICE_TRANSLATOR == "DeepL_API": - if config.SOURCE_LANGUAGE == "English": - if config.SOURCE_COUNTRY in ["United States", "Canada", "Philippines"]: - config.SOURCE_LANGUAGE = "English American" - else: - config.SOURCE_LANGUAGE = "English British" - elif config.SOURCE_LANGUAGE in ["Portuguese"]: - if config.SOURCE_COUNTRY == "Portugal": - config.SOURCE_LANGUAGE = "Portuguese European" - else: - config.SOURCE_LANGUAGE = "Portuguese Brazilian" + if config.CHOICE_TRANSLATOR == "DeepL_API": + if config.SOURCE_LANGUAGE == "English": + if config.SOURCE_COUNTRY in ["United States", "Canada", "Philippines"]: + config.SOURCE_LANGUAGE = "English American" + else: + config.SOURCE_LANGUAGE = "English British" + elif config.SOURCE_LANGUAGE in ["Portuguese"]: + if config.SOURCE_COUNTRY == "Portugal": + config.SOURCE_LANGUAGE = "Portuguese European" + else: + config.SOURCE_LANGUAGE = "Portuguese Brazilian" - translation = self.translator.translate( - translator_name=config.CHOICE_TRANSLATOR, - source_language=config.TARGET_LANGUAGE, - target_language=config.SOURCE_LANGUAGE, - message=message - ) - except: - translation = None + translation = self.translator.translate( + translator_name=config.CHOICE_TRANSLATOR, + source_language=config.TARGET_LANGUAGE, + target_language=config.SOURCE_LANGUAGE, + message=message + ) return translation def addKeywords(self): diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index 4f4a1550..bcca750f 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -66,5 +66,5 @@ class Translator(): to_language=target_language, ) except: - pass + result = False return result \ No newline at end of file From ff0e13caf03f86ce07cce0420989deab6ca6c206 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 20 Oct 2023 10:36:03 +0900 Subject: [PATCH 346/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Main=20:=20error?= =?UTF-8?q?.log=E3=82=92=E5=87=BA=E5=8A=9B=E3=81=99=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=E6=A9=9F=E8=83=BD=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/main.py b/main.py index 2c68e5d2..5db42c69 100644 --- a/main.py +++ b/main.py @@ -1,13 +1,18 @@ -import ctypes -ctypes.windll.shcore.SetProcessDpiAwareness(0) - -from vrct_gui.splash_window import SplashWindow -splash = SplashWindow() -splash.showSplash() - -import controller - if __name__ == "__main__": - controller.createMainWindow() - splash.destroySplash() - controller.showMainWindow() \ No newline at end of file + try: + import ctypes + ctypes.windll.shcore.SetProcessDpiAwareness(0) + + from vrct_gui.splash_window import SplashWindow + splash = SplashWindow() + splash.showSplash() + + import controller + controller.createMainWindow() + splash.destroySplash() + controller.showMainWindow() + + except Exception as e: + import traceback + with open('error.log', 'a') as f: + traceback.print_exc(file=f) \ No newline at end of file From b63990d4ceb267672ed908fa8714c72683b8cb81 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 20 Oct 2023 14:35:29 +0900 Subject: [PATCH 347/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20auth?= =?UTF-8?q?=20key=E3=82=92=E8=A8=98=E5=85=A5=E3=81=97=E3=81=9F=E3=81=82?= =?UTF-8?q?=E3=81=A8=E3=81=AB=E3=80=81=E5=89=8A=E9=99=A4=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=81=A8=E7=BF=BB=E8=A8=B3=E3=81=AB=E5=A4=B1=E6=95=97=E3=81=99?= =?UTF-8?q?=E3=82=8B=E3=83=90=E3=82=B0=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- controller.py | 30 ++++++++------------ model.py | 20 +++++++++---- models/translation/translation_translator.py | 20 +++---------- 3 files changed, 30 insertions(+), 40 deletions(-) diff --git a/controller.py b/controller.py index 2082840e..d8dfc1fb 100644 --- a/controller.py +++ b/controller.py @@ -22,8 +22,6 @@ def sendMicMessage(message): return elif config.ENABLE_TRANSLATION is False: pass - elif model.getTranslatorStatus() is False: - view.printToTextbox_AuthenticationError() else: translation = model.getInputTranslate(message) if translation == False: @@ -88,8 +86,6 @@ def receiveSpeakerMessage(message): translation = "" if config.ENABLE_TRANSLATION is False: pass - elif model.getTranslatorStatus() is False: - view.printToTextbox_AuthenticationError() else: translation = model.getOutputTranslate(message) if translation == False: @@ -156,8 +152,6 @@ def sendChatMessage(message): translation = "" if config.ENABLE_TRANSLATION is False: pass - elif model.getTranslatorStatus() is False: - view.printToTextbox_AuthenticationError() else: translation = model.getInputTranslate(message) if translation == False: @@ -215,7 +209,6 @@ def initSetLanguageAndCountry(): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator() def setYourLanguageAndCountry(select): languages = config.SELECTED_TAB_YOUR_LANGUAGES @@ -225,7 +218,6 @@ def setYourLanguageAndCountry(select): config.SOURCE_LANGUAGE = language config.SOURCE_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator() view.printToTextbox_selectedYourLanguages(select) def setTargetLanguageAndCountry(select): @@ -236,7 +228,6 @@ def setTargetLanguageAndCountry(select): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator() view.printToTextbox_selectedTargetLanguages(select) def callbackSelectedLanguagePresetTab(selected_tab_no): @@ -253,7 +244,6 @@ def callbackSelectedLanguagePresetTab(selected_tab_no): config.TARGET_LANGUAGE = language config.TARGET_COUNTRY = country config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator() view.printToTextbox_changedLanguagePresetTab(config.SELECTED_TAB_NO) # command func @@ -377,21 +367,23 @@ def callbackSetUiLanguage(value): # Translation Tab def callbackSetDeeplAuthkey(value): print("callbackSetDeeplAuthkey", str(value)) - if len(value) > 0: + if len(value) == 39: result = model.authenticationTranslator(choice_translator="DeepL_API", auth_key=value) if result is True: - auth_keys = config.AUTH_KEYS - auth_keys["DeepL_API"] = value - config.AUTH_KEYS = auth_keys + key = value view.printToTextbox_AuthenticationSuccess() + else: + key = None + view.printToTextbox_AuthenticationError() + auth_keys = config.AUTH_KEYS + auth_keys["DeepL_API"] = key + config.AUTH_KEYS = auth_keys config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) elif len(value) == 0: auth_keys = config.AUTH_KEYS auth_keys["DeepL_API"] = None config.AUTH_KEYS = auth_keys config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - else: - view.printToTextbox_AuthenticationError() # Transcription Tab (Mic) def callbackSetMicHost(value): @@ -638,11 +630,13 @@ def createMainWindow(): # init config initSetLanguageAndCountry() - if model.authenticationTranslator() is False: + if model.authenticationTranslator(config.CHOICE_TRANSLATOR, config.AUTH_KEYS[config.CHOICE_TRANSLATOR]) is False: # error update Auth key + auth_keys = config.AUTH_KEYS + auth_keys[config.CHOICE_TRANSLATOR] = None + config.AUTH_KEYS = auth_keys view.printToTextbox_AuthenticationError() config.CHOICE_TRANSLATOR = model.findTranslationEngine(config.SOURCE_LANGUAGE, config.TARGET_LANGUAGE) - model.authenticationTranslator() # set word filter model.addKeywords() diff --git a/model.py b/model.py index 8f25c706..8d0f0083 100644 --- a/model.py +++ b/model.py @@ -121,8 +121,7 @@ class Model: country = parts[1][1:-1] return language, country - @staticmethod - def findTranslationEngine(source_lang, target_lang): + def findTranslationEngine(self, source_lang, target_lang): compatible_engines = [] for engine in translatorEngine: source_languages = translation_lang.get(engine, {}).get("source", {}) @@ -132,16 +131,15 @@ class Model: engine_name = compatible_engines[0] if engine_name == "DeepL" and config.AUTH_KEYS["DeepL_API"] != None: - engine_name = "DeepL_API" + if self.authenticationTranslator(engine_name, config.AUTH_KEYS["DeepL_API"]) is True: + engine_name = "DeepL_API" elif engine_name == "DeepL_API" and config.AUTH_KEYS["DeepL_API"] == None: engine_name = "DeepL" return engine_name - def getTranslatorStatus(self): - return self.translator.translator_status[config.CHOICE_TRANSLATOR] - def getInputTranslate(self, message): + print(config.CHOICE_TRANSLATOR, config.AUTH_KEYS) if config.CHOICE_TRANSLATOR == "DeepL_API": if config.TARGET_LANGUAGE == "English": if config.TARGET_COUNTRY in ["United States", "Canada", "Philippines"]: @@ -153,6 +151,11 @@ class Model: config.TARGET_LANGUAGE = "Portuguese European" else: config.TARGET_LANGUAGE = "Portuguese Brazilian" + elif config.CHOICE_TRANSLATOR == "DeepL": + if config.TARGET_LANGUAGE in ["English American", "English British"]: + config.TARGET_LANGUAGE = "English" + elif config.TARGET_LANGUAGE in ["Portuguese European", "Portuguese Brazilian"]: + config.TARGET_COUNTRY = "Portugal" translation = self.translator.translate( translator_name=config.CHOICE_TRANSLATOR, @@ -174,6 +177,11 @@ class Model: config.SOURCE_LANGUAGE = "Portuguese European" else: config.SOURCE_LANGUAGE = "Portuguese Brazilian" + elif config.CHOICE_TRANSLATOR == "DeepL": + if config.SOURCE_LANGUAGE in ["English American", "English British"]: + config.SOURCE_LANGUAGE = "English" + elif config.SOURCE_LANGUAGE in ["Portuguese European", "Portuguese Brazilian"]: + config.SOURCE_COUNTRY = "Portugal" translation = self.translator.translate( translator_name=config.CHOICE_TRANSLATOR, diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index bcca750f..81bd0ef5 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -6,34 +6,22 @@ from .translation_languages import translatorEngine, translation_lang # Translator class Translator(): def __init__(self): + pass self.translator_status = {} - for translator in translatorEngine: - self.translator_status[translator] = False - self.deepl_client = None def authentication(self, translator_name, authkey=None): - result = False + result = True match translator_name: - case "DeepL": - self.translator_status[translator_name] = True - result = True case "DeepL_API": try: self.deepl_client = deepl_Translator(authkey) self.deepl_client.translate_text(" ", target_lang="EN-US") - self.translator_status[translator_name] = True - result = True except: - self.translator_status[translator_name] = False - case "Google": - self.translator_status[translator_name] = True - result = True - case "Bing": - self.translator_status[translator_name] = True - result = True + result = False return result def translate(self, translator_name, source_language, target_language, message): + print(translator_name, source_language, target_language, message) try: result = "" source_language=translation_lang[translator_name]["source"][source_language] From 46ebd84c396275d4628f12eb8a0bdc65d8128e5a Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 20 Oct 2023 14:38:19 +0900 Subject: [PATCH 348/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Config=20:=20ver?= =?UTF-8?q?sion=E3=82=922.0.0=20alpha=205=E3=81=AB=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index a684e649..73e94f6e 100644 --- a/config.py +++ b/config.py @@ -508,7 +508,7 @@ class Config: def init_config(self): # Read Only - self._VERSION = "2.0.0 alpha 4.1" + self._VERSION = "2.0.0 alpha 5" self._PATH_CONFIG = os_path.join(os_path.dirname(sys.argv[0]), "config.json") self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" self._BOOTH_URL = "https://misyaguziya.booth.pm/" From 5f3f3afeb7b992444d68d140028ffef75d36ff1a Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 20 Oct 2023 17:29:36 +0900 Subject: [PATCH 349/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20prin?= =?UTF-8?q?t=E6=96=87=E5=89=8A=E9=99=A4/speaker=20energy=20=E3=81=AE?= =?UTF-8?q?=E3=82=A8=E3=83=A9=E3=83=BC=E7=99=BA=E7=94=9F=E5=95=8F=E9=A1=8C?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 1 - models/transcription/transcription_recorder.py | 1 + models/translation/translation_translator.py | 1 - 3 files changed, 1 insertion(+), 2 deletions(-) diff --git a/model.py b/model.py index 8d0f0083..f084a27c 100644 --- a/model.py +++ b/model.py @@ -139,7 +139,6 @@ class Model: return engine_name def getInputTranslate(self, message): - print(config.CHOICE_TRANSLATOR, config.AUTH_KEYS) if config.CHOICE_TRANSLATOR == "DeepL_API": if config.TARGET_LANGUAGE == "English": if config.TARGET_COUNTRY in ["United States", "Canada", "Philippines"]: diff --git a/models/transcription/transcription_recorder.py b/models/transcription/transcription_recorder.py index 4e46cfc7..9abe5eb4 100644 --- a/models/transcription/transcription_recorder.py +++ b/models/transcription/transcription_recorder.py @@ -84,6 +84,7 @@ class SelectedSpeakeEnergyRecorder(BaseEnergyRecorder): source = Microphone(speaker=True, device_index= device["index"], sample_rate=int(device["defaultSampleRate"]), + channels=device["maxInputChannels"] ) super().__init__(source=source) # self.adjustForNoise() \ No newline at end of file diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index 81bd0ef5..ba4a36d6 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -21,7 +21,6 @@ class Translator(): return result def translate(self, translator_name, source_language, target_language, message): - print(translator_name, source_language, target_language, message) try: result = "" source_language=translation_lang[translator_name]["source"][source_language] From c001e5c0697519849fe5d5b6db193fb12bf71c9e Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Fri, 20 Oct 2023 18:26:28 +0900 Subject: [PATCH 350/355] [Update] add(move) UI Language Korean. that was used in v1.3.x too. --- languages.py | 2 +- locales/ko.yml | 187 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 188 insertions(+), 1 deletion(-) create mode 100644 locales/ko.yml diff --git a/languages.py b/languages.py index bac2eb0f..4c7b8c94 100644 --- a/languages.py +++ b/languages.py @@ -1,6 +1,6 @@ selectable_languages = { "en": "English", "ja": "日本語", - "ko": "한국어" + "ko": "한국어(일부 지원)" # 新しい言語とキーを追加する場合はここに追記してください } \ No newline at end of file diff --git a/locales/ko.yml b/locales/ko.yml new file mode 100644 index 00000000..ad08942f --- /dev/null +++ b/locales/ko.yml @@ -0,0 +1,187 @@ +main_window: + translation: 번역 + transcription_send: 마이크 -> 챗박스 + transcription_receive: 스피커 -> 로그 + foreground: 항상 위로 + + # language_settings: Language Settings + # your_language: Your Language + # both_direction_desc: Translate Each Other + # target_language: Target Language + + textbox_tab_all: 전체 + textbox_tab_sent: 전송 + textbox_tab_received: 수신 + textbox_tab_system: 시스템 + + # textbox_system_message: + # enabled_translation: Translation feature is turned on. + # disabled_translation: Translation feature is turned off. + # enabled_voice2chatbox: Transcription from the microphone has started. + # disabled_voice2chatbox: Transcription from the microphone has been stopped. + # enabled_speaker2log: Transcription from the speaker has started. + # disabled_speaker2log: Transcription from the speaker has been stopped. + # enabled_foreground: The screen is fixed in the foreground. + # disabled_foreground: The foreground fixation has been released. + + # auth_key_success: Auth key update completed. + # auth_key_error: Auth Key is incorrect or Usage limit reached. + + # no_mic_device_detected_error: No mic device detected. + # no_speaker_device_detected_error: No speaker device detected. + # translation_engine_limit_error: It has automatically disabled the translation feature. Access has been temporarily restricted due to an excessive number of requests to the translation engine. Please wait for a while, restart VRCT, and try again. + + # detected_by_word_filter: The word %{detected_message} has not been sent due to detection by the word filter. + + # selected_your_language: "\"Your Language\" has set to %{your_language}." + # selected_target_language: "\"Target Language\" has set to %{target_language}." + # switched_language_preset_tab: Switched to Language Preset Tab No.%{tab_no}." + # latest_language_setting: "Currently, \"Your Language\" is set to %{your_language}, and \"Target Language\" is set to %{target_language}." + + # opened_web_page_booth: Opened Booth page in your web browser. + # opened_web_page_vrct_documents: "Opened VRCT Documents page in your web browser.\nFor any issues, requests, or inquiries, please feel free to contact us through the links at the bottom of the documents page, the \"Contact Form,\" or via X (formerly Twitter)!" + + # update_available: New version is here! + + # cover_message: The functionality is temporarily disabled until the settings window is closed. + + # confirmation_message: + # update_software: "Download new version and restart automatically.\nIt'll take a while. Do it now?" + # deny_update_software: Do it later + # accept_update_software: Update and Restart + # updating: Now updating... + + # detected_over_ui_size: "Current UI Size: %{current_ui_size}\nVRCT's window size may be larger than your display size.\n* Depending on your display size, you may need to adjust it multiple times." + # deny_adjust_ui_size: "Keep it at this size" + # accept_adjust_ui_size: "Set it smaller and restart" + + + # translation_engine_limit_error: "It has automatically disabled the translation feature.\nAccess has been temporarily restricted\ndue to an excessive number of requests to the translation engine.\nPlease wait for a while, restart VRCT, and try again." + # accept_translation_engine_limit_error: Accept and close + + +# selectable_language_window: +# title_your_language: Select Your Language +# title_target_language: Select Target Language +# go_back_button: Go Back + + +config_window: + # config_title: Settings + # compact_mode: Compact Mode + # version: version %{version} + # restart_message: Apply changes with a restart. + # common_error_message: + # invalid_value: Invalid value. + + side_menu_labels: + # appearance: Appearance + translation: 번역 + transcription: 음성인식 + transcription_mic: 마이크 + transcription_speaker: 스피커 + others: 기타 + # advanced_settings: Advanced Settings + + + transparency: + label: 투명도 + # desc: Change the main window's transparency. + + appearance_theme: + label: 테마 [Under development] + # desc: Change the color theme. Currently, only the Dark theme is supported. The Light theme is under development. + + ui_size: + label: UI 크기 + + font_family: + label: 폰트 + + ui_language: + label: UI 언어 / UI Language + + deepl_auth_key: + label: DeepL 인증키 + + mic_host: + label: 마이크 호스트/Driver + + mic_device: + label: 마이크 장치 + + mic_dynamic_energy_threshold: + label_for_automatic: "음성 입력 최소 볼륨 (Current Setting: Automatic)" + # desc_for_automatic: "Automatically determine microphone input sensitivity." + label_for_manual: "음성 입력 최소 볼륨 (Current Setting: Manual)" + # desc_for_manual: "Manually determine the microphone input sensitivity using the slider. Press the microphone icon to input your voice and adjust the sensitivity while monitoring the volume." + # error_message: You can set it with a value between 0 to %{max}. + + mic_record_timeout: + label: 최대 무음 시간 + # desc: Detects silence and, when the specified number of seconds has passed, considers the mic input to have ended. (Second(s)) + # error_message: It cannot be greater than '%{mic_phrase_timeout_label}' with a value of 0 or more. + + mic_phrase_timeout: + label: 최대 인식 시간 + # desc: Transcription processing is performed at intervals of the specified number of seconds. + # error_message: It cannot be set lower than '%{mic_record_timeout_label}' with a value of 0 or more. + + mic_max_phrase: + label: 최대 입력 절(phrases) 수 + # desc: It is the lower limit for the number of transcribed words, and only when this number is exceeded will the transcription results be displayed logs and send to VRChat. + # error_message: You can set a number equal to or greater than 0. + + mic_word_filter: + label: 단어 필터 + # desc: "It will not send the sentence if the word(s) included in the set list of words.\nHow to set: e.g. AAA,BBB,CCC" + + + speaker_dynamic_energy_threshold: + label_for_automatic: "음성 입력 최소 볼륨 (Current Setting: Automatic)" + # desc_for_automatic: "Automatically determine speaker input sensitivity." + label_for_manual: "음성 입력 최소 볼륨 (Current Setting: Manual)" + # desc_for_manual: "Manually determine the speaker input sensitivity using the slider. Press the headphones icon to listen to the audio and adjust the sensitivity while monitoring the volume." + # error_message: You can set it with a value between 0 to %{max}. + # no_device_error_message: No speaker device detected. + + speaker_record_timeout: + label: 최대 무음 시간 + # desc: Detects silence and, when the specified number of seconds has passed, considers the speaker input to have ended. (Second(s)) + # error_message: It cannot be greater than '%{speaker_phrase_timeout_label}' with a value of 0 or more. + + speaker_phrase_timeout: + label: 최대 인식 시간 + # desc: Transcription processing is performed at intervals of the specified number of seconds. + # error_message: It cannot be set lower than '%{speaker_record_timeout_label}' with a value of 0 or more. + + speaker_max_phrase: + label: 최대 입력 절(phrases) 수 + # desc: It is the lower limit for the number of transcribed words, and only when this number is exceeded will the transcription results be displayed logs. + # error_message: You can set a number equal to or greater than 0. + + + auto_clear_the_message_box: + label: 챗박스 자동 삭제 + + # notice_xsoverlay: + # label: Notification XSOverlay (VR Only) + # desc: Notify received messages by using XSOverlay's notification feature. + + # auto_export_message_logs: + # label: Auto Export Message Logs + # desc: Automatically export the conversation messages as a text file. + + message_format: + label: 전송 형식 + # desc: "You can change the decoration of the message you want to send.\n[message] will be replaced with the message, and [translation] will be replaced with the translated message.\nIt will be used in Notification XSOverlay too." + + # send_message_to_vrc: + # label: Send Message To VRChat + # desc: There is a way to use it without sending messages to VRChat, but it is not supported. Enable this feature when you intend to send a message to VRChat. + + osc_ip_address: + label: OSC IP 주소 + + osc_port: + label: OSC 포트 \ No newline at end of file From e52321132d4f8e2e76bacc59a0e8a69b31fac01c Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 20 Oct 2023 18:32:42 +0900 Subject: [PATCH 351/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20?= =?UTF-8?q?=E5=87=A6=E7=90=86=E8=B2=A0=E8=8D=B7=E8=BB=BD=E6=B8=9B=E3=81=AE?= =?UTF-8?q?=E3=81=9F=E3=82=81=E3=81=ABsleep=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/model.py b/model.py index f084a27c..7b89e854 100644 --- a/model.py +++ b/model.py @@ -368,7 +368,7 @@ class Model: fnc(energy) except: pass - # sleep(0.01) + sleep(0.01) mic_energy_queue = Queue() mic_device = [device for device in getInputDevices()[config.CHOICE_MIC_HOST] if device["name"] == config.CHOICE_MIC_DEVICE][0] @@ -450,7 +450,7 @@ class Model: fnc(energy) except: pass - # sleep(0.01) + sleep(0.01) speaker_energy_queue = Queue() self.speaker_energy_recorder = SelectedSpeakeEnergyRecorder(speaker_device) From f3b218e86e98d2c2e9c4b4342af69f11932c81c6 Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Fri, 20 Oct 2023 23:58:46 +0900 Subject: [PATCH 352/355] =?UTF-8?q?=F0=9F=90=9B[bugfix]=20Model=20:=20mode?= =?UTF-8?q?l=E3=81=A7config=E3=81=AB=E3=82=BB=E3=83=83=E3=83=88=E3=81=97?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=81=9F=E3=81=9F=E3=82=81=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model.py | 78 +++++++++++--------- models/translation/translation_translator.py | 5 +- 2 files changed, 48 insertions(+), 35 deletions(-) diff --git a/model.py b/model.py index 7b89e854..51a881dd 100644 --- a/model.py +++ b/model.py @@ -139,53 +139,63 @@ class Model: return engine_name def getInputTranslate(self, message): - if config.CHOICE_TRANSLATOR == "DeepL_API": - if config.TARGET_LANGUAGE == "English": - if config.TARGET_COUNTRY in ["United States", "Canada", "Philippines"]: - config.TARGET_LANGUAGE = "English American" + translator_name=config.CHOICE_TRANSLATOR + source_language=config.SOURCE_LANGUAGE + target_language=config.TARGET_LANGUAGE + target_country = config.TARGET_COUNTRY + + if translator_name == "DeepL_API": + if target_language == "English": + if target_country in ["United States", "Canada", "Philippines"]: + target_language = "English American" else: - config.TARGET_LANGUAGE = "English British" - elif config.TARGET_LANGUAGE in ["Portuguese"]: - if config.TARGET_COUNTRY == "Portugal": - config.TARGET_LANGUAGE = "Portuguese European" + target_language = "English British" + elif target_language == "Portuguese": + if target_country in ["Portugal"]: + target_language = "Portuguese European" else: - config.TARGET_LANGUAGE = "Portuguese Brazilian" - elif config.CHOICE_TRANSLATOR == "DeepL": - if config.TARGET_LANGUAGE in ["English American", "English British"]: - config.TARGET_LANGUAGE = "English" - elif config.TARGET_LANGUAGE in ["Portuguese European", "Portuguese Brazilian"]: - config.TARGET_COUNTRY = "Portugal" + target_language = "Portuguese Brazilian" + elif translator_name == "DeepL": + if target_language in ["English American", "English British"]: + target_language = "English" + elif target_language in ["Portuguese European", "Portuguese Brazilian"]: + target_language = "Portuguese" translation = self.translator.translate( - translator_name=config.CHOICE_TRANSLATOR, - source_language=config.SOURCE_LANGUAGE, - target_language=config.TARGET_LANGUAGE, + translator_name=translator_name, + source_language=source_language, + target_language=target_language, message=message ) return translation def getOutputTranslate(self, message): - if config.CHOICE_TRANSLATOR == "DeepL_API": - if config.SOURCE_LANGUAGE == "English": - if config.SOURCE_COUNTRY in ["United States", "Canada", "Philippines"]: - config.SOURCE_LANGUAGE = "English American" + translator_name=config.CHOICE_TRANSLATOR + source_language=config.TARGET_LANGUAGE + target_language=config.SOURCE_LANGUAGE + target_country = config.SOURCE_COUNTRY + + if translator_name == "DeepL_API": + if target_language == "English": + if target_country in ["United States", "Canada", "Philippines"]: + target_language = "English American" else: - config.SOURCE_LANGUAGE = "English British" - elif config.SOURCE_LANGUAGE in ["Portuguese"]: - if config.SOURCE_COUNTRY == "Portugal": - config.SOURCE_LANGUAGE = "Portuguese European" + target_language = "English British" + elif target_language == "Portuguese": + if target_country in ["Portugal"]: + target_language = "Portuguese European" else: - config.SOURCE_LANGUAGE = "Portuguese Brazilian" - elif config.CHOICE_TRANSLATOR == "DeepL": - if config.SOURCE_LANGUAGE in ["English American", "English British"]: - config.SOURCE_LANGUAGE = "English" - elif config.SOURCE_LANGUAGE in ["Portuguese European", "Portuguese Brazilian"]: - config.SOURCE_COUNTRY = "Portugal" + target_language = "Portuguese Brazilian" + elif translator_name == "DeepL": + if target_language in ["English American", "English British"]: + target_language = "English" + elif target_language in ["Portuguese European", "Portuguese Brazilian"]: + target_language = "Portuguese" translation = self.translator.translate( - translator_name=config.CHOICE_TRANSLATOR, - source_language=config.TARGET_LANGUAGE, - target_language=config.SOURCE_LANGUAGE, + translator_name=translator_name, + source_language=source_language, + target_language=target_language, message=message ) return translation diff --git a/models/translation/translation_translator.py b/models/translation/translation_translator.py index ba4a36d6..18d2c394 100644 --- a/models/translation/translation_translator.py +++ b/models/translation/translation_translator.py @@ -52,6 +52,9 @@ class Translator(): from_language=source_language, to_language=target_language, ) - except: + except Exception as e: + import traceback + with open('error.log', 'a') as f: + traceback.print_exc(file=f) result = False return result \ No newline at end of file From bea88879af6e4c34ea509f8a157bc862be3fb30a Mon Sep 17 00:00:00 2001 From: Sakamoto Shiina <68018796+ShiinaSakamoto@users.noreply.github.com> Date: Sat, 21 Oct 2023 00:02:50 +0900 Subject: [PATCH 353/355] =?UTF-8?q?[Chore]=20Config=20Window:=20=E8=A8=AD?= =?UTF-8?q?=E5=AE=9A=E9=A0=85=E7=9B=AE=E3=81=AE=E3=83=A9=E3=83=99=E3=83=AB?= =?UTF-8?q?=E5=90=8D=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- locales/en.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/locales/en.yml b/locales/en.yml index 6f17aa3a..3e5c4aff 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -128,7 +128,7 @@ config_window: error_message: It cannot be set lower than '%{mic_record_timeout_label}' with a value of 0 or more. mic_max_phrase: - label: Mic Max Phrases + label: Mic Max Words desc: It is the lower limit for the number of transcribed words, and only when this number is exceeded will the transcription results be displayed logs and send to VRChat. error_message: You can set a number equal to or greater than 0. @@ -156,7 +156,7 @@ config_window: error_message: It cannot be set lower than '%{speaker_record_timeout_label}' with a value of 0 or more. speaker_max_phrase: - label: Speaker Max Phrases + label: Speaker Max Words desc: It is the lower limit for the number of transcribed words, and only when this number is exceeded will the transcription results be displayed logs. error_message: You can set a number equal to or greater than 0. From e2f87e94dd2162cbbe33c352bb74cd31995168eb Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 21 Oct 2023 01:00:48 +0900 Subject: [PATCH 354/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20README=20:=20you?= =?UTF-8?q?tube=E3=83=AA=E3=83=B3=E3=82=AF=E3=82=92=E5=A4=89=E6=9B=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.jp.md | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.jp.md b/README.jp.md index b281eec6..586912c5 100644 --- a/README.jp.md +++ b/README.jp.md @@ -41,7 +41,7 @@ VRCTはあなたの会話を以下でサポートをします。 # 使い方(Youtube)
-[![](https://img.youtube.com/vi/mI4DQaeaAPI/0.jpg)](https://www.youtube.com/watch?v=mI4DQaeaAPI) +[![](https://img.youtube.com/vi/rUTad037n8Q/0.jpg)](https://www.youtube.com/watch?v=rUTad037n8Q)
diff --git a/README.md b/README.md index 37d302cc..39faab7f 100644 --- a/README.md +++ b/README.md @@ -41,7 +41,7 @@ Initial setup, basic functions, and other features are also described. # How to Use (YouTube)
-[![](https://img.youtube.com/vi/mI4DQaeaAPI/0.jpg)](https://www.youtube.com/watch?v=mI4DQaeaAPI) +[![](https://img.youtube.com/vi/rUTad037n8Q/0.jpg)](https://www.youtube.com/watch?v=rUTad037n8Q)
From ffc505a712c71bece76ad2aa39aa3abdfefe330f Mon Sep 17 00:00:00 2001 From: misyaguziya Date: Sat, 21 Oct 2023 01:03:05 +0900 Subject: [PATCH 355/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20Config=20:=20ver?= =?UTF-8?q?sion=202.0.0=20alpha=205=20->=202.0.0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config.py b/config.py index 73e94f6e..65f8fa15 100644 --- a/config.py +++ b/config.py @@ -508,7 +508,7 @@ class Config: def init_config(self): # Read Only - self._VERSION = "2.0.0 alpha 5" + self._VERSION = "2.0.0" self._PATH_CONFIG = os_path.join(os_path.dirname(sys.argv[0]), "config.json") self._GITHUB_URL = "https://api.github.com/repos/misyaguziya/VRCT/releases/latest" self._BOOTH_URL = "https://misyaguziya.booth.pm/"

3v+SiVttv|FvWfOO*XCe_bzv$FBH~QjtrW)*~m& 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%0D&1Jv*aytfTIOOWz_Z0K&_-kt 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%0DSh7mva8`|=!V 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 YL&wtAc`+KD>YMV Date: Thu, 12 Oct 2023 11:27:45 +0900 Subject: [PATCH 265/355] =?UTF-8?q?=F0=9F=91=8D[Update]=20README=20:=20EN?= =?UTF-8?q?=E7=89=88=E3=82=92=E8=BF=BD=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.jp.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ README.md | 39 ++++++++++++++++++----------------- 2 files changed, 78 insertions(+), 19 deletions(-) create mode 100644 README.jp.md diff --git a/README.jp.md b/README.jp.md new file mode 100644 index 00000000..138da5d8 --- /dev/null +++ b/README.jp.md @@ -0,0 +1,58 @@ +