Hi guys,
I am creating a popup that, while user is typing, shows some options for the user to select, if tab, enter or arrow keys are pressed, or if none is pressed, it keeps typing the text in the current window and the popup updates itself. However, when the popup show and is visible (but has no focus - if the user chose to keeps typing) the keys work in the popup (only in the fist time it shows), but are propagating to the text, for instance, if I press tab, it will write the selection, but only after a tab is inserted into te text. I tried many things, but cannot solve this issue? Any help??
I am creating a popup that, while user is typing, shows some options for the user to select, if tab, enter or arrow keys are pressed, or if none is pressed, it keeps typing the text in the current window and the popup updates itself. However, when the popup show and is visible (but has no focus - if the user chose to keeps typing) the keys work in the popup (only in the fist time it shows), but are propagating to the text, for instance, if I press tab, it will write the selection, but only after a tab is inserted into te text. I tried many things, but cannot solve this issue? Any help??
import os import time import tkinter as tk from tkinter import Listbox, font import keyboard from popup_utils import get_caret_position from queue_manager import gui_queue class PopupAutoComplete: def __init__(self): self.popup_auto = None self.listbox = None self.prevent_typing = False def show_autocomplete_popup(self, suggestions, position, current_word, last_word): global word_at_caret, last_word_at_caret word_at_caret = current_word # Atualiza a variável global last_word_at_caret = last_word # Atualiza a variável global if not self.popup_auto: self.popup_auto = tk.Toplevel() self.popup_auto.wm_overrideredirect(True) self.popup_auto.attributes("-topmost", True) self.popup_auto.attributes("-alpha", 0.87) self.popup_auto.configure(bg="#303030") frame = tk.Frame(self.popup_auto, bg="#303030") frame.pack(fill=tk.BOTH, expand=True) self.listbox = Listbox( frame, font=("Work Sans", 11), bg="#303030", fg="white", selectbackground="orange", selectforeground="black", activestyle="none", ) self.listbox.pack(fill=tk.BOTH, expand=True) self.popup_auto.bind_all("<Return>", self.suppress_key) self.popup_auto.bind_all("<Tab>", self.suppress_key) self.popup_auto.bind_all("<Up>", self.suppress_key) self.popup_auto.bind_all("<Down>", self.suppress_key) self.popup_auto.bind_all("<Left>", self.suppress_key) self.popup_auto.bind_all("<Right>", self.suppress_key) self.listbox.bind("<<ListboxSelect>>", self.on_suggestion_select) self.listbox.bind("<Return>", self.on_suggestion_select) self.listbox.bind("<Motion>", self.on_mouse_motion) self.popup_auto.bind("<FocusOut>", self.on_focus_out) self.popup_auto.focus_set() else: self.popup_auto.deiconify() suggestion_height = 20 total_height = suggestion_height * len(suggestions) listbox_font = font.Font(font=self.listbox.cget("font")) max_width = ( max(listbox_font.measure(suggestion) for suggestion in suggestions) + 70 ) screen_width = self.popup_auto.winfo_screenwidth() screen_height = self.popup_auto.winfo_screenheight() x_position = position[0] + 10 y_position = position[1] + 35 if x_position + max_width > screen_width: x_position = screen_width - max_width - 20 elif x_position < 0: x_position = 20 if y_position + total_height > screen_height: y_position = position[1] - total_height - 20 elif y_position < 0: y_position = 20 self.popup_auto.geometry( f"{max_width}x{total_height}+{x_position}+{y_position}" ) self.listbox.delete(0, tk.END) for i, suggestion in enumerate(suggestions, 1): self.listbox.insert( tk.END, f"{i}. {suggestion.encode('utf-8').decode('utf-8')}" ) if suggestions: self.listbox.selection_set(0) self.listbox.activate(0) def suppress_key(self, event): return "break" def hide_popup_auto(self): if self.popup_auto: self.popup_auto.withdraw() def on_focus_out(self, event): self.hide_popup_auto() def get_selected_suggestion(self): if self.listbox: try: return self.listbox.get(self.listbox.curselection()) except tk.TclError: return None return None def on_mouse_motion(self, event): widget = event.widget index = widget.nearest(event.y) widget.selection_clear(0, tk.END) widget.selection_set(index) widget.activate(index) widget.configure(cursor="hand2") ################################################################ def on_suggestion_select(self, event=None): global word_at_caret, last_word_at_caret # Se o evento for None, use a listbox diretamente if event is None: widget = self.listbox # Assume que a listbox está armazenada como um atributo selection = widget.curselection() else: widget = event.widget selection = widget.curselection() if isinstance(selection, int): index = selection elif selection: index = selection[0] else: return value = widget.get(index)[3:] print(f"Selected suggestion: {value}") self.selected_suggestion = value self.popup_auto.withdraw() # Compara word_at_caret com value e adiciona apenas os caracteres que faltam if word_at_caret: common_prefix = os.path.commonprefix([word_at_caret.lower(), value.lower()]) chars_to_add = value[len(common_prefix):] print(f"Palavra atual: {word_at_caret}") print(f"Sugestão selecionada: {value}") print(f"Prefixo comum: {common_prefix}") print(f"Caracteres a adicionar: {chars_to_add}") # Adiciona um pequeno atraso antes de escrever os novos caracteres time.sleep(0.05) # Escreve apenas os caracteres que faltam if last_word_at_caret.endswith("."): keyboard.write(chars_to_add.capitalize()) else: keyboard.write(chars_to_add) else: # Se word_at_caret estiver vazio, escreve a sugestão completa if last_word_at_caret.endswith("."): keyboard.write(value.capitalize()) else: keyboard.write(value) # Adiciona um pequeno atraso após escrever a sugestão time.sleep(0.05) self.hide_popup_auto() # Previne a propagação do evento, se houver if event: return "break" ################################################################ def keys_on_popup_AUTO(self, key): global word_at_caret, last_word_at_caret if key.isdigit(): # Imprime os valores das variáveis globais para depuração print(f"word_at_caret (global): {word_at_caret}") print(f"last_word_at_caret (global): {last_word_at_caret}") index = int(key) - 1 if 0 <= index < self.listbox.size(): value = self.listbox.get(index)[3:] print(f"Selected suggestion by key press: {value}") self.selected_suggestion = value self.popup_auto.withdraw() # Verifica e apaga a palavra na posição do cursor if word_at_caret: print(f"Apagando a palavra no cursor: {word_at_caret}") for _ in range(len(word_at_caret) +1): keyboard.send("backspace") # Escreve o valor selecionado if last_word_at_caret.endswith("."): keyboard.write(value.capitalize()) else: keyboard.write(value) self.hide_popup_auto() self.prevent_typing = False return elif key in ["tab", "enter"]: self.suppress_key(None) # Previne a propagação do evento # Adiciona a letra "a" a word_at_caret if word_at_caret: # print(f"Adicionando 'a' ao cursor.") # keyboard.write("a") # Atualiza word_at_caret para refletir a adição do caractere word_at_caret += "a" self.on_suggestion_select(event=None) return "break" # Impede qualquer processamento adicional do evento elif key in ["down", "right"]: selection = self.listbox.curselection() if selection: next_index = selection[0] + 1 if next_index < self.listbox.size(): self.listbox.selection_clear(0, tk.END) self.listbox.selection_set(next_index) self.listbox.activate(next_index) print( f"Selected next suggestion: {self.listbox.get(next_index)[3:]}" ) elif key in ["up", "left"]: selection = self.listbox.curselection() if selection: prev_index = selection[0] - 1 if prev_index >= 0: self.listbox.selection_clear(0, tk.END) self.listbox.selection_set(prev_index) self.listbox.activate(prev_index)