import tkinter as tk from tkinter import ttk, messagebox import numpy as np from logger import logger class BinaryGraphWindow: def __init__(self, parent, binary_data): try: logger.info("Initializing Binary Graph Window") # Validate input if not isinstance(binary_data, (list, np.ndarray)) or len(binary_data) == 0: raise ValueError("Invalid binary data: must be a non-empty list or numpy array") # Store data as instance attribute self.binary_data = list(binary_data) self.parent = parent # Criar janela self.window = tk.Toplevel(parent) self.window.title("2D Binary Graph") self.window.geometry("1000x600") # Frame principal self.main_frame = ttk.Frame(self.window) self.main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) # Frame para os botões de bits e configurações self.top_frame = ttk.Frame(self.main_frame) self.top_frame.pack(fill=tk.X, padx=5, pady=5) # Frame para os botões de bits self.bits_frame = ttk.Frame(self.top_frame) self.bits_frame.pack(side=tk.LEFT, padx=(0, 10)) # Frame para seleção de pontos self.points_frame = ttk.Frame(self.top_frame) self.points_frame.pack(side=tk.LEFT) # Variável para controlar o modo de bits self.bits_mode = tk.StringVar(value="8b") # Variável para controlar o número de pontos self.points_var = tk.IntVar(value=512) # Estilo para botões pequenos style = ttk.Style() style.configure("Small.TButton", padding=1) # Botões para seleção de bits self.btn_8b = ttk.Button(self.bits_frame, text="8b", width=3, style="Small.TButton", command=lambda: self.change_bits_mode("8b")) self.btn_8b.pack(side=tk.LEFT, padx=2) self.btn_16b = ttk.Button(self.bits_frame, text="16b", width=3, style="Small.TButton", command=lambda: self.change_bits_mode("16b")) self.btn_16b.pack(side=tk.LEFT, padx=2) self.btn_32b = ttk.Button(self.bits_frame, text="32b", width=3, style="Small.TButton", command=lambda: self.change_bits_mode("32b")) self.btn_32b.pack(side=tk.LEFT, padx=2) # Label e Spinbox para seleção de pontos ttk.Label(self.points_frame, text="Points:").pack(side=tk.LEFT, padx=(0, 5)) self.points_spinbox = ttk.Spinbox( self.points_frame, from_=5, to=1500, textvariable=self.points_var, width=5, command=self.update_points_display ) self.points_spinbox.pack(side=tk.LEFT, padx=(0, 5)) # Frame para canvas e scrollbar self.graph_frame = ttk.Frame(self.main_frame) self.graph_frame.pack(fill=tk.BOTH, expand=True) # Canvas para o gráfico self.canvas = tk.Canvas(self.graph_frame, bg='white') self.canvas.pack(side=tk.TOP, fill=tk.BOTH, expand=True) # Scrollbar horizontal self.scrollbar = ttk.Scrollbar(self.graph_frame, orient=tk.HORIZONTAL, command=self.on_scroll) self.scrollbar.pack(side=tk.BOTTOM, fill=tk.X) # Configurar canvas para usar scrollbar self.canvas.configure(xscrollcommand=self.scrollbar.set) # Configurações iniciais self.max_display = 512 # Definir 512 pontos como padrão self.current_start = 0 self.points_var.set(512) # Definir valor inicial do spinbox # Processar dados iniciais self.process_data() # Plotar dados iniciais self.plot_data() # Destacar botão 8b inicialmente self.update_bits_buttons() # Vincular evento de redimensionamento self.canvas.bind('', self.on_resize) # Configurar scrolling com o mouse self.canvas.bind('', self.on_mousewheel) logger.info(f"Binary Graph initialized with {len(binary_data)} data points") except Exception as e: logger.error(f"Error initializing Binary Graph: {e}", exc_info=True) messagebox.showerror("Initialization Error", str(e)) if hasattr(self, 'window'): self.window.destroy() def on_scroll(self, *args): """Manipula a rolagem do scrollbar""" try: # Obter a posição atual do scrollbar if len(args) > 1: # Scroll através da scrollbar fraction = float(args[1]) total_points = len(self.processed_data) # Calcular novo índice inicial baseado na fração da scrollbar max_start = max(0, total_points - self.max_display) self.current_start = int(fraction * max_start) # Garantir que current_start não ultrapasse o limite self.current_start = max(0, min(self.current_start, max_start)) # Replottar com a nova posição self.plot_data() logger.info(f"Scrolled to position {self.current_start}") except Exception as e: logger.error(f"Error in scroll handling: {e}", exc_info=True) def update_scrollbar(self): """Atualiza a posição e o tamanho do scrollbar""" try: total_points = len(self.processed_data) if total_points <= self.max_display: # Se todos os pontos estão visíveis, desabilitar scrollbar self.scrollbar.set(0, 1) return # Calcular a fração visível visible_fraction = self.max_display / total_points # Calcular a posição inicial como fração max_start = total_points - self.max_display start_fraction = self.current_start / max_start if max_start > 0 else 0 # Definir posição e tamanho do scrollbar self.scrollbar.set(start_fraction, start_fraction + visible_fraction) logger.info(f"Updated scrollbar: position {start_fraction:.2f}, size {visible_fraction:.2f}") except Exception as e: logger.error(f"Error updating scrollbar: {e}", exc_info=True) def change_bits_mode(self, mode): # Definir o modo de bits self.bits_mode.set(mode) # Atualizar estado dos botões self.update_bits_buttons() # Processar dados com o novo modo de bits self.process_data() # Resetar para 512 pontos ao mudar o modo de bits self.points_var.set(512) self.max_display = 512 self.current_start = 0 # Replottar dados self.plot_data() logger.info(f"Changed graph mode to {mode}") def update_bits_buttons(self): # Atualizar aparência dos botões current_mode = self.bits_mode.get() for btn, mode in [(self.btn_8b, "8b"), (self.btn_16b, "16b"), (self.btn_32b, "32b")]: if mode == current_mode: btn.state(['pressed']) else: btn.state(['!pressed']) def process_data(self): # Processar dados de acordo com o modo selecionado mode = self.bits_mode.get() raw_data = self.binary_data if mode == "8b": self.processed_data = raw_data elif mode == "16b": # Converter para 16 bits (combinar bytes adjacentes) self.processed_data = [] for i in range(0, len(raw_data)-1, 2): value = (raw_data[i+1] << 8) | raw_data[i] self.processed_data.append(value) elif mode == "32b": # Converter para 32 bits (combinar 4 bytes adjacentes) self.processed_data = [] for i in range(0, len(raw_data)-3, 4): value = (raw_data[i+3] << 24) | (raw_data[i+2] << 16) | \ (raw_data[i+1] << 8) | raw_data[i] self.processed_data.append(value) def get_max_value_for_mode(self): """Retorna o valor máximo para o modo de bits atual""" mode = self.bits_mode.get() if mode == "8b": return 255 elif mode == "16b": return 65535 else: # 32b return 4294967295 def format_hex(self, value): """Formata um número para hexadecimal sem o prefixo 0x""" return format(value, 'X') def plot_data(self): """Plota os dados no canvas""" try: # Limpar canvas self.canvas.delete("all") # Obter dimensões do canvas plot_width = self.canvas.winfo_width() - 100 # Margem para eixos plot_height = self.canvas.winfo_height() - 100 # Margem para eixos margin_left = 50 margin_bottom = 50 if not self.processed_data or len(self.processed_data) == 0: return # Calcular índices de início e fim para exibição end_idx = min(self.current_start + self.max_display, len(self.processed_data)) visible_data = self.processed_data[self.current_start:end_idx] if not visible_data: return # Usar valor máximo fixo baseado no modo de bits max_val = self.get_max_value_for_mode() min_val = 0 # Sempre começar do zero # Calcular escalas x_scale = plot_width / (len(visible_data) - 1) if len(visible_data) > 1 else plot_width y_scale = plot_height / max_val if max_val != 0 else 1 # Preparar pontos para plotagem points = [] for i, value in enumerate(visible_data): x = margin_left + (i * x_scale) y = self.canvas.winfo_height() - margin_bottom - (value * y_scale) points.extend([x, y]) # Plotar linha if len(points) >= 4: self.canvas.create_line(points, fill='blue', width=1) # Desenhar eixo X self.canvas.create_line( margin_left, self.canvas.winfo_height() - margin_bottom, margin_left + plot_width, self.canvas.winfo_height() - margin_bottom, fill='black' ) # Desenhar eixo Y self.canvas.create_line( margin_left, self.canvas.winfo_height() - margin_bottom, margin_left, margin_bottom, fill='black' ) # Desenhar marcações no eixo X num_x_ticks = 10 for i in range(num_x_ticks + 1): x_pos = margin_left + (i * plot_width / num_x_ticks) self.canvas.create_line( x_pos, self.canvas.winfo_height() - margin_bottom, x_pos, self.canvas.winfo_height() - margin_bottom + 5, fill='black' ) x_val = self.current_start + int((i / num_x_ticks) * (end_idx - self.current_start)) # Multiplicar por 2 e converter para hex apenas no modo 16 bits if self.bits_mode.get() == "16b": display_val = x_val * 2 hex_x_val = self.format_hex(display_val) else: hex_x_val = self.format_hex(x_val) self.canvas.create_text( x_pos, self.canvas.winfo_height() - margin_bottom + 20, text=hex_x_val ) # Desenhar marcações no eixo Y num_y_ticks = 5 for i in range(num_y_ticks + 1): y_val = int((i / num_y_ticks) * max_val) y_pos = self.canvas.winfo_height() - margin_bottom - (y_val * y_scale) self.canvas.create_line( margin_left - 5, y_pos, margin_left, y_pos, fill='black' ) # Manter valor do eixo Y em decimal self.canvas.create_text( margin_left - 25, y_pos, text=str(y_val) ) # Atualizar scrollbar self.update_scrollbar() # Atualizar título do gráfico title = f"{self.bits_mode.get()}-bit Binary Data" self.canvas.create_text( self.canvas.winfo_width() // 2, 20, text=title, font=('Arial', 12, 'bold') ) logger.info(f"Plotted {len(visible_data)} points from index {self.current_start} to {end_idx}") except Exception as e: logger.error(f"Error plotting data: {e}", exc_info=True) messagebox.showerror("Plotting Error", str(e)) def on_resize(self, event): # Replottar quando o canvas for redimensionado self.plot_data() def update_points_display(self): """Atualiza o número de pontos exibidos no gráfico""" try: # Obter o novo número de pontos do spinbox new_points = self.points_var.get() # Validar entrada if new_points < 5 or new_points > 1500: messagebox.showerror("Invalid Input", "Points must be between 5 and 1500") # Restaurar o valor anterior self.points_var.set(self.max_display) return # Limpar canvas antes de replottar self.canvas.delete("all") # Atualizar número máximo de pontos self.max_display = new_points # Garantir que o início atual não ultrapasse o total de pontos total_points = len(self.processed_data) self.current_start = min(self.current_start, max(0, total_points - new_points)) # Replottar dados self.plot_data() # Atualizar scrollbar self.update_scrollbar() logger.info(f"Updated graph display to {new_points} points") except Exception as e: logger.error(f"Error updating points display: {e}", exc_info=True) messagebox.showerror("Update Error", str(e)) def on_mousewheel(self, event): # Manipula a rolagem com o mouse try: # Obter a posição atual do scrollbar total_points = len(self.processed_data) # Calcular novo índice inicial baseado na fração da scrollbar max_start = max(0, total_points - self.max_display) if event.delta > 0: self.current_start = max(0, self.current_start - 10) else: self.current_start = min(max_start, self.current_start + 10) # Replottar com a nova posição self.plot_data() logger.info(f"Scrolled to position {self.current_start}") except Exception as e: logger.error(f"Error in mousewheel handling: {e}", exc_info=True)