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)
