Implementación de un sistema de logging para mejorar la gestión de advertencias y errores en la aplicación. Se establece un límite para la cantidad de archivos de log generados y se optimiza la configuración de la ventana. Se ajusta el manejo de la evaluación y el autocompletado, mejorando la experiencia del usuario.
This commit is contained in:
parent
589bab03b2
commit
0c7ed33d0d
File diff suppressed because it is too large
Load Diff
15
calc.py
15
calc.py
|
@ -19,6 +19,7 @@ import platform
|
||||||
|
|
||||||
def setup_logging():
|
def setup_logging():
|
||||||
"""Configura el sistema de logging completo"""
|
"""Configura el sistema de logging completo"""
|
||||||
|
MAX_LOG_FILES = 10 # Límite de archivos de log
|
||||||
log_dir = Path("logs")
|
log_dir = Path("logs")
|
||||||
log_dir.mkdir(exist_ok=True)
|
log_dir.mkdir(exist_ok=True)
|
||||||
|
|
||||||
|
@ -26,6 +27,20 @@ def setup_logging():
|
||||||
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||||
log_file = log_dir / f"mav_calc_{timestamp}.log"
|
log_file = log_dir / f"mav_calc_{timestamp}.log"
|
||||||
|
|
||||||
|
# Eliminar logs antiguos si se supera el límite
|
||||||
|
try:
|
||||||
|
existing_logs = sorted(
|
||||||
|
[f for f in log_dir.glob("mav_calc_*.log") if f.is_file()],
|
||||||
|
key=os.path.getmtime
|
||||||
|
)
|
||||||
|
if len(existing_logs) >= MAX_LOG_FILES:
|
||||||
|
logs_to_delete = existing_logs[:len(existing_logs) - MAX_LOG_FILES + 1]
|
||||||
|
for old_log in logs_to_delete:
|
||||||
|
old_log.unlink()
|
||||||
|
logging.info(f"Eliminado log antiguo: {old_log}")
|
||||||
|
except Exception as e:
|
||||||
|
logging.warning(f"No se pudieron eliminar logs antiguos: {e}")
|
||||||
|
|
||||||
# Configurar logging
|
# Configurar logging
|
||||||
logging.basicConfig(
|
logging.basicConfig(
|
||||||
level=logging.DEBUG,
|
level=logging.DEBUG,
|
||||||
|
|
|
@ -1,10 +1,5 @@
|
||||||
|
|
||||||
|
a = 10 + b
|
||||||
|
a=?
|
||||||
|
|
||||||
x=12
|
|
||||||
a=x
|
|
||||||
|
|
||||||
x=2
|
|
||||||
b=x
|
|
||||||
|
|
||||||
a
|
|
||||||
b
|
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"window_geometry": "1020x700+144+161",
|
"window_geometry": "1020x700+467+199",
|
||||||
"sash_pos_x": 355,
|
"sash_pos_x": 363,
|
||||||
"symbolic_mode": true,
|
"symbolic_mode": true,
|
||||||
"show_numeric_approximation": true,
|
"show_numeric_approximation": true,
|
||||||
"keep_symbolic_fractions": true,
|
"keep_symbolic_fractions": true,
|
||||||
|
|
202
main_calc_app.py
202
main_calc_app.py
|
@ -6,6 +6,7 @@ import tkinter as tk
|
||||||
from tkinter import scrolledtext, messagebox, Menu, filedialog
|
from tkinter import scrolledtext, messagebox, Menu, filedialog
|
||||||
import tkinter.font as tkFont
|
import tkinter.font as tkFont
|
||||||
import json
|
import json
|
||||||
|
import logging # <--- AÑADIDO
|
||||||
import os
|
import os
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
import threading
|
import threading
|
||||||
|
@ -35,10 +36,12 @@ except ImportError:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
HTML_VIEWER_TYPE = None
|
HTML_VIEWER_TYPE = None
|
||||||
|
|
||||||
|
# Usar logging para estas advertencias iniciales
|
||||||
|
module_logger = logging.getLogger(__name__)
|
||||||
if not MARKDOWN_AVAILABLE:
|
if not MARKDOWN_AVAILABLE:
|
||||||
print("Advertencia: La librería 'markdown' no está instalada. La ayuda se mostrará en texto plano.")
|
module_logger.warning("La librería 'markdown' no está instalada. La ayuda se mostrará en texto plano.")
|
||||||
if MARKDOWN_AVAILABLE and HTML_VIEWER_TYPE is None:
|
if MARKDOWN_AVAILABLE and HTML_VIEWER_TYPE is None:
|
||||||
print("Advertencia: 'markdown' está disponible, pero no se encontró un visor HTML (tkinterweb/tkhtmlview). La ayuda se mostrará en texto plano.")
|
module_logger.warning("'markdown' está disponible, pero no se encontró un visor HTML (tkinterweb/tkhtmlview). La ayuda se mostrará en texto plano.")
|
||||||
|
|
||||||
# ========== IMPORTS ADAPTADOS AL NUEVO SISTEMA ==========
|
# ========== IMPORTS ADAPTADOS AL NUEVO SISTEMA ==========
|
||||||
# Importar componentes del CAS híbrido con nuevo sistema de tipos
|
# Importar componentes del CAS híbrido con nuevo sistema de tipos
|
||||||
|
@ -58,6 +61,7 @@ class HybridCalculatorApp:
|
||||||
|
|
||||||
def __init__(self, root: tk.Tk):
|
def __init__(self, root: tk.Tk):
|
||||||
self.root = root
|
self.root = root
|
||||||
|
self.logger = logging.getLogger(__name__) # <--- AÑADIDO: Logger para la instancia
|
||||||
self.root.title("Calculadora MAV - CAS Híbrido")
|
self.root.title("Calculadora MAV - CAS Híbrido")
|
||||||
|
|
||||||
# Configuración y estado
|
# Configuración y estado
|
||||||
|
@ -121,34 +125,35 @@ class HybridCalculatorApp:
|
||||||
# Obtener helpers registrados dinámicamente
|
# Obtener helpers registrados dinámicamente
|
||||||
self.HELPERS = get_registered_helper_functions()
|
self.HELPERS = get_registered_helper_functions()
|
||||||
|
|
||||||
# Añadir SympyHelper al final
|
# Añadir SympyHelper.Helper al final
|
||||||
self.HELPERS.append(SympyHelper.Helper)
|
self.HELPERS.append(SympyHelper.Helper)
|
||||||
|
|
||||||
print(f"🆘 Helpers dinámicos cargados: {len(self.HELPERS)}")
|
# Usar logger en lugar de print, y sin emoji para la consola
|
||||||
|
self.logger.info(f"Helpers dinámicos cargados: {len(self.HELPERS)}") # Original: 🆘
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"⚠️ Error cargando helpers dinámicos: {e}")
|
# Usar logger en lugar de print, y sin emoji para la consola
|
||||||
|
self.logger.error(f"Error cargando helpers dinámicos: {e}", exc_info=True) # Original: ⚠️
|
||||||
# Fallback a helpers básicos
|
# Fallback a helpers básicos
|
||||||
self.HELPERS = [SympyHelper.Helper]
|
self.HELPERS = [SympyHelper.Helper]
|
||||||
|
|
||||||
def reload_types(self):
|
def reload_types(self):
|
||||||
"""Recarga el sistema de tipos (útil para desarrollo)"""
|
"""Recarga el sistema de tipos (útil para desarrollo)"""
|
||||||
try:
|
try:
|
||||||
print("🔄 Recargando sistema de tipos...")
|
self.logger.info("Recargando sistema de tipos...") # Original: 🔄
|
||||||
|
|
||||||
# Recargar engine
|
# Recargar engine
|
||||||
self.engine.reload_types()
|
self.engine.reload_types()
|
||||||
|
|
||||||
# Recargar helpers
|
# Recargar helpers
|
||||||
self._setup_dynamic_helpers()
|
self._setup_dynamic_helpers()
|
||||||
|
|
||||||
# Re-evaluar contenido actual
|
# Re-evaluar contenido actual
|
||||||
self._evaluate_and_update()
|
self._evaluate_and_update()
|
||||||
|
|
||||||
print("✅ Sistema de tipos recargado")
|
self.logger.info("Sistema de tipos recargado.") # Original: ✅
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"❌ Error recargando tipos: {e}")
|
self.logger.error(f"Error recargando tipos: {e}", exc_info=True) # Original: ❌
|
||||||
messagebox.showerror("Error", f"Error recargando tipos:\n{e}")
|
messagebox.showerror("Error", f"Error recargando tipos:\n{e}")
|
||||||
|
|
||||||
def show_types_info(self):
|
def show_types_info(self):
|
||||||
|
@ -186,15 +191,15 @@ CLASES DISPONIBLES:
|
||||||
icon_path = script_dir / "icon.png"
|
icon_path = script_dir / "icon.png"
|
||||||
|
|
||||||
if not icon_path.is_file():
|
if not icon_path.is_file():
|
||||||
print(f"Advertencia: Archivo de ícono no encontrado en '{icon_path}'.")
|
self.logger.warning(f"Archivo de ícono no encontrado en '{icon_path}'.")
|
||||||
return
|
return
|
||||||
|
|
||||||
self.app_icon = tk.PhotoImage(file=str(icon_path))
|
self.app_icon = tk.PhotoImage(file=str(icon_path))
|
||||||
self.root.iconphoto(True, self.app_icon)
|
self.root.iconphoto(True, self.app_icon)
|
||||||
except tk.TclError as e:
|
except tk.TclError as e:
|
||||||
print(f"Advertencia: No se pudo cargar el ícono desde '{icon_path}'. Error de Tkinter: {e}")
|
self.logger.warning(f"No se pudo cargar el ícono desde '{icon_path}'. Error de Tkinter: {e}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Advertencia: Ocurrió un error inesperado al cargar el ícono desde '{icon_path}': {e}")
|
self.logger.warning(f"Ocurrió un error inesperado al cargar el ícono desde '{icon_path}': {e}", exc_info=True)
|
||||||
|
|
||||||
def _load_settings(self) -> Dict[str, Any]:
|
def _load_settings(self) -> Dict[str, Any]:
|
||||||
"""Carga configuración de la aplicación"""
|
"""Carga configuración de la aplicación"""
|
||||||
|
@ -221,7 +226,7 @@ CLASES DISPONIBLES:
|
||||||
json.dump(self.settings, f, indent=4, ensure_ascii=False)
|
json.dump(self.settings, f, indent=4, ensure_ascii=False)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print(f"Error guardando configuración: {e}")
|
self.logger.error(f"Error guardando configuración: {e}", exc_info=True)
|
||||||
|
|
||||||
def update_symbolic_settings(self, symbolic_mode=None, show_numeric=None,
|
def update_symbolic_settings(self, symbolic_mode=None, show_numeric=None,
|
||||||
keep_fractions=None, auto_simplify=None):
|
keep_fractions=None, auto_simplify=None):
|
||||||
|
@ -444,8 +449,8 @@ CLASES DISPONIBLES:
|
||||||
if event and event.char == '.' and self.input_text.focus_get() == self.input_text:
|
if event and event.char == '.' and self.input_text.focus_get() == self.input_text:
|
||||||
self._handle_dot_autocomplete()
|
self._handle_dot_autocomplete()
|
||||||
|
|
||||||
# Evaluación con debounce
|
# Evaluación con debounce y auto-dimensionado
|
||||||
self._debounce_job = self.root.after(300, self._evaluate_and_update)
|
self._debounce_job = self.root.after(300, self._process_input_and_adjust_layout)
|
||||||
|
|
||||||
def _handle_dot_autocomplete(self):
|
def _handle_dot_autocomplete(self):
|
||||||
"""Maneja el autocompletado cuando se escribe un punto - VERSIÓN DINÁMICA"""
|
"""Maneja el autocompletado cuando se escribe un punto - VERSIÓN DINÁMICA"""
|
||||||
|
@ -456,7 +461,7 @@ CLASES DISPONIBLES:
|
||||||
char_idx_after_dot = int(char_num_str)
|
char_idx_after_dot = int(char_num_str)
|
||||||
|
|
||||||
if char_idx_after_dot == 0:
|
if char_idx_after_dot == 0:
|
||||||
print("DEBUG: Autocomplete: Cursor at beginning of line after dot. No action.")
|
self.logger.debug("Autocomplete: Cursor at beginning of line after dot. No action.")
|
||||||
return
|
return
|
||||||
|
|
||||||
dot_char_index_in_line = char_idx_after_dot - 1
|
dot_char_index_in_line = char_idx_after_dot - 1
|
||||||
|
@ -466,7 +471,7 @@ CLASES DISPONIBLES:
|
||||||
|
|
||||||
# 1. Determinar si es un popup GLOBAL (usando contexto dinámico)
|
# 1. Determinar si es un popup GLOBAL (usando contexto dinámico)
|
||||||
if not stripped_text_before_dot:
|
if not stripped_text_before_dot:
|
||||||
print("DEBUG: Dot on empty line or after spaces. Offering global suggestions.")
|
self.logger.debug("Dot on empty line or after spaces. Offering global suggestions.")
|
||||||
suggestions = []
|
suggestions = []
|
||||||
|
|
||||||
# ========== USAR CONTEXTO DINÁMICO DEL REGISTRO ==========
|
# ========== USAR CONTEXTO DINÁMICO DEL REGISTRO ==========
|
||||||
|
@ -485,12 +490,12 @@ CLASES DISPONIBLES:
|
||||||
if helper_text:
|
if helper_text:
|
||||||
hint = helper_text.split('\n')[0]
|
hint = helper_text.split('\n')[0]
|
||||||
except Exception as e_helper:
|
except Exception as e_helper:
|
||||||
print(f"DEBUG: Error calling Helper for {name}: {e_helper}")
|
self.logger.debug(f"Error calling Helper for {name}: {e_helper}")
|
||||||
pass
|
pass
|
||||||
suggestions.append((name, hint))
|
suggestions.append((name, hint))
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"DEBUG: Error obteniendo contexto dinámico: {e}")
|
self.logger.debug(f"Error obteniendo contexto dinámico: {e}")
|
||||||
# Fallback básico
|
# Fallback básico
|
||||||
suggestions = [("sin", "Función seno"), ("cos", "Función coseno")]
|
suggestions = [("sin", "Función seno"), ("cos", "Función coseno")]
|
||||||
|
|
||||||
|
@ -503,7 +508,7 @@ CLASES DISPONIBLES:
|
||||||
if fname not in current_suggestion_names:
|
if fname not in current_suggestion_names:
|
||||||
suggestions.append((fname, fhint))
|
suggestions.append((fname, fhint))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList() for global: {e}")
|
self.logger.debug(f"Error calling SympyHelper.PopupFunctionList() for global: {e}")
|
||||||
|
|
||||||
if suggestions:
|
if suggestions:
|
||||||
suggestions.sort(key=lambda x: x[0])
|
suggestions.sort(key=lambda x: x[0])
|
||||||
|
@ -522,48 +527,48 @@ CLASES DISPONIBLES:
|
||||||
if not obj_expr_str_candidate or \
|
if not obj_expr_str_candidate or \
|
||||||
not re.match(r"^[a-zA-Z_0-9\(\)\[\]\.\"\'\+\-\*\/ ]*$", obj_expr_str_candidate) or \
|
not re.match(r"^[a-zA-Z_0-9\(\)\[\]\.\"\'\+\-\*\/ ]*$", obj_expr_str_candidate) or \
|
||||||
obj_expr_str_candidate.endswith(("+", "-", "*", "/", "(", ",")):
|
obj_expr_str_candidate.endswith(("+", "-", "*", "/", "(", ",")):
|
||||||
print(f"DEBUG: Extracted expr '{obj_expr_str_candidate}' from '{stripped_text_before_dot}' not a valid object for dot autocomplete.")
|
self.logger.debug(f"Extracted expr '{obj_expr_str_candidate}' from '{stripped_text_before_dot}' not a valid object for dot autocomplete.")
|
||||||
return
|
return
|
||||||
|
|
||||||
obj_expr_str = obj_expr_str_candidate
|
obj_expr_str = obj_expr_str_candidate
|
||||||
print(f"DEBUG: Autocomplete for object. Extracted: '{obj_expr_str}' from: '{text_on_line_up_to_dot}'")
|
self.logger.debug(f"Autocomplete for object. Extracted: '{obj_expr_str}' from: '{text_on_line_up_to_dot}'")
|
||||||
|
|
||||||
if not obj_expr_str:
|
if not obj_expr_str:
|
||||||
print("DEBUG: Object expression is empty after extraction. No autocomplete.")
|
self.logger.debug("Object expression is empty after extraction. No autocomplete.")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 3. Caso especial para el módulo sympy
|
# 3. Caso especial para el módulo sympy
|
||||||
if obj_expr_str == "sympy":
|
if obj_expr_str == "sympy":
|
||||||
print(f"DEBUG: Detected 'sympy.', using SympyHelper for suggestions.")
|
self.logger.debug(f"Detected 'sympy.', using SympyHelper for suggestions.")
|
||||||
try:
|
try:
|
||||||
methods = SympyHelper.PopupFunctionList()
|
methods = SympyHelper.PopupFunctionList()
|
||||||
if methods:
|
if methods:
|
||||||
self._show_autocomplete_popup(methods, is_global_popup=False)
|
self._show_autocomplete_popup(methods, is_global_popup=False)
|
||||||
else:
|
else:
|
||||||
print(f"DEBUG: SympyHelper.PopupFunctionList returned no methods.")
|
self.logger.debug(f"SympyHelper.PopupFunctionList returned no methods.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList(): {e}")
|
self.logger.debug(f"Error calling SympyHelper.PopupFunctionList(): {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 4. Preprocesar con BracketParser
|
# 4. Preprocesar con BracketParser
|
||||||
if '[' in obj_expr_str:
|
if '[' in obj_expr_str:
|
||||||
original_for_debug = obj_expr_str
|
original_for_debug = obj_expr_str
|
||||||
obj_expr_str = self.engine.parser._transform_brackets(obj_expr_str)
|
obj_expr_str = self.engine.parser._transform_brackets(obj_expr_str)
|
||||||
if obj_expr_str != original_for_debug:
|
if obj_expr_str != original_for_debug and self.debug: # Solo loguear si self.debug es True
|
||||||
print(f"DEBUG: Preprocessed by BracketParser: '{original_for_debug}' -> '{obj_expr_str}'")
|
self.logger.debug(f"Preprocessed by BracketParser: '{original_for_debug}' -> '{obj_expr_str}'")
|
||||||
|
|
||||||
# 5. Evaluar la expresión del objeto (usando contexto dinámico)
|
# 5. Evaluar la expresión del objeto (usando contexto dinámico)
|
||||||
eval_context = self.engine._get_full_context()
|
eval_context = self.engine._get_full_context()
|
||||||
obj = None
|
obj = None
|
||||||
try:
|
try:
|
||||||
if not obj_expr_str.strip():
|
if not obj_expr_str.strip():
|
||||||
print("DEBUG: Object expression became empty before eval. No action.")
|
self.logger.debug("Object expression became empty before eval. No action.")
|
||||||
return
|
return
|
||||||
print(f"DEBUG: Attempting to eval: '{obj_expr_str}'")
|
self.logger.debug(f"Attempting to eval: '{obj_expr_str}'")
|
||||||
obj = eval(obj_expr_str, eval_context)
|
obj = eval(obj_expr_str, eval_context)
|
||||||
print(f"DEBUG: Eval successful. Object: {type(obj)}, Value: {obj}")
|
self.logger.debug(f"Eval successful. Object: {type(obj)}, Value: {obj}")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"DEBUG: Error evaluating object expression '{obj_expr_str}': {e}")
|
self.logger.debug(f"Error evaluating object expression '{obj_expr_str}': {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
# 6. Mostrar popup de autocompletado para el objeto
|
# 6. Mostrar popup de autocompletado para el objeto
|
||||||
|
@ -793,7 +798,7 @@ CLASES DISPONIBLES:
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print(f"DEBUG: Error en get_result_tag_dynamic: {e}")
|
self.logger.debug(f"Error en get_result_tag_dynamic: {e}")
|
||||||
|
|
||||||
# Fallback a tags existentes para tipos no registrados
|
# Fallback a tags existentes para tipos no registrados
|
||||||
if isinstance(result, sympy.Basic):
|
if isinstance(result, sympy.Basic):
|
||||||
|
@ -813,7 +818,7 @@ CLASES DISPONIBLES:
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
if self.debug:
|
if self.debug:
|
||||||
print(f"DEBUG: Error en get_class_display_name_dynamic: {e}")
|
self.logger.debug(f"Error en get_class_display_name_dynamic: {e}")
|
||||||
|
|
||||||
# Fallback a lógica existente para tipos nativos
|
# Fallback a lógica existente para tipos nativos
|
||||||
if isinstance(obj, sympy.logic.boolalg.BooleanAtom):
|
if isinstance(obj, sympy.logic.boolalg.BooleanAtom):
|
||||||
|
@ -933,7 +938,7 @@ CLASES DISPONIBLES:
|
||||||
|
|
||||||
self.input_text.delete("1.0", tk.END)
|
self.input_text.delete("1.0", tk.END)
|
||||||
self.input_text.insert("1.0", content)
|
self.input_text.insert("1.0", content)
|
||||||
self._evaluate_and_update()
|
self._process_input_and_adjust_layout()
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
messagebox.showerror("Error", f"No se pudo cargar el archivo:\n{e}")
|
messagebox.showerror("Error", f"No se pudo cargar el archivo:\n{e}")
|
||||||
|
@ -1203,9 +1208,9 @@ programación y análisis numérico.
|
||||||
self.input_text.insert("1.0", content)
|
self.input_text.insert("1.0", content)
|
||||||
# Hacer evaluación inicial para mostrar resultados del historial
|
# Hacer evaluación inicial para mostrar resultados del historial
|
||||||
# Esto mantiene el comportamiento de contexto limpio pero muestra resultados
|
# Esto mantiene el comportamiento de contexto limpio pero muestra resultados
|
||||||
self.root.after_idle(self._evaluate_and_update)
|
self.root.after_idle(self._process_input_and_adjust_layout)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error cargando historial: {e}")
|
self.logger.error(f"Error cargando historial: {e}", exc_info=True)
|
||||||
|
|
||||||
def save_history(self):
|
def save_history(self):
|
||||||
"""Guarda historial de entrada"""
|
"""Guarda historial de entrada"""
|
||||||
|
@ -1217,7 +1222,7 @@ programación y análisis numérico.
|
||||||
elif os.path.exists(self.HISTORY_FILE):
|
elif os.path.exists(self.HISTORY_FILE):
|
||||||
os.remove(self.HISTORY_FILE)
|
os.remove(self.HISTORY_FILE)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error guardando historial: {e}")
|
self.logger.error(f"Error guardando historial: {e}", exc_info=True)
|
||||||
|
|
||||||
def on_close(self):
|
def on_close(self):
|
||||||
"""Maneja cierre de la aplicación"""
|
"""Maneja cierre de la aplicación"""
|
||||||
|
@ -1336,7 +1341,7 @@ programación y análisis numérico.
|
||||||
|
|
||||||
html_viewer.pack(padx=0, pady=0, fill=tk.BOTH, expand=True)
|
html_viewer.pack(padx=0, pady=0, fill=tk.BOTH, expand=True)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Error al renderizar Markdown a HTML: {e}")
|
self.logger.error(f"Error al renderizar Markdown a HTML: {e}", exc_info=True)
|
||||||
# Fallback to text if HTML fails
|
# Fallback to text if HTML fails
|
||||||
self._show_text_help(help_win, readme_content)
|
self._show_text_help(help_win, readme_content)
|
||||||
else:
|
else:
|
||||||
|
@ -1419,7 +1424,7 @@ Para crear un archivo de ayuda personalizado, cree un archivo `readme.md` en el
|
||||||
if ayuda:
|
if ayuda:
|
||||||
return ayuda
|
return ayuda
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"DEBUG: Error en helper: {e}")
|
self.logger.debug(f"Error en helper: {e}")
|
||||||
continue
|
continue
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@ -1460,6 +1465,123 @@ Para crear un archivo de ayuda personalizado, cree un archivo `readme.md` en el
|
||||||
|
|
||||||
return f"{mode}{numeric_indicator}{fractions_indicator}"
|
return f"{mode}{numeric_indicator}{fractions_indicator}"
|
||||||
|
|
||||||
|
def _get_input_font(self):
|
||||||
|
"""Obtiene o crea y cachea el objeto tk.Font para el panel de entrada."""
|
||||||
|
if not self._cached_input_font:
|
||||||
|
# Asume la fuente configurada en create_widgets: ("Consolas", 11)
|
||||||
|
self._cached_input_font = tkFont.Font(family="Consolas", size=11)
|
||||||
|
return self._cached_input_font
|
||||||
|
|
||||||
|
def _adjust_input_pane_width(self):
|
||||||
|
"""Ajusta el ancho del panel de entrada según su contenido."""
|
||||||
|
if not hasattr(self, 'paned_window') or not self.paned_window.winfo_exists():
|
||||||
|
return
|
||||||
|
|
||||||
|
# Esperar a que la ventana tenga un tamaño válido
|
||||||
|
if self.paned_window.winfo_width() <= 1:
|
||||||
|
return # Se reintentará en la siguiente llamada (ej. por KeyRelease)
|
||||||
|
|
||||||
|
# Obtener contenido excluyendo el último newline automático del widget Text
|
||||||
|
input_content = self.input_text.get("1.0", f"{tk.END}-1c")
|
||||||
|
lines = input_content.splitlines()
|
||||||
|
input_font = self._get_input_font()
|
||||||
|
|
||||||
|
max_pixel_width = 0
|
||||||
|
if not input_content.strip(): # Si está vacío o solo espacios en blanco
|
||||||
|
max_pixel_width = 5 # Ancho mínimo para el cursor o como placeholder
|
||||||
|
else:
|
||||||
|
for line in lines:
|
||||||
|
measured_width = input_font.measure(line) if line.strip() else input_font.measure(" ")
|
||||||
|
if measured_width > max_pixel_width:
|
||||||
|
max_pixel_width = measured_width
|
||||||
|
|
||||||
|
padding = 40 # Relleno para barra de desplazamiento, márgenes, etc.
|
||||||
|
width_needed_by_text = max_pixel_width + padding
|
||||||
|
|
||||||
|
# Debugging opcional (descomenta si necesitas depurar)
|
||||||
|
if self.debug:
|
||||||
|
self.logger.debug(f"--- Adjusting Input Pane ---")
|
||||||
|
self.logger.debug(f"Input content: '{input_content[:50]}...'")
|
||||||
|
self.logger.debug(f"Max pixel width of text: {max_pixel_width}")
|
||||||
|
self.logger.debug(f"Width needed by text (max_pixel_width + padding): {width_needed_by_text}")
|
||||||
|
|
||||||
|
min_input_pane_width = 200 # Definido en create_widgets
|
||||||
|
min_output_pane_width = 200 # Definido en create_widgets
|
||||||
|
total_width = self.paned_window.winfo_width()
|
||||||
|
|
||||||
|
current_sash_pos = 0
|
||||||
|
try:
|
||||||
|
sash_coords = self.paned_window.sash_coord(0)
|
||||||
|
if sash_coords:
|
||||||
|
current_sash_pos = sash_coords[0]
|
||||||
|
else:
|
||||||
|
if self.debug:
|
||||||
|
self.logger.debug("Could not get sash_coord.")
|
||||||
|
return
|
||||||
|
except tk.TclError:
|
||||||
|
if self.debug:
|
||||||
|
self.logger.debug("TclError getting sash_coord.")
|
||||||
|
return
|
||||||
|
|
||||||
|
if self.debug:
|
||||||
|
self.logger.debug(f"Current sash position (input pane width): {current_sash_pos}")
|
||||||
|
|
||||||
|
if width_needed_by_text > current_sash_pos:
|
||||||
|
if self.debug:
|
||||||
|
self.logger.debug(f"Condition MET: Text needs more space ({width_needed_by_text} > {current_sash_pos})")
|
||||||
|
new_input_width = width_needed_by_text # Punto de partida
|
||||||
|
|
||||||
|
# Asegurar que el nuevo ancho no sea menor que el mínimo del panel de entrada
|
||||||
|
new_input_width = max(new_input_width, min_input_pane_width)
|
||||||
|
|
||||||
|
# Asegurar que el panel de salida conserve su espacio mínimo
|
||||||
|
if total_width - new_input_width < min_output_pane_width:
|
||||||
|
new_input_width = total_width - min_output_pane_width
|
||||||
|
new_input_width = max(new_input_width, min_input_pane_width) # Re-verificar mínimo del input
|
||||||
|
|
||||||
|
# Aplicar un ratio máximo para el panel de entrada
|
||||||
|
max_input_ratio = 0.75 # Podría ser una constante de clase
|
||||||
|
max_width_by_ratio = int(total_width * max_input_ratio)
|
||||||
|
|
||||||
|
if new_input_width > max_width_by_ratio:
|
||||||
|
if max_width_by_ratio >= min_input_pane_width and \
|
||||||
|
(total_width - max_width_by_ratio) >= min_output_pane_width:
|
||||||
|
new_input_width = max_width_by_ratio
|
||||||
|
|
||||||
|
final_new_input_width = max(0, int(new_input_width)) # No debe ser negativo
|
||||||
|
if self.debug:
|
||||||
|
self.logger.debug(f"Calculated final new input width: {final_new_input_width}")
|
||||||
|
|
||||||
|
# Mover el sash solo si el nuevo ancho es significativamente mayor que el actual (umbral ajustado)
|
||||||
|
sash_adjustment_threshold = 3 # Píxeles
|
||||||
|
if final_new_input_width > current_sash_pos and \
|
||||||
|
(final_new_input_width - current_sash_pos) >= sash_adjustment_threshold:
|
||||||
|
if self.debug:
|
||||||
|
self.logger.debug(f"Condition MET for sash_place: New width {final_new_input_width} is significantly larger (diff >= {sash_adjustment_threshold}).")
|
||||||
|
try:
|
||||||
|
if self.paned_window.winfo_exists() and total_width >= (min_input_pane_width + min_output_pane_width):
|
||||||
|
self.paned_window.sash_place(0, final_new_input_width, 0) # Añadido el argumento y=0
|
||||||
|
if self.debug:
|
||||||
|
self.logger.debug(f"Sash placed at: {final_new_input_width}")
|
||||||
|
elif self.debug:
|
||||||
|
self.logger.debug("Paned window not ready or total width too small for sash_place.")
|
||||||
|
except tk.TclError as e_sash:
|
||||||
|
if self.debug:
|
||||||
|
self.logger.debug(f"TclError during sash_place: {e_sash}")
|
||||||
|
pass
|
||||||
|
elif self.debug:
|
||||||
|
self.logger.debug(f"Condition NOT MET for sash_place: final_new_input_width ({final_new_input_width}) vs current_sash_pos ({current_sash_pos}) or threshold ({sash_adjustment_threshold}).")
|
||||||
|
elif self.debug:
|
||||||
|
self.logger.debug(f"Condition NOT MET: Text does not need more space ({width_needed_by_text} <= {current_sash_pos})")
|
||||||
|
|
||||||
|
if self.debug:
|
||||||
|
self.logger.debug(f"--- End Adjusting Input Pane ---")
|
||||||
|
|
||||||
|
def _process_input_and_adjust_layout(self):
|
||||||
|
"""Evalúa todas las líneas y luego ajusta el ancho del panel de entrada."""
|
||||||
|
self._evaluate_and_update()
|
||||||
|
self._adjust_input_pane_width()
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
"""Función principal"""
|
"""Función principal"""
|
||||||
|
|
|
@ -343,10 +343,10 @@ class HybridEvaluationEngine:
|
||||||
try:
|
try:
|
||||||
numeric_eval = result.evalf()
|
numeric_eval = result.evalf()
|
||||||
# Solo mostrar evaluación numérica si es diferente del resultado simbólico
|
# Solo mostrar evaluación numérica si es diferente del resultado simbólico
|
||||||
if numeric_eval != result and not (
|
if (str(numeric_eval) != str(result) and numeric_eval != result and
|
||||||
hasattr(result, 'is_number') and result.is_number and
|
not (isinstance(result, (int, float)) or
|
||||||
abs(float(numeric_eval) - float(result)) < 1e-15
|
(hasattr(result, 'is_number') and result.is_number and
|
||||||
):
|
hasattr(result, 'is_Integer') and result.is_Integer))):
|
||||||
numeric_result = numeric_eval
|
numeric_result = numeric_eval
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
|
Loading…
Reference in New Issue