From 7633788da6189ce7e77fab6bf09b07810915973e Mon Sep 17 00:00:00 2001 From: Andrew Ward Date: Sat, 22 Mar 2025 11:27:22 +0000 Subject: [PATCH] version checking nearly working --- assets/leatest-version.example.json | 5 + .../presets_manager.cpython-312.pyc | Bin 34097 -> 37857 bytes utils/__pycache__/text_to_mic.cpython-312.pyc | Bin 66338 -> 68127 bytes utils/presets_manager.py | 64 +++++++- utils/settings_manager.py | 3 +- utils/text_to_mic.py | 74 ++++++--- utils/version_checker.py | 151 ++++++++++++++++++ 7 files changed, 276 insertions(+), 21 deletions(-) create mode 100644 assets/leatest-version.example.json create mode 100644 utils/version_checker.py diff --git a/assets/leatest-version.example.json b/assets/leatest-version.example.json new file mode 100644 index 0000000..6f3bba1 --- /dev/null +++ b/assets/leatest-version.example.json @@ -0,0 +1,5 @@ +{ + "latestVersion": "1.4.0", + "downloadUrl": "https://www.scorchsoft.com/blog/text-to-mic-for-meetings/", + "notificationMessage": "A new version of Text to Mic is available. Please update to access the latest features." +} \ No newline at end of file diff --git a/utils/__pycache__/presets_manager.cpython-312.pyc b/utils/__pycache__/presets_manager.cpython-312.pyc index ef4d15fba45683caeb23155916aa6e72dd486d75..e4f719777558e451b52138c6907b5daebdf0ec9d 100644 GIT binary patch delta 5790 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 delta 11256 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 diff --git a/utils/presets_manager.py b/utils/presets_manager.py index c206520..6101925 100644 --- a/utils/presets_manager.py +++ b/utils/presets_manager.py @@ -61,7 +61,7 @@ class PresetsManager: compound=tk.LEFT, text=" Presets", command=self.toggle_presets, - style="Flat.TButton" + style="PresetsButton.TButton" ) # Use grid instead of pack for the button to avoid mixing layout managers self.presets_button.grid(column=0, row=0, sticky=tk.W, padx=0, pady=2) @@ -84,6 +84,14 @@ class PresetsManager: anchor="center", background=bg_color) + # Create specific style for the presets button with smaller font + self.parent.style.configure("PresetsButton.TButton", + borderwidth=0, + highlightthickness=0, + font=("Arial", 10), # Smaller font size + anchor="center", + background=bg_color) + # Create compact styles for arrow buttons self.parent.style.configure("Arrow.TButton", borderwidth=0, @@ -578,6 +586,60 @@ class PresetsManager: else: messagebox.showinfo("Error", "Please enter text and select a category before saving.") + def show_save_preset_dialog(self): + """Show a dialog to save the current text as a preset.""" + text = self.parent.text_input.get("1.0", tk.END).strip() + if not text: + messagebox.showinfo("Error", "Please enter some text before saving as a preset.") + return + + # Create dialog window + dialog = tk.Toplevel(self.parent) + dialog.title("Save As Preset") + dialog.geometry("300x200") + dialog.transient(self.parent) + dialog.grab_set() + dialog.focus_set() + + # Add widgets to the dialog + ttk.Label(dialog, text="Enter preset category:").pack(pady=(15, 5)) + + # Add existing categories dropdown + categories = ["Select Category"] + list(set([cat["category"] for cat in self.presets])) + category_var = tk.StringVar(value="Select Category") + + category_combo = ttk.Combobox(dialog, textvariable=category_var, values=categories) + category_combo.pack(pady=5, padx=20, fill=tk.X) + + ttk.Label(dialog, text="Or enter a new category:").pack(pady=(10, 5)) + + new_category_entry = ttk.Entry(dialog) + new_category_entry.pack(pady=5, padx=20, fill=tk.X) + + def save_preset(): + new_category = new_category_entry.get().strip() + selected_category = category_var.get() + + # Use new category if provided, otherwise use the dropdown selection + category = new_category if new_category else selected_category + + if category and category != "Select Category": + self.add_preset(category, text, is_favourite=False) + messagebox.showinfo("Save Successful", f"The text has been successfully saved to the category: '{category}'.") + dialog.destroy() + else: + messagebox.showinfo("Error", "Please select an existing category or enter a new one.") + + # Add save button + save_button = ttk.Button(dialog, text="Save", command=save_preset) + save_button.pack(pady=15) + + # Center the dialog on the parent window + dialog.update_idletasks() + x = self.parent.winfo_x() + (self.parent.winfo_width() // 2) - (dialog.winfo_width() // 2) + y = self.parent.winfo_y() + (self.parent.winfo_height() // 2) - (dialog.winfo_height() // 2) + dialog.geometry(f"+{x}+{y}") + def load_presets(self): """ Load presets from the JSON file, copying from example if necessary. diff --git a/utils/settings_manager.py b/utils/settings_manager.py index 4aaca94..d6718ec 100644 --- a/utils/settings_manager.py +++ b/utils/settings_manager.py @@ -25,8 +25,9 @@ class SettingsManager: "model": "gpt-4o-mini", "prompt": "", "auto_apply_ai_to_recording": False, - "current_tone": "None", "hide_banner": False, + "auto_check_version": True, + "current_tone": "None", "input_device": "Default", "primary_device": "Select Device", "secondary_device": "None", diff --git a/utils/text_to_mic.py b/utils/text_to_mic.py index b552762..70e3ebe 100644 --- a/utils/text_to_mic.py +++ b/utils/text_to_mic.py @@ -8,6 +8,7 @@ import webbrowser import json import sys import time +import requests from pystray import Icon as icon, MenuItem as item, Menu as menu from PIL import Image, ImageDraw, ImageTk @@ -27,6 +28,7 @@ from utils.presets_manager import PresetsManager from utils.ai_editor_manager import AIEditorManager from utils.settings_manager import SettingsManager from utils.app_text import AppText +from utils.version_checker import VersionChecker # Modify the load environment variables to load from config/.env def load_env_file(): @@ -125,14 +127,14 @@ class TextToMic(tk.Tk): self.tone_presets = TonePresetsManager.load_tone_presets(self) self.current_tone_name = self.load_current_tone_from_settings() - # Create the category variable for the dropdown - self.category_var = tk.StringVar(value="Select Category") - - # Add toggle for banner visibility before presets manager initialization - self.banner_var = tk.BooleanVar() + # Initialize settings before creating menu settings = self.load_settings() + self.banner_var = tk.BooleanVar() self.banner_var.set(settings.get("hide_banner", False)) - + + # Initialize auto_check_version before creating menu + self.auto_check_version = tk.BooleanVar(value=settings.get("auto_check_version", True)) + # Create the presets manager before initializing the GUI self.presets_manager = PresetsManager(self) @@ -142,6 +144,9 @@ class TextToMic(tk.Tk): # Store reference to presets state self.presets_collapsed = self.presets_manager.presets_collapsed + # Initialize the main frame as a class variable for version notification to work + self.main_frame = None + # Create menu and initialize GUI after presets manager is created self.create_menu() self.initialize_gui() @@ -149,9 +154,18 @@ class TextToMic(tk.Tk): # Initialize our HotkeyManager self.hotkey_manager = HotkeyManager(self) + # Initialize version checker + self.version_checker = VersionChecker(self, self.version) + # If banner should be hidden based on settings, hide it now if self.banner_var.get(): self.toggle_banner() + + # Schedule version check after app is fully loaded + # Only check automatically if the setting is enabled + if self.auto_check_version.get(): + # Delay the check to ensure UI is fully loaded + self.after(2000, self.version_checker.check_version, False) def ensure_config_directory(self): """Ensure the config directory exists.""" @@ -198,9 +212,12 @@ class TextToMic(tk.Tk): self.menubar.add_cascade(label="Help", menu=help_menu) 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="Version", command=self.show_version) + help_menu.add_command(label="Check Version", command=self.check_version) help_menu.add_command(label="Hotkey Instructions", command=self.show_hotkey_instructions) + # Add toggle for automatic version checking + help_menu.add_checkbutton(label="Auto Check for Updates", variable=self.auto_check_version, command=self.toggle_auto_version_check) + # 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) @@ -272,6 +289,9 @@ class TextToMic(tk.Tk): main_frame.grid(column=0, row=0, sticky=(tk.W, tk.E, tk.N, tk.S)) self.columnconfigure(0, weight=1) self.rowconfigure(0, weight=1) + + # Store reference to main_frame for version notification + self.main_frame = main_frame # Use the background color from our style for the text widget bg_color = self.style.lookup('TFrame', 'background') @@ -373,18 +393,10 @@ class TextToMic(tk.Tk): save_frame = ttk.Frame(text_read_frame) save_frame.grid(column=1, row=0, sticky=tk.E) - # Preset Category dropdown - categories = [cat["category"] for cat in self.presets_manager.presets] - category_menu = ttk.OptionMenu(save_frame, self.category_var, *categories) - category_menu.grid(column=0, row=0, sticky=tk.E, padx=(0, 5)) - category_menu.config(style='Compact.TMenubutton') - - # Create a compact style for the Save button to match dropdown height + # Create a compact style for the button self.style.configure('Compact.TButton', padding=(2, 1)) - - # Save button with matching height - save_button = ttk.Button(save_frame, text="Save", width=8, style='Compact.TButton', command=self.save_current_text_as_preset) - save_button.grid(column=1, row=0, sticky=tk.E) + save_as_preset_button = ttk.Button(save_frame, text="Save As Preset", width=15, style='Compact.TButton', command=self.show_save_preset_dialog) + save_as_preset_button.grid(column=0, row=0, sticky=tk.E) # Text input area with proper spacing self.text_input = tk.Text(main_frame, height=5, width=68) @@ -485,7 +497,7 @@ class TextToMic(tk.Tk): def save_current_text_as_preset(self): """Forward the save request to the presets manager.""" - self.presets_manager.save_current_text_as_preset() + self.show_save_preset_dialog() def show_instructions(self): instruction_window = tk.Toplevel(self) @@ -1227,6 +1239,11 @@ class TextToMic(tk.Tk): # Use grid (not pack) to ensure proper positioning self.presets_manager.presets_button.grid_configure(column=0, row=0, sticky=tk.W, padx=0, pady=2) + # If we have a version notification visible, ensure it remains at the top + if hasattr(self, 'version_checker') and self.version_checker.notification_visible: + self.version_checker.notification_frame.grid(row=0, column=0, sticky="ew") + self.main_frame.grid(row=1, column=0, sticky="nsew") + def toggle_presets(self): """Toggle the visibility of the presets panel.""" if hasattr(self, 'presets_manager'): @@ -1265,6 +1282,11 @@ class TextToMic(tk.Tk): if not self.presets_collapsed: self.presets_manager.refresh_presets_display() + # If we have a version notification visible, ensure it remains at the top + if hasattr(self, 'version_checker') and self.version_checker.notification_visible: + self.version_checker.notification_frame.grid(row=0, column=0, sticky="ew") + self.main_frame.grid(row=1, column=0, sticky="nsew") + def update_buttons_for_playback(self, is_playing): """Update button text based on playback state.""" try: @@ -1326,4 +1348,18 @@ class TextToMic(tk.Tk): settings["secondary_device"] = device_name self.save_settings_to_JSON(settings) + def show_save_preset_dialog(self): + """Show the save preset dialog.""" + self.presets_manager.show_save_preset_dialog() + + def check_version(self): + """Run the version checker and show the result""" + self.version_checker.check_version(True) # True means show result even if no update available + + def toggle_auto_version_check(self): + """Toggle automatic version checking and save the setting""" + settings = self.load_settings() + settings["auto_check_version"] = self.auto_check_version.get() + self.save_settings_to_JSON(settings) + diff --git a/utils/version_checker.py b/utils/version_checker.py new file mode 100644 index 0000000..fed1a3d --- /dev/null +++ b/utils/version_checker.py @@ -0,0 +1,151 @@ +import threading +import requests +import json +import webbrowser +import tkinter as tk +from tkinter import ttk, messagebox +from packaging import version +import time + +class VersionChecker: + def __init__(self, app, version): + self.app = app + self.current_version = version + self.version_url = "https://www.scorchsoft.com/public/blog/text-to-mic/leatest-version.json" + self.notification_visible = False + self.notification_frame = None + + def check_version(self, show_result=True): + """ + Check if a new version is available. + If show_result is True, show a message even if no new version is found. + """ + thread = threading.Thread(target=self._check_version_thread, args=(show_result,)) + thread.daemon = True # Make thread terminate when main program exits + thread.start() + + def _check_version_thread(self, show_result): + """Run the version check in a background thread to avoid blocking the UI""" + try: + # Add a small timeout to prevent hanging + response = requests.get(self.version_url, timeout=5) + if response.status_code == 200: + data = response.json() + latest_version = data.get("latestVersion") + download_url = data.get("downloadUrl") + message = data.get("notificationMessage") + + # Ensure these values are present + if not latest_version or not download_url: + if show_result: + self.app.after(0, lambda: messagebox.showwarning( + "Version Check Failed", + "The update information is incomplete. Please try again later." + )) + return + + # Use packaging.version for proper version comparison + try: + if version.parse(latest_version) > version.parse(self.current_version): + # New version available - show notification in UI thread + self.app.after(0, lambda: self.show_update_notification(latest_version, download_url, message)) + elif show_result: + # No new version, but user requested check + self.app.after(0, lambda: messagebox.showinfo( + "Version Check", + f"You have the latest version ({self.current_version})." + )) + except (version.InvalidVersion, TypeError) as e: + if show_result: + self.app.after(0, lambda: messagebox.showwarning( + "Version Check Failed", + f"Could not compare versions: {str(e)}" + )) + else: + if show_result: + self.app.after(0, lambda: messagebox.showwarning( + "Version Check Failed", + f"Could not check for updates. Server returned status code: {response.status_code}" + )) + except requests.RequestException as e: + if show_result: + self.app.after(0, lambda: messagebox.showwarning( + "Version Check Failed", + f"Could not connect to update server: {str(e)}" + )) + except json.JSONDecodeError: + if show_result: + self.app.after(0, lambda: messagebox.showwarning( + "Version Check Failed", + "Invalid update information received." + )) + except Exception as e: + if show_result: + self.app.after(0, lambda: messagebox.showwarning( + "Version Check Failed", + f"Could not check for updates: {str(e)}" + )) + + def show_update_notification(self, latest_version, download_url, message): + """Display an update notification banner in the app""" + if self.notification_visible: + return # Already showing notification + + # Create notification frame + self.notification_frame = ttk.Frame(self.app, style='Notification.TFrame') + + # Configure notification style (light yellow background) + self.app.style.configure('Notification.TFrame', background='#fff3cd') + self.app.style.configure('Notification.TLabel', background='#fff3cd', foreground='#856404') + self.app.style.configure('Notification.TButton', background='#fff3cd') + + # Create notification content + notification_text = message or f"A new version ({latest_version}) is available. You're currently using version {self.current_version}." + + label = ttk.Label( + self.notification_frame, + text=notification_text, + style='Notification.TLabel', + wraplength=400 + ) + label.grid(row=0, column=0, padx=(10, 5), pady=10, sticky="w") + + # Create buttons + download_button = ttk.Button( + self.notification_frame, + text="Download", + command=lambda: self.open_download_page(download_url) + ) + download_button.grid(row=0, column=1, padx=5, pady=10) + + close_button = ttk.Button( + self.notification_frame, + text="×", + width=2, + command=self.dismiss_notification + ) + close_button.grid(row=0, column=2, padx=(0, 5), pady=10) + + # Insert at the top of the application, below menu + self.notification_frame.grid(row=0, column=0, sticky="ew", padx=0, pady=0) + + # Move other content down + self.app.main_frame.grid(row=1, column=0, sticky="nsew") + + self.notification_visible = True + + def dismiss_notification(self): + """Remove the notification banner""" + if self.notification_frame: + self.notification_frame.grid_forget() + self.notification_frame = None + + # Move main frame back to top position + self.app.main_frame.grid(row=0, column=0, sticky="nsew") + + self.notification_visible = False + + def open_download_page(self, url): + """Open the download URL in a web browser""" + webbrowser.open(url) + self.dismiss_notification() \ No newline at end of file