From bd2b15503ddbf88cd550f257908f70767d3c1c3b Mon Sep 17 00:00:00 2001 From: godax84 Date: Thu, 29 May 2025 17:18:09 +0000 Subject: [PATCH] Carregar ficheiros para "/" --- README.md | 44 ++++ ecu_ascii_analyzer.py | 514 ++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 8 + 3 files changed, 566 insertions(+) create mode 100644 README.md create mode 100644 ecu_ascii_analyzer.py create mode 100644 requirements.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..0a9db7e --- /dev/null +++ b/README.md @@ -0,0 +1,44 @@ +# Analisador ASCII de Ficheiros ECU + +Esta é uma aplicação Python standalone para analisar ficheiros binários (com foco em ficheiros de ECU automóvel) e extrair padrões ASCII relevantes. + +## Funcionalidades + +- Interface gráfica simples para seleção de ficheiros. +- Procura por diversos padrões de texto, incluindo: + - Famílias de ECU (MEDC, EDC, MDG, MD1) + - Números de Software + - VIN (Número de Identificação do Veículo) + - Códigos de 3+ letras + - Sequências longas de números + - Possíveis Códigos PIN + - Números de Peça Bosch + - Endereços de Email + - URLs +- Apresentação dos resultados numa janela dedicada, com contagem de ocorrências. +- Barra de progresso durante a análise. + +## Como Executar + +1. Certifique-se de que tem Python 3 instalado. +2. Guarde o ficheiro `ecu_ascii_analyzer.py`. +3. Execute o script a partir da linha de comandos: + ```bash + python ecu_ascii_analyzer.py + ``` +4. Use o botão "Procurar..." para selecionar um ficheiro. +5. Clique em "Analisar Ficheiro" para iniciar a análise. + +## Padrões Procurados + +A aplicação procura pelos seguintes padrões (ignorando maiúsculas/minúsculas): + +- **Ecu Family**: `\b(MEDC|EDC|MDG|MD1)\b` +- **Possible Software Number**: `\b\d{9,11}\b` +- **VIN (Vehicle Identification Number)**: `\b[A-HJ-NPR-Z0-9]{17}\b` +- **Potential Codes (3+ letters)**: `\b[A-Z]{3,}\b` +- **Long Numbers (5+ digits)**: `\b\d{5,}\b` +- **Possible Pin Code (4 Alphanum)**: `\b[A-Z0-9]{4}\b` +- **Bosch Part Number**: `\b0\s?2[68]\d[\s\d]{6,7}\b` +- **Email Address**: `[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}` +- **URL**: `https?:\/\/(?:www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b(?:[-a-zA-Z0-9()@:%_\+.~#?&//=]*)` diff --git a/ecu_ascii_analyzer.py b/ecu_ascii_analyzer.py new file mode 100644 index 0000000..0f5460b --- /dev/null +++ b/ecu_ascii_analyzer.py @@ -0,0 +1,514 @@ +import tkinter as tk +from tkinter import filedialog, scrolledtext, ttk, messagebox +import re +import os +from collections import Counter + +# Importações dos novos módulos de padrões +from patterns.common_patterns import PATTERNS_COMMON, INITIAL_ANALYSIS_PATTERNS_KEYS +from patterns.landrover_jaguar_patterns import PATTERNS_LANDROVER_JAGUAR +from patterns.vag_patterns import PATTERNS_VAG +from patterns.bmw_patterns import PATTERNS_BMW # Importar PATTERNS_BMW +from Data.vag_ecu_group_handler import load_vag_ecu_list +from Data.jlr_ecu_group_handler import load_jlr_ecu_list +from Data.bmw_ecu_group_handler import load_bmw_ecu_list # Nova importação para BMW +# No futuro, adicionar importações para outros módulos de marca aqui: +# from patterns.bmw_patterns import PATTERNS_BMW +# etc. + +# --- Constantes Globais --- +BRAND_NAMES = ['VAG', 'LandRover/Jaguar', 'BMW', 'Mercedes', 'Renault/Dacia', 'Peugeot/Citroen', 'Opel', 'Fiat/Lancia/Alfa', 'Ford'] + +# Variáveis globais para armazenar referências a widgets e estado +global_root_window = None +global_file_path_var = None +global_results_text_widget = None +global_brand_buttons_frame = None +global_category_buttons_frame = None +global_ascii_text = None # Armazena o conteúdo ASCII do ficheiro lido +global_progress_bar = None +global_progress_label = None + +# Função para ler o ficheiro em modo binário e tentar decodificar para ASCII +def read_file_content(file_path): + try: + with open(file_path, 'rb') as f: + binary_content = f.read() + # Tenta decodificar como ASCII, ignorando erros ou substituindo caracteres problemáticos + ascii_content = binary_content.decode('ascii', errors='replace') + return ascii_content + except FileNotFoundError: + print(f"Erro: Ficheiro não encontrado em {file_path}") + return None + except Exception as e: + print(f"Erro ao ler ou decodificar o ficheiro {file_path}: {e}") + return None + +# Função principal de análise de padrões ASCII a partir de uma string de conteúdo +def analyze_ascii_patterns_from_file(file_content, current_patterns_to_search, progress_callback=None, target_pattern_name=None): + results = {} + search_dict = {} + if target_pattern_name: + if target_pattern_name in current_patterns_to_search: + search_dict = {target_pattern_name: current_patterns_to_search[target_pattern_name]} + else: + print(f"Aviso: Padrão alvo '{target_pattern_name}' não encontrado no dicionário de pesquisa fornecido.") + return {target_pattern_name: {}} + else: + search_dict = current_patterns_to_search + + for pattern_name, regex_pattern in search_dict.items(): + try: + flags = 0 + # A flag re.IGNORECASE para 'Ecu Family' foi removida para restaurar o comportamento anterior. + # Se a insensibilidade a maiúsculas/minúsculas for necessária para os KEYWORDS, + # isso deve ser tratado diretamente no regex em common_patterns.py. + + if pattern_name == 'Possible PIN': + pin_candidates_matches = re.finditer(r'\b([A-Z0-9]{4,8})\b', file_content) + uppercase_block_counts = Counter() + found_items_for_pin = {} + for match_obj in pin_candidates_matches: + block_match = match_obj.group(1) + if block_match.isalnum() and block_match == block_match.upper(): + uppercase_block_counts[block_match] += 1 + + for block, count in uppercase_block_counts.items(): + if 2 <= count <= 3: + found_items_for_pin[block] = count + results[pattern_name] = found_items_for_pin + else: + matches = re.finditer(regex_pattern, file_content, flags) + found_items = {} + for match_obj in matches: + item = match_obj.group(1) if match_obj.groups() else match_obj.group(0) + if item: + cleaned_item = ''.join(char for char in item if char.isprintable()).strip() + if cleaned_item: + found_items[cleaned_item] = found_items.get(cleaned_item, 0) + 1 + results[pattern_name] = found_items + except re.error as e: + print(f"Erro de regex para o padrão '{pattern_name}' com regex '{regex_pattern}': {e}") + results[pattern_name] = {"ERRO DE REGEX": 1} + except Exception as e: + print(f"Erro inesperado ao processar o padrão '{pattern_name}': {e}") + results[pattern_name] = {"ERRO INESPERADO": 1} + return results + +# Função para lidar com a seleção de ficheiro e realizar a análise inicial +def handle_file_selection_and_initial_analysis(file_path_var): + file_path = filedialog.askopenfilename( + title="Selecionar ficheiro ECU", + filetypes=(("Binary files", "*.bin"), ("All files", "*.*")) + ) + if not file_path: + return + + file_path_var.set(os.path.basename(file_path)) + global global_ascii_text + global_ascii_text = read_file_content(file_path) + + if global_ascii_text is None: + tk.messagebox.showerror("Erro de Leitura", f"Não foi possível ler ou decodificar o ficheiro: {file_path}", parent=global_root_window) + clear_ui_state() + return + + if global_results_text_widget: + global_results_text_widget.config(state=tk.NORMAL) + global_results_text_widget.delete(1.0, tk.END) + global_results_text_widget.config(state=tk.DISABLED) + + for widget in global_brand_buttons_frame.winfo_children(): + widget.destroy() + for widget in global_category_buttons_frame.winfo_children(): + widget.destroy() + + initial_patterns_dict = {key: PATTERNS_COMMON[key] for key in INITIAL_ANALYSIS_PATTERNS_KEYS if key in PATTERNS_COMMON} + + if not initial_patterns_dict: + tk.messagebox.showwarning("Configuração", "Nenhum padrão de análise inicial definido ou encontrado em common_patterns.py.", parent=global_root_window) + create_brand_buttons() + return + + analysis_output = analyze_ascii_patterns_from_file(global_ascii_text, initial_patterns_dict) + + display_initial_results(analysis_output, global_results_text_widget, global_root_window) + create_brand_buttons() + global_progress_label.config(text="Análise inicial concluída. Selecione uma marca.") + global_progress_bar['value'] = 100 + global_root_window.update_idletasks() + +# Nova função para exibir os resultados iniciais combinados +def display_initial_results(initial_results_map, results_text_widget, root_window): + results_text_widget.config(state=tk.NORMAL) + results_text_widget.delete(1.0, tk.END) + results_text_widget.insert(tk.END, "--- Análise Inicial Rápida ---\n") + found_any_overall = False + + for pattern_name in INITIAL_ANALYSIS_PATTERNS_KEYS: + results_text_widget.insert(tk.END, f" --- {pattern_name} ---\n") + matches_for_pattern = initial_results_map.get(pattern_name) + + if matches_for_pattern: + found_any_overall = True + for item, count in matches_for_pattern.items(): + results_text_widget.insert(tk.END, f" {item}\n") # Removida a contagem de ocorrências + else: + results_text_widget.insert(tk.END, " Nenhum resultado encontrado para este padrão.\n") + + if not found_any_overall: + results_text_widget.insert(tk.END, " Nenhum dos padrões de análise rápida encontrou correspondências no ficheiro.\n") + + results_text_widget.insert(tk.END, "\n--- Fim da Análise Inicial Rápida ---\n") + + # Adicionar nova secção para Grupos (VAG, JLR, etc.) + results_text_widget.insert(tk.END, "\n---- Centralina Usada nestes Marcas----\n") + vag_ecu_list = load_vag_ecu_list() + jlr_ecu_list = load_jlr_ecu_list() + bmw_ecu_list = load_bmw_ecu_list() + + found_in_vag_group = False + found_in_jlr_group = False + found_in_bmw_group = False + any_group_found = False + + ecu_families_found = set() # Para armazenar as famílias de ECU encontradas na análise inicial + if initial_results_map and 'Ecu Family' in initial_results_map: + for ecu_family, count in initial_results_map['Ecu Family'].items(): + ecu_families_found.add(ecu_family) + + if ecu_families_found: + for ecu_family in ecu_families_found: + if ecu_family in vag_ecu_list: + found_in_vag_group = True + if ecu_family in jlr_ecu_list: + found_in_jlr_group = True + if ecu_family in bmw_ecu_list: + found_in_bmw_group = True + + if found_in_vag_group: + results_text_widget.insert(tk.END, " - Grupo VAG\n") + any_group_found = True + if found_in_jlr_group: + results_text_widget.insert(tk.END, " - Grupo Jaguar LandRover\n") + any_group_found = True + if found_in_bmw_group: + results_text_widget.insert(tk.END, " - Grupo BMW\n") + any_group_found = True + + if not any_group_found: + results_text_widget.insert(tk.END, " - Nenhum grupo correspondente encontrado.\n") + else: + results_text_widget.insert(tk.END, " Nenhuma família de ECU encontrada na análise para verificar grupos.\n") + + results_text_widget.config(state=tk.DISABLED) + if root_window: root_window.update_idletasks() + +# Função a ser chamada pelo clique de um botão de categoria (sub-análise) +def run_single_analysis(pattern_name_key, ascii_content, results_widget, root, progress_bar, progress_label_widget, brand_context=None): + if not ascii_content: + tk.messagebox.showwarning("Aviso", "Conteúdo do ficheiro não está carregado.", parent=root) + return + + progress_label_widget.config(text=f"A analisar para: {pattern_name_key}...") + progress_bar['value'] = 0 + root.update_idletasks() + + regex_to_use = None + if brand_context == 'VAG' and pattern_name_key in PATTERNS_VAG: + regex_to_use = PATTERNS_VAG[pattern_name_key] + elif brand_context == 'LandRover/Jaguar' and pattern_name_key in PATTERNS_LANDROVER_JAGUAR: + regex_to_use = PATTERNS_LANDROVER_JAGUAR[pattern_name_key] + elif brand_context == 'BMW' and pattern_name_key in PATTERNS_BMW: + regex_to_use = PATTERNS_BMW[pattern_name_key] + # Adicionar elif para outras marcas aqui + + if regex_to_use is None and pattern_name_key in PATTERNS_COMMON: + regex_to_use = PATTERNS_COMMON[pattern_name_key] + + if not regex_to_use: + tk.messagebox.showerror("Erro de Configuração", f"Padrão '{pattern_name_key}' não encontrado para o contexto '{brand_context or 'Comum'}'.", parent=root) + progress_label_widget.config(text=f"Erro: Padrão '{pattern_name_key}' não configurado.") + return + + single_pattern_dict_to_search = {pattern_name_key: regex_to_use} + analysis_result = analyze_ascii_patterns_from_file(ascii_content, single_pattern_dict_to_search, target_pattern_name=pattern_name_key) + + progress_bar['value'] = 50 + root.update_idletasks() + + results_widget.config(state=tk.NORMAL) + results_widget.delete(1.0, tk.END) + matches_dict = analysis_result.get(pattern_name_key, {}) + + if matches_dict: + results_widget.insert(tk.END, f"Resultados para '{pattern_name_key}':\n") + for item, count in matches_dict.items(): + results_widget.insert(tk.END, f" - {item} (Ocorrências: {count})\n") + else: + results_widget.insert(tk.END, f"Nenhum resultado encontrado para '{pattern_name_key}'.\n") + + results_widget.config(state=tk.DISABLED) + progress_bar['value'] = 100 + progress_label_widget.config(text=f"Análise para '{pattern_name_key}' concluída.") + root.update_idletasks() + +def run_vag_sw_analysis(ascii_content, results_widget, root, progress_bar, progress_label_widget): + if not ascii_content: + messagebox.showwarning("Aviso", "Nenhum ficheiro carregado ou conteúdo ASCII não disponível.") + return + + # Check if required patterns are defined + missing_patterns = [] + if 'PATTERNS_COMMON' not in globals() or not isinstance(PATTERNS_COMMON, dict) or \ + 'Possible Software Number' not in PATTERNS_COMMON: + missing_patterns.append("'Possible Software Number' de PATTERNS_COMMON") + if 'PATTERNS_VAG' not in globals() or not isinstance(PATTERNS_VAG, dict) or \ + 'VAG 10SW' not in PATTERNS_VAG: + missing_patterns.append("'VAG 10SW' de PATTERNS_VAG") + + if missing_patterns: + messagebox.showerror("Erro de Configuração", f"Padrões não encontrados ou mal configurados: {', '.join(missing_patterns)}.") + return + + progress_label_widget.config(text="A analisar VAG SW...") + progress_bar['value'] = 0 + if root: root.update_idletasks() + + patterns_for_vag_sw = { + 'Possible Software Number': PATTERNS_COMMON['Possible Software Number'], + 'VAG 10SW': PATTERNS_VAG['VAG 10SW'] + } + + all_results_map = {} + total_patterns_to_analyze = len(patterns_for_vag_sw) + + for i, (name, pattern_regex) in enumerate(patterns_for_vag_sw.items()): + progress_val = int(((i + 0.5) / total_patterns_to_analyze) * 100) + progress_bar['value'] = progress_val + progress_label_widget.config(text=f"A analisar VAG SW: {name} ({progress_val}%)") + if root: root.update_idletasks() + + current_search_dict = {name: pattern_regex} + # Pass progress_callback=None as analyze_ascii_patterns_from_file might not use it for single pattern + analysis_results = analyze_ascii_patterns_from_file(ascii_content, current_search_dict, progress_callback=None, target_pattern_name=name) + + if name in analysis_results and analysis_results[name]: + all_results_map[name] = analysis_results[name] + + results_widget.config(state=tk.NORMAL) + results_widget.delete('1.0', tk.END) + + results_widget.insert(tk.END, "--- Análise VAG SW ---\n") + displayed_sw_numbers = set() + found_any_sw = False + + # Processar 'Possible Software Number' primeiro + pattern_order = ['Possible Software Number', 'VAG 10SW'] + + for pattern_title in pattern_order: + if pattern_title in all_results_map and all_results_map[pattern_title]: + items_found = all_results_map[pattern_title] + # Filtrar itens já exibidos, especialmente para 'VAG 10SW' + unique_items_to_display = [] + if items_found: + sorted_items = sorted(items_found.items(), key=lambda x: x[1], reverse=True) + for item, count in sorted_items: + if item not in displayed_sw_numbers: + unique_items_to_display.append(item) + displayed_sw_numbers.add(item) + + if unique_items_to_display: + found_any_sw = True + results_widget.insert(tk.END, f" --- {pattern_title} ---\n") + for item_text in unique_items_to_display: + results_widget.insert(tk.END, f" {item_text}\n") + elif pattern_title == 'Possible Software Number' and not found_any_sw: # Se PSN não achou nada, e é a primeira vez + results_widget.insert(tk.END, f" --- {pattern_title} ---\n Nenhum resultado encontrado.\n") + + if not found_any_sw: + results_widget.insert(tk.END, " Nenhum número de software VAG encontrado.\n") + + results_widget.insert(tk.END, "\n--- Fim da Análise VAG SW ---\n") + + results_widget.config(state=tk.DISABLED) + progress_label_widget.config(text="Análise VAG SW concluída.") + progress_bar['value'] = 100 + if root: root.update_idletasks() + +# Função para limpar o estado da UI +def clear_ui_state(): + if global_file_path_var: global_file_path_var.set("Nenhum ficheiro selecionado") + if global_results_text_widget: + global_results_text_widget.config(state=tk.NORMAL) + global_results_text_widget.delete(1.0, tk.END) + global_results_text_widget.insert(tk.END, "Por favor, selecione um ficheiro para análise.") + global_results_text_widget.config(state=tk.DISABLED) + for frame in [global_brand_buttons_frame, global_category_buttons_frame]: + if frame: + for widget in frame.winfo_children(): + widget.destroy() + if global_progress_label: global_progress_label.config(text="Pronto para iniciar.") + if global_progress_bar: global_progress_bar['value'] = 0 + # Limpar o texto ASCII global para a próxima análise + global global_ascii_text + global_ascii_text = None + if global_root_window: global_root_window.update_idletasks() + +# Função para criar os botões das marcas +def create_brand_buttons(): + for widget in global_brand_buttons_frame.winfo_children(): + widget.destroy() + for brand_name in BRAND_NAMES: + button = ttk.Button(global_brand_buttons_frame, text=brand_name, + command=lambda b_name=brand_name: show_brand_analysis_options(b_name)) + button.pack(side=tk.LEFT, padx=2, pady=2, fill=tk.X, expand=True) + if global_root_window: global_root_window.update_idletasks() + +# Função para mostrar opções de análise baseadas na marca +def show_brand_analysis_options(brand_name): + if not global_ascii_text: + tk.messagebox.showwarning("Aviso", "Conteúdo do ficheiro não está carregado.", parent=global_root_window) + return + + for widget in global_category_buttons_frame.winfo_children(): + widget.destroy() + + patterns_to_show_for_brand_keys = [] + for p_name in PATTERNS_COMMON.keys(): + # Exclui 'Possible PIN' especificamente para a marca VAG ou BMW + if (brand_name == 'VAG' and p_name == 'Possible PIN') or \ + (brand_name == 'BMW' and p_name == 'Possible PIN'): + continue + if p_name not in INITIAL_ANALYSIS_PATTERNS_KEYS: + patterns_to_show_for_brand_keys.append(p_name) + + brand_specific_patterns_for_this_brand_keys = [] + if brand_name == 'VAG': + brand_specific_patterns_for_this_brand_keys = list(PATTERNS_VAG.keys()) + elif brand_name == 'LandRover/Jaguar': + brand_specific_patterns_for_this_brand_keys = list(PATTERNS_LANDROVER_JAGUAR.keys()) + elif brand_name == 'BMW': + brand_specific_patterns_for_this_brand_keys = list(PATTERNS_BMW.keys()) + # Adicionar elif para outras marcas aqui + + combined_keys = patterns_to_show_for_brand_keys + brand_specific_patterns_for_this_brand_keys + patterns_to_show_for_brand_keys = sorted(list(set(combined_keys))) + + global_progress_label.config(text=f"Marca: {brand_name}. Selecione um tipo de análise.") + global_progress_bar['value'] = 0 + + if not patterns_to_show_for_brand_keys: + label_no_patterns = ttk.Label(global_category_buttons_frame, text=f"Nenhum padrão de análise definido para a marca {brand_name}.") + label_no_patterns.pack(pady=10) + global_root_window.update_idletasks() + return + + for pattern_name_key in patterns_to_show_for_brand_keys: + button_text = pattern_name_key + current_command = None + + if brand_name == 'VAG': + if pattern_name_key == 'Bosch SW': # Esta é a chave original que queremos substituir por 'VAG SW' + button_text = 'VAG SW' + current_command = lambda: run_vag_sw_analysis( # run_vag_sw_analysis agora só faz 10SW + global_ascii_text, + global_results_text_widget, + global_root_window, + global_progress_bar, + global_progress_label + ) + elif pattern_name_key == 'VAG 10SW': # Esta chave vem de PATTERNS_VAG.keys(), não queremos um botão para ela. + continue # Pula a criação de um botão dedicado 'VAG 10SW' + elif pattern_name_key == 'VAG Specific PartNumbers': + # Para este, queremos um botão que chame run_single_analysis como normal + button_text = 'VAG Specific PartNumbers' + current_command = lambda p_name=pattern_name_key: run_single_analysis( + p_name, + global_ascii_text, + global_results_text_widget, + global_root_window, + global_progress_bar, + global_progress_label, + brand_context=brand_name + ) + else: + # Para outros padrões comuns ou específicos de VAG que não são os acima + current_command = lambda p_name=pattern_name_key: run_single_analysis( + p_name, + global_ascii_text, + global_results_text_widget, + global_root_window, + global_progress_bar, + global_progress_label, + brand_context=brand_name + ) + else: # Para marcas que não são VAG + current_command = lambda p_name=pattern_name_key: run_single_analysis( + p_name, + global_ascii_text, + global_results_text_widget, + global_root_window, + global_progress_bar, + global_progress_label, + brand_context=brand_name + ) + + if current_command: # Só cria o botão se um comando foi definido + button = ttk.Button(global_category_buttons_frame, text=button_text, command=current_command) + button.pack(side=tk.LEFT, padx=2, pady=2, fill=tk.X, expand=True) + if global_root_window: global_root_window.update_idletasks() + +# Função principal para criar a janela e os widgets +def create_main_window(): + global global_root_window, global_file_path_var, global_results_text_widget + global global_brand_buttons_frame, global_category_buttons_frame, global_progress_bar, global_progress_label + + root = tk.Tk() + root.title("Analisador ASCII de Ficheiros ECU - Refatorado") + global_root_window = root + + style = ttk.Style(root) + try: + style.theme_use('clam') # 'clam' é uma boa opção multiplataforma + except tk.TclError: + # Fallback para um tema padrão se 'clam' não estiver disponível + # Isto pode acontecer em algumas instalações mínimas de Tk + print("Tema 'clam' não encontrado, usando tema padrão do Tk.") + pass # Usa o tema padrão do sistema + + file_path_var = tk.StringVar() + global_file_path_var = file_path_var + file_label = ttk.Label(root, textvariable=file_path_var) + file_label.pack(pady=5, padx=10, fill=tk.X) + + select_button = ttk.Button(root, text="Selecionar Ficheiro e Iniciar Análise", + command=lambda: handle_file_selection_and_initial_analysis(file_path_var)) + select_button.pack(pady=5, padx=10, fill=tk.X) # pady ajustado para consistência + + progress_frame = ttk.Frame(root) + progress_frame.pack(pady=5, padx=10, fill=tk.X) + progress_label = ttk.Label(progress_frame, text="Pronto para iniciar.") + progress_label.pack(side=tk.LEFT, padx=5) + progress_bar = ttk.Progressbar(progress_frame, orient='horizontal', mode='determinate', length=200) + progress_bar.pack(side=tk.LEFT, fill=tk.X, expand=True) + global_progress_bar = progress_bar + global_progress_label = progress_label + + brand_buttons_frame = ttk.Frame(root) + brand_buttons_frame.pack(pady=5, padx=10, fill=tk.X) + global_brand_buttons_frame = brand_buttons_frame + + category_buttons_frame = ttk.Frame(root) + category_buttons_frame.pack(pady=5, padx=10, fill=tk.X) + global_category_buttons_frame = category_buttons_frame + + results_text_widget = scrolledtext.ScrolledText(root, wrap=tk.WORD, width=100, height=20, state=tk.DISABLED) + results_text_widget.pack(pady=10, padx=10, fill=tk.BOTH, expand=True) + global_results_text_widget = results_text_widget + + clear_ui_state() # Limpa a UI antes de iniciar + root.mainloop() + +if __name__ == "__main__": + create_main_window() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..70fe80e --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +# Esta aplicação usa principalmente módulos built-in do Python. +# Tkinter (para a GUI) faz parte da instalação standard do Python. +# Nenhuma dependência externa é estritamente necessária para a funcionalidade base. + +# Se quiser executar num ambiente virtual, pode criar um e ativá-lo. +# python -m venv venv +# venv\Scripts\activate (Windows) +# source venv/bin/activate (Linux/macOS)