From 3429e51c08fe5c7bb9544a24a00b1a38723b9109 Mon Sep 17 00:00:00 2001 From: Andrew Ward Date: Mon, 28 Oct 2024 16:02:28 +0000 Subject: [PATCH] add presets area, working but some refinement needed --- utils/__pycache__/text_to_mic.cpython-312.pyc | Bin 54036 -> 70478 bytes utils/text_to_mic.py | 271 +++++++++++++++++- 2 files changed, 268 insertions(+), 3 deletions(-) diff --git a/utils/__pycache__/text_to_mic.cpython-312.pyc b/utils/__pycache__/text_to_mic.cpython-312.pyc index 4e95b71723914d19d5801f2b776022864043d4bc..aa7ca5f277e935f434007f892bf416169b89f555 100644 GIT binary patch delta 20676 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 LHGkqT%`pByb{r4I delta 8154 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 T5yg@>OI_wp^LGX+3&#Hd?T#rA diff --git a/utils/text_to_mic.py b/utils/text_to_mic.py index f8cf4e1..3f09f71 100644 --- a/utils/text_to_mic.py +++ b/utils/text_to_mic.py @@ -11,7 +11,7 @@ import sys from pystray import Icon as icon, MenuItem as item, Menu as menu from PIL import Image, ImageDraw -from tkinter import ttk, messagebox, simpledialog, Menu +from tkinter import ttk, messagebox, simpledialog, Menu, Frame, Canvas, Scrollbar from openai import OpenAI from dotenv import load_dotenv from pathlib import Path @@ -40,6 +40,10 @@ class TextToMic(tk.Tk): self.style.configure('Recording.TButton', background='red', foreground='white') self.style.configure("Green.TButton", background="green", foreground="white") + self.presets = self.load_presets() + self.current_category = "All" + self.presets_collapsed = True + # Ensure that the config directory exists self.ensure_config_directory() @@ -65,6 +69,9 @@ class TextToMic(tk.Tk): self.create_menu() self.initialize_gui() self.setup_hotkeys() + + + def ensure_config_directory(self): """Ensure the config directory exists.""" @@ -116,6 +123,7 @@ class TextToMic(tk.Tk): help_menu.add_command(label="Hotkey Instructions", command=self.show_hotkey_instructions) + def hotkey_settings(self): settings = self.load_settings() hotkey_window = tk.Toplevel(self) @@ -287,10 +295,19 @@ class TextToMic(tk.Tk): secondary_device_menu = ttk.OptionMenu(main_frame, self.device_index_2, "None", *self.available_devices.keys()) secondary_device_menu.grid(column=1, row=3, sticky=tk.W) + # Preset selection and save button + self.category_var = tk.StringVar(value="Select Category") + categories = [cat["category"] for cat in self.presets] + ttk.OptionMenu(main_frame, self.category_var, *categories).grid(column=1, row=4, sticky=tk.W) + + save_button = ttk.Button(main_frame, text="Save", command=self.save_current_text_as_preset) + save_button.grid(column=2, row=4, sticky=tk.E) + # Specify the text to read ttk.Label(main_frame, text="Text to Read:").grid(column=0, row=4, sticky=tk.W, pady=(10, 0)) self.text_input = tk.Text(main_frame, height=10, width=50) self.text_input.grid(column=0, row=5, columnspan=2, pady=(0, 20)) # Padding added before submit button + # Button configuration @@ -304,16 +321,69 @@ class TextToMic(tk.Tk): - + self.create_presets_section() #Credits info_label = tk.Label(main_frame, text="Created by Scorchsoft.com App Development", fg="blue", cursor="hand2") - info_label.grid(column=0, row=7, columnspan=2, pady=(0, 0)) + info_label.grid(column=0, row=9, columnspan=2, pady=(0, 0)) info_label.bind("", lambda e: self.open_scorchsoft()) + def create_presets_section(self): + # Accordion frame to show/hide presets section + self.presets_frame = ttk.Frame(self) + self.presets_button = ttk.Button(self, text="▶ Presets", command=self.toggle_presets) + self.presets_button.grid(column=0, row=7, columnspan=2, sticky=tk.W) + self.presets_frame.grid(column=0, row=8, columnspan=2, sticky=(tk.W, tk.E)) + + # Tabs for categories with scrolling arrows + self.tab_frame = ttk.Frame(self.presets_frame) + self.tab_frame.pack(fill=tk.X) + + # Thinner left arrow for tabs + self.left_arrow = ttk.Button(self.tab_frame, text="◀", command=self.scroll_left, width=2, style="Flat.TButton") + self.left_arrow.pack(side=tk.LEFT, padx=1) # Reduced padding + + # Canvas for scrolling tabs horizontally, removing the horizontal scrollbar + self.tabs_canvas = Canvas(self.tab_frame, height=30) + self.tabs_canvas.pack(side=tk.LEFT, fill=tk.X, expand=True) + self.tabs_frame_inner = ttk.Frame(self.tabs_canvas) + self.tabs_canvas.create_window((0, 0), window=self.tabs_frame_inner, anchor="nw") + + # Thinner right arrow for tabs + self.right_arrow = ttk.Button(self.tab_frame, text="▶", command=self.scroll_right, width=2, style="Flat.TButton") + self.right_arrow.pack(side=tk.RIGHT, padx=1) # Reduced padding + + # Presets display area with a fixed height and vertical scrollbar + self.presets_canvas = Canvas(self.presets_frame, height=200, width=self.presets_frame.winfo_width()) + self.presets_scrollbar = Scrollbar(self.presets_frame, orient="vertical", command=self.presets_canvas.yview) + self.presets_canvas.configure(yscrollcommand=self.presets_scrollbar.set) + + # Frame inside the canvas to hold presets + self.presets_scrollable_frame = ttk.Frame(self.presets_canvas) + self.presets_canvas.create_window((0, 0), window=self.presets_scrollable_frame, anchor="nw") + + # Pack the canvas and scrollbar + self.presets_canvas.pack(side=tk.LEFT, fill=tk.X, expand=True) + self.presets_scrollbar.pack(side=tk.RIGHT, fill=tk.Y) + + # Configure the scroll region to update when the frame changes + self.presets_scrollable_frame.bind("", lambda e: self.presets_canvas.configure(scrollregion=self.presets_canvas.bbox("all"))) + + # Populate tabs and presets + self.populate_tabs() + self.refresh_presets_display() + + + + def scroll_left(self): + self.tabs_canvas.xview_scroll(-5, "units") + + def scroll_right(self): + self.tabs_canvas.xview_scroll(5, "units") + @@ -996,3 +1066,198 @@ Please also make sure you read the Terms of use and licence statement before usi return return_text + + + def populate_tabs(self): + # Clear current tabs + for widget in self.tabs_frame_inner.winfo_children(): + widget.destroy() + + # Add "All" and "Favourites" tabs along with dynamic categories + for category in ["All", "Favourites"] + [cat["category"] for cat in self.presets if cat["category"] not in ["All", "Favourites"]]: + btn = ttk.Button(self.tabs_frame_inner, text=category, command=lambda c=category: self.switch_category(c)) + btn.pack(side=tk.LEFT, padx=2) + + # Style selected category + if category == self.current_category: + btn.state(['pressed']) # Visual style for selected tab + else: + btn.state(['!pressed']) + + def switch_category(self, category): + """Switch displayed category.""" + self.current_category = category + self.populate_tabs() # Refresh tabs to show selection + self.refresh_presets_display() + + def refresh_presets_display(self): + """Refresh displayed presets based on selected category.""" + + # Clear existing items in the scrollable frame + for widget in self.presets_scrollable_frame.winfo_children(): + widget.destroy() + + # Debounce - cancel any previous refresh call if pending + if hasattr(self, 'refresh_handle'): + self.after_cancel(self.refresh_handle) + self.refresh_handle = self.after(100, self._populate_presets) + + def _populate_presets(self): + """Populate presets into grid layout with responsive columns.""" + # Filter presets based on current category + display_phrases = [] + if self.current_category == "All": + for cat in self.presets: + display_phrases.extend(cat["phrases"]) + elif self.current_category == "Favourites": + for cat in self.presets: + display_phrases.extend([p for p in cat["phrases"] if p["isFavourite"]]) + else: + display_phrases = next((cat["phrases"] for cat in self.presets if cat["category"] == self.current_category), []) + + # Update canvas width to calculate dynamic column width + self.presets_canvas.update_idletasks() + preset_width = max(self.presets_canvas.winfo_width() // 3, 100) # Minimum width of 100 + preset_height = 100 + + # Configure columns to fill available space + for col in range(3): + self.presets_scrollable_frame.columnconfigure(col, weight=1) + + # Populate filtered presets in grid layout + for i, phrase in enumerate(display_phrases): + frame = ttk.Frame(self.presets_scrollable_frame, width=preset_width, height=preset_height, relief="solid", borderwidth=1) + frame.grid(row=i // 3, column=i % 3, padx=2, pady=2, sticky="nsew") + frame.grid_propagate(False) + + # Text label with truncation for long text + wrapped_text = self.wrap_text(phrase["text"], max_lines=3, max_chars_per_line=20) + label = ttk.Label(frame, text=wrapped_text, anchor="center", justify="center", width=20) + label.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) + label.bind("", lambda e, t=phrase["text"]: self.insert_text(t)) + label.bind("", lambda e, t=phrase["text"]: self.play_preset(t)) + + # Bottom frame for icons + bottom_frame = ttk.Frame(frame) + bottom_frame.pack(side=tk.BOTTOM, fill=tk.X, pady=2) + + # Favourite button + fav_icon = "❤️" if phrase["isFavourite"] else "♡" + fav_btn = ttk.Button(bottom_frame, text=fav_icon, command=lambda p=phrase: self.toggle_favourite(p), width=3, style="Flat.TButton") + fav_btn.pack(side=tk.RIGHT, padx=2) + + # Delete button + del_btn = ttk.Button(bottom_frame, text="🗑️", command=lambda t=phrase["text"]: self.delete_preset(self.current_category, t), width=3, style="Flat.TButton") + del_btn.pack(side=tk.RIGHT, padx=2) + + # Update scroll region after populating all items + self.presets_canvas.configure(scrollregion=self.presets_canvas.bbox("all")) + + + + + + + + + + + + + def wrap_text(self, text, max_lines=3, max_chars_per_line=20): + """Wrap text to fit within a limited number of lines and characters.""" + words = text.split() + wrapped_text = "" + line = "" + line_count = 0 + + for word in words: + if len(line + word) <= max_chars_per_line: + line += word + " " + else: + wrapped_text += line.strip() + "\n" + line = word + " " + line_count += 1 + if line_count >= max_lines - 1: # Leave space for ellipsis + break + + # Add final line and handle overflow with ellipsis + wrapped_text += line.strip() + if line_count >= max_lines - 1 and len(wrapped_text.splitlines()) >= max_lines: + wrapped_text = "\n".join(wrapped_text.splitlines()[:max_lines - 1]) + "\n..." + + return wrapped_text + + + + def insert_text(self, text): + """Insert preset text into the text area.""" + self.text_input.delete("1.0", tk.END) + self.text_input.insert("1.0", text) + + def play_preset(self, text): + """Insert text and play audio immediately.""" + self.insert_text(text) + self.submit_text() + + def toggle_favourite(self, phrase): + """Toggle the favourite status of a preset.""" + phrase["isFavourite"] = not phrase["isFavourite"] + self.save_presets() + self.refresh_presets_display() + + def toggle_presets(self): + if self.presets_collapsed: + self.presets_frame.grid() + self.presets_button.config(text="▼ Presets") + else: + self.presets_frame.grid_remove() + self.presets_button.config(text="▶ Presets") + self.presets_collapsed = not self.presets_collapsed + + + def save_current_text_as_preset(self): + """Save current text to the selected category as a preset.""" + text = self.text_input.get("1.0", tk.END).strip() + category = self.category_var.get() + if text and category != "Select Category": + self.add_preset(category, text, is_favourite=False) + + def load_presets(self): + """Load presets from the JSON file.""" + try: + with open("config/presets.json", "r") as f: + data = json.load(f) + return data.get("presets", []) + except (FileNotFoundError, json.JSONDecodeError): + # Create a default structure if the file doesn't exist or is corrupted + return [] + + def save_presets(self): + """Save presets to the JSON file.""" + data = {"presets": self.presets} + with open("config/presets.json", "w") as f: + json.dump(data, f, indent=2) + + def add_preset(self, category, text, is_favourite=False): + """Add a new preset and save it.""" + for cat in self.presets: + if cat["category"] == category: + cat["phrases"].append({"text": text, "isFavourite": is_favourite}) + break + else: + # Add a new category if not found + self.presets.append({"category": category, "phrases": [{"text": text, "isFavourite": is_favourite}]}) + self.save_presets() + self.refresh_presets_display() + + def delete_preset(self, category, text): + """Delete a preset by category and text.""" + for cat in self.presets: + if cat["category"] == category: + cat["phrases"] = [p for p in cat["phrases"] if p["text"] != text] + break + self.save_presets() + self.refresh_presets_display() + +