lots of ux improvements and small bug fixes

This commit is contained in:
Andrew Ward
2025-03-22 12:18:40 +00:00
parent 0e3cb8925a
commit a2a7c6f9a5
9 changed files with 81 additions and 42 deletions

View File

@@ -62,7 +62,7 @@ class AIEditorManager:
# Prompt entry renamed to "Copy Editing Rules" with a Text area
ttk.Label(main_frame, text="Copy Editing Rules:").grid(row=4, column=0, sticky=tk.NW, pady=2)
prompt_entry = tk.Text(main_frame, height=8, width=50)
prompt_entry = tk.Text(main_frame, height=8, width=50, background="white", font=("Arial", 10))
# Default prompt example if none exists
default_prompt = "Edit the text provided to ensure it has a clear, professional tone. Fix any grammatical errors, improve sentence structure, and maintain consistent formatting. Make the language concise and impactful while preserving the original meaning. Make sure to edit text only and do not reply to it."

View File

@@ -44,6 +44,8 @@ class PresetsManager:
# Bind to window resize for responsive layout
self.parent.bind("<Configure>", self.on_window_resize)
# This resize timer helps batch updates during resizing
self.resize_timer = None
def create_presets_section(self):
"""Create the presets section UI with accordion behavior."""
@@ -146,6 +148,9 @@ class PresetsManager:
# Configure the scroll region to update when the frame changes
self.presets_scrollable_frame.bind("<Configure>",
lambda e: self.presets_canvas.configure(scrollregion=self.presets_canvas.bbox("all")))
# Add a binding for the canvas size changes
self.presets_canvas.bind("<Configure>", self.on_canvas_resize)
# Populate tabs and presets
self.populate_tabs() # Refresh tabs to show selection
@@ -159,10 +164,9 @@ class PresetsManager:
"""Handler for window resize events to adjust the presets layout."""
# Only proceed if event is from the main window and presets are visible
if event and event.widget == self.parent and not self.presets_collapsed:
# Schedule a refresh after a short delay to prevent excessive updates during resize
if hasattr(self, 'resize_timer') and self.resize_timer:
self.parent.after_cancel(self.resize_timer)
self.resize_timer = self.parent.after(100, self.refresh_presets_display)
# We don't need to immediately refresh here, as on_canvas_resize will handle it
# This is because the window resize will trigger canvas resize events
pass
def _adjust_row_weights(self):
"""Adjust row weights to prioritize presets area expansion."""
@@ -313,14 +317,17 @@ class PresetsManager:
canvas_width = self.presets_canvas.winfo_width()
# Ensure we have a minimum width to calculate with
if canvas_width < 50: # If the canvas is too narrow or not yet realized
canvas_width = self.parent.winfo_width() - 30 # Estimate canvas width
canvas_width = self.parent.winfo_width() - 40 # Estimate canvas width with more margin for scrollbar
# Calculate number of columns (minimum 1, maximum 20)
min_card_width = 140 # Minimum width for each card
num_columns = max(1, min(20, canvas_width // min_card_width))
# Log for debugging - can be removed in production
print(f"Canvas width: {canvas_width}, Columns: {num_columns}")
# Dynamically adjust card width based on available space
preset_width = max(min_card_width, canvas_width // num_columns - 8)
preset_width = max(min_card_width, (canvas_width // num_columns) - 10) # Slightly more padding
preset_height = 100
# Configure columns to fill available space
@@ -715,4 +722,15 @@ class PresetsManager:
# Save and refresh
self.debounced_save()
self.refresh_presets_display()
self.refresh_presets_display()
def on_canvas_resize(self, event=None):
"""Handle resize events specifically for the presets canvas area."""
# Only process if presets are visible
if not self.presets_collapsed:
# Cancel any previous refresh timer to avoid multiple refreshes
if hasattr(self, 'resize_timer') and self.resize_timer:
self.parent.after_cancel(self.resize_timer)
# Schedule a refresh with a short delay to avoid excessive refreshes during drag
self.resize_timer = self.parent.after(150, self.refresh_presets_display)

View File

@@ -87,7 +87,7 @@ class SettingsManager:
settings_file = cls.get_settings_file_path()
with open(settings_file, "w") as f:
json.dump(settings, f)
json.dump(settings, f, indent=4)
@classmethod
def update_settings(cls, partial_settings):

View File

@@ -40,7 +40,7 @@ class TextToMic(tk.Tk):
def __init__(self):
super().__init__()
self.version = "1.3.5"
self.version = "1.3.0"
self.title(f"Text to Mic by Scorchsoft.com - v{self.version}")
@@ -57,7 +57,7 @@ class TextToMic(tk.Tk):
# Fixed window dimensions for all states - DEFINED ONCE as class constants
# These are the ONLY values that should be used throughout the application
self.BASE_WIDTH = 590
self.BASE_WIDTH = 600
self.BASE_HEIGHT_WITH_BANNER = 860
self.BASE_HEIGHT_NO_BANNER = 700
self.COLLAPSED_HEIGHT_WITH_BANNER = 630
@@ -188,45 +188,59 @@ class TextToMic(tk.Tk):
self.menubar = Menu(self)
self.config(menu=self.menubar)
# Get current hotkey settings
settings = self.load_settings()
hotkey_manager = self.hotkey_manager if hasattr(self, 'hotkey_manager') else None
# Format hotkeys for display in menus
if hotkey_manager:
replay_shortcut = hotkey_manager.format_shortcut(settings["hotkeys"]["play_last_audio"])
record_shortcut = hotkey_manager.format_shortcut(settings["hotkeys"]["record_start_stop"])
stop_shortcut = hotkey_manager.format_shortcut(settings["hotkeys"]["stop_recording"])
cancel_shortcut = hotkey_manager.format_shortcut(settings["hotkeys"]["cancel_operation"])
else:
# Default values if hotkey_manager isn't available
replay_shortcut = "Ctrl+Shift+8"
record_shortcut = "Ctrl+Shift+0"
stop_shortcut = "Ctrl+Shift+9"
cancel_shortcut = "Ctrl+Shift+1"
# File or settings menu
settings_menu = Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="Settings", menu=settings_menu)
settings_menu.add_command(label="Change API Key", command=self.change_api_key)
settings_menu.add_command(label="AI Copy Editing", command=self.show_ai_editor_settings)
settings_menu.add_command(label="Hotkey Settings", command=self.show_hotkey_settings)
settings_menu.add_command(label="Manage Tone Presets", command=self.show_tone_presets_manager)
settings_menu.add_command(label="API Key", command=self.change_api_key)
settings_menu.add_command(label="AI Copyediting", command=self.show_ai_editor_settings)
settings_menu.add_command(label="Keyboard Shortcuts", command=self.show_hotkey_settings)
settings_menu.add_command(label="Manage Tones", command=self.show_tone_presets_manager)
settings_menu.add_separator()
settings_menu.add_checkbutton(label="Auto Check for Updates", variable=self.auto_check_version, command=self.toggle_auto_version_check)
settings_menu.add_checkbutton(label="Hide Scorchsoft Banner", variable=self.banner_var, command=self.toggle_banner)
# Playback menu
playback_menu = Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="Playback", menu=playback_menu)
playback_menu.add_command(label="Play Last Audio", command=self.play_last_audio)
#apply_ai
input_menu = Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="Input", menu=input_menu)
input_menu.add_command(label="Apply AI Manipulation to Input Text", command=self.apply_ai_to_input)
self.menubar.add_cascade(label="Actions", menu=playback_menu)
# Add keyboard shortcuts to menu items
playback_menu.add_command(label=f"Replay [{replay_shortcut}]", command=self.play_last_audio)
playback_menu.add_command(label="Apply AI Copyedit", command=self.apply_ai_to_input)
playback_menu.add_separator()
playback_menu.add_command(label=f"Start/Stop Recording [{record_shortcut}]", command=self.handle_record_button_click)
playback_menu.add_command(label=f"Stop Recording [{stop_shortcut}]", command=lambda: self.stop_recording(auto_play=False))
playback_menu.add_command(label=f"Cancel Operation [{cancel_shortcut}]", command=self.stop_playback)
# Help menu
help_menu = Menu(self.menubar, tearoff=0)
self.menubar.add_cascade(label="Help", menu=help_menu)
help_menu.add_command(label="Check Version", command=self.check_version)
help_menu.add_command(label="How to Use", command=self.show_instructions)
help_menu.add_command(label="Terms of Use and Licence", command=self.show_terms_of_use)
help_menu.add_command(label="Hotkey Instructions", command=self.show_hotkey_instructions)
# Add toggle for banner visibility - use the existing banner_var from __init__
help_menu.add_checkbutton(label="Hide Banner", variable=self.banner_var, command=self.toggle_banner)
def show_hotkey_settings(self):
"""Show the hotkey settings dialog."""
HotkeyManager.hotkey_settings_dialog(self)
def show_hotkey_instructions(self):
"""Show hotkey instructions."""
HotkeyManager.show_hotkey_instructions(self)
def change_api_key(self):
"""Change the API key using APIKeyManager."""
@@ -401,12 +415,12 @@ class TextToMic(tk.Tk):
self.text_input = tk.Text(main_frame, height=5, width=68)
# Use white background for text input instead of the system background color
text_color = self.style.lookup('TLabel', 'foreground')
self.text_input.configure(bg="white", fg=text_color, insertbackground=text_color, wrap=tk.WORD)
self.text_input.configure(bg="white", fg=text_color, insertbackground=text_color, wrap=tk.WORD, font=("Arial", 10))
self.text_input.grid(column=0, row=5, columnspan=2, pady=(0, 20), sticky="nsew") # Proper spacing
# Add a status frame at the bottom of the text input with white background
status_frame = ttk.Frame(main_frame, style='White.TFrame')
status_frame.grid(column=0, row=5, columnspan=2, sticky=(tk.S, tk.E), pady=(0, 25))
status_frame.grid(column=0, row=5, columnspan=2, sticky=(tk.S, tk.E), pady=(0, 25), padx=(0, 5)) # Add right padding to shift the frame inward
# Create a custom style for the white frame
self.style.configure('White.TFrame', background='white')
@@ -1029,6 +1043,7 @@ class TextToMic(tk.Tk):
record_shortcut = "+".join(filter(None, settings["hotkeys"]["record_start_stop"]))
play_shortcut = "+".join(filter(None, settings["hotkeys"]["play_last_audio"]))
stop_shortcut = "+".join(filter(None, settings["hotkeys"]["stop_recording"]))
cancel_shortcut = "+".join(filter(None, settings["hotkeys"]["cancel_operation"]))
# Update CTkButton for recording state, keeping shortcuts visible
self.record_button.configure(text=f"Stop and Insert", fg_color="#d32f2f")

View File

@@ -103,7 +103,7 @@ class TonePresetsManager:
# Listbox for tones with scrollbar
self.tone_list = tk.Listbox(select_frame, height=8, selectmode=tk.BROWSE,
bg="#f0f0f0", fg="#333333",
bg="#ffffff", fg="#333333",
selectbackground="#0078d7", selectforeground="#ffffff",
font=("Arial", 10))
tone_scrollbar = ttk.Scrollbar(select_frame, orient=tk.VERTICAL, command=self.tone_list.yview)
@@ -168,7 +168,7 @@ class TonePresetsManager:
# Create the text widget with word wrap and vertical scrollbar
self.content_text = tk.Text(text_frame, wrap=tk.WORD,
yscrollcommand=v_scrollbar.set,
bg="#f0f0f0", fg="#333333",
bg="#ffffff", fg="#333333",
font=("Arial", 10),
relief="solid", borderwidth=1)
self.content_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)

View File

@@ -68,22 +68,28 @@ class VersionChecker:
f"Could not check for updates. Server returned status code: {response.status_code}"
))
except requests.RequestException as e:
error_message = f"Could not connect to update server: {str(e)}"
print(f"Version check error: {error_message}")
if show_result:
self.app.after(0, lambda: messagebox.showwarning(
self.app.after(0, lambda msg=error_message: messagebox.showwarning(
"Version Check Failed",
f"Could not connect to update server: {str(e)}"
msg
))
except json.JSONDecodeError:
except json.JSONDecodeError as e:
error_message = "Invalid update information received."
print(f"Version check error: {error_message}")
if show_result:
self.app.after(0, lambda: messagebox.showwarning(
self.app.after(0, lambda msg=error_message: messagebox.showwarning(
"Version Check Failed",
"Invalid update information received."
msg
))
except Exception as e:
error_message = f"Could not check for updates: {str(e)}"
print(f"Version check error: {error_message}")
if show_result:
self.app.after(0, lambda: messagebox.showwarning(
self.app.after(0, lambda msg=error_message: messagebox.showwarning(
"Version Check Failed",
f"Could not check for updates: {str(e)}"
msg
))
def show_update_notification(self, latest_version, download_url, message):