Optimizacion y cambios masivos en la app IMPORTANTE falta implementar el READ.ME y la carpeta de msuica para el reproductor y cambiar el nombre de las solapas
This commit is contained in:
commit
1d4908840f
|
@ -0,0 +1,8 @@
|
||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="Python 3.13 (ProyectoFinalProcesosServicios)" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="Python 3.13 (ProyectoFinalProcesosServicios)" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/ProyectoFinalProcesosServicios.iml" filepath="$PROJECT_DIR$/.idea/ProyectoFinalProcesosServicios.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
|
@ -0,0 +1,194 @@
|
||||||
|
import tkinter as tk
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
|
from tkinter import Menu # Importar el widget Menu
|
||||||
|
from tkinter import ttk # Importar el widget ttk
|
||||||
|
|
||||||
|
from hilos.ChatWidget import ChatWidget
|
||||||
|
from hilos.MusicPlayer import MusicPlayer
|
||||||
|
from hilos.WeatherWidget import WeatherWidget
|
||||||
|
from hilos.SystemMonitor import SystemMonitor
|
||||||
|
from hilos.ApplicationLauncher import ApplicationLauncher
|
||||||
|
from hilos.LanguageChart import LanguageChart
|
||||||
|
from solapas.MusicDownloader import MusicDownloader
|
||||||
|
from solapas.EconomyBitcoinChart import EconomyBitcoinChart
|
||||||
|
from solapas.SQLQueryExecutor import SQLQueryExecutor
|
||||||
|
from solapas.TicTacToe import TicTacToe
|
||||||
|
from solapas.WebScraperToDB import WebScraperToDB
|
||||||
|
|
||||||
|
# Clave de API de OpenWeatherMap
|
||||||
|
API_KEY = "1fa8fd05b650773bbc3f2130657e808a"
|
||||||
|
|
||||||
|
def update_time(status_bar):
|
||||||
|
"""Función que actualiza la hora y el día de la semana en un label"""
|
||||||
|
while True:
|
||||||
|
# Obtener la fecha y hora actual
|
||||||
|
now = datetime.datetime.now()
|
||||||
|
day_of_week = now.strftime("%A") # Día de la semana
|
||||||
|
time_str = now.strftime("%H:%M:%S") # Hora en formato HH:MM:SS
|
||||||
|
date_str = now.strftime("%Y-%m-%d") # Fecha en formato YYYY-MM-DD
|
||||||
|
label_text = f"{day_of_week}, {date_str} - {time_str}"
|
||||||
|
|
||||||
|
# Actualizar el label (debemos usar `after` para asegurarnos que se actualice en el hilo principal de Tkinter)
|
||||||
|
label_fecha_hora.after(1000, status_bar.config, {"text": label_text})
|
||||||
|
|
||||||
|
# Espera 1 segundo antes de actualizar de nuevo
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
# Crear la ventana principal
|
||||||
|
root = tk.Tk()
|
||||||
|
root.title("Ventana Responsive")
|
||||||
|
root.geometry("1000x700") # Tamaño inicial
|
||||||
|
|
||||||
|
# Configurar la ventana principal para que sea responsive
|
||||||
|
root.columnconfigure(0, weight=0) # Columna izquierda, tamaño fijo
|
||||||
|
root.columnconfigure(1, weight=1) # Columna central, tamaño variable
|
||||||
|
root.columnconfigure(2, weight=0) # Columna derecha, tamaño fijo
|
||||||
|
root.rowconfigure(0, weight=1) # Fila principal, tamaño variable
|
||||||
|
root.rowconfigure(1, weight=0) # Barra de estado, tamaño fijo
|
||||||
|
|
||||||
|
# Crear el menú superior
|
||||||
|
menu_bar = Menu(root)
|
||||||
|
|
||||||
|
file_menu = Menu(menu_bar, tearoff=0)
|
||||||
|
file_menu.add_command(label="Nuevo")
|
||||||
|
file_menu.add_command(label="Abrir")
|
||||||
|
file_menu.add_separator()
|
||||||
|
file_menu.add_command(label="Salir", command=root.quit)
|
||||||
|
|
||||||
|
edit_menu = Menu(menu_bar, tearoff=0)
|
||||||
|
edit_menu.add_command(label="Copiar")
|
||||||
|
edit_menu.add_command(label="Pegar")
|
||||||
|
|
||||||
|
help_menu = Menu(menu_bar, tearoff=0)
|
||||||
|
help_menu.add_command(label="Acerca de")
|
||||||
|
|
||||||
|
menu_bar.add_cascade(label="Archivo", menu=file_menu)
|
||||||
|
menu_bar.add_cascade(label="Editar", menu=edit_menu)
|
||||||
|
menu_bar.add_cascade(label="Ayuda", menu=help_menu)
|
||||||
|
|
||||||
|
root.config(menu=menu_bar)
|
||||||
|
|
||||||
|
# Crear los frames laterales y el central
|
||||||
|
frame_izquierdo = tk.Frame(root, bg="lightblue", width=150)
|
||||||
|
frame_central = tk.Frame(root, bg="white")
|
||||||
|
frame_derecho = tk.Frame(root, bg="lightgreen", width=150)
|
||||||
|
|
||||||
|
# Colocar los frames laterales y el central
|
||||||
|
frame_izquierdo.grid(row=0, column=0, sticky="ns")
|
||||||
|
frame_central.grid(row=0, column=1, sticky="nsew")
|
||||||
|
frame_derecho.grid(row=0, column=2, sticky="ns")
|
||||||
|
|
||||||
|
# Configurar los tamaños fijos de los frames laterales
|
||||||
|
frame_izquierdo.grid_propagate(False)
|
||||||
|
frame_derecho.grid_propagate(False)
|
||||||
|
|
||||||
|
# Integrar el widget del clima en el panel izquierdo
|
||||||
|
weather_widget = WeatherWidget(frame_izquierdo, API_KEY)
|
||||||
|
|
||||||
|
# Añadir el lanzador de aplicaciones al panel izquierdo
|
||||||
|
app_launcher = ApplicationLauncher(frame_izquierdo)
|
||||||
|
|
||||||
|
# Añadir gráfico de lenguajes al panel izquierdo
|
||||||
|
language_chart = LanguageChart(frame_izquierdo)
|
||||||
|
|
||||||
|
# Crear el widget de Chat en el panel derecho con más espacio
|
||||||
|
chat_widget = ChatWidget(frame_derecho)
|
||||||
|
|
||||||
|
# Agregar el reproductor de música al panel derecho, en la parte inferior
|
||||||
|
music_player = MusicPlayer(frame_derecho)
|
||||||
|
|
||||||
|
# Dividir el frame central en dos partes (superior variable e inferior fija)
|
||||||
|
frame_central.rowconfigure(0, weight=1) # Parte superior, tamaño variable
|
||||||
|
frame_central.rowconfigure(1, weight=0) # Parte inferior, tamaño fijo
|
||||||
|
frame_central.columnconfigure(0, weight=1) # Ocupa toda la anchura
|
||||||
|
|
||||||
|
# Crear subframes dentro del frame central
|
||||||
|
frame_superior = tk.Frame(frame_central, bg="lightyellow")
|
||||||
|
frame_inferior = tk.Frame(frame_central, bg="lightgray", height=100)
|
||||||
|
|
||||||
|
# Colocar los subframes dentro del frame central
|
||||||
|
frame_superior.grid(row=0, column=0, sticky="nsew")
|
||||||
|
frame_inferior.grid(row=1, column=0, sticky="ew")
|
||||||
|
|
||||||
|
# Fijar el tamaño de la parte inferior
|
||||||
|
frame_inferior.grid_propagate(False)
|
||||||
|
|
||||||
|
# Crear un evento de parada
|
||||||
|
stop_event = threading.Event()
|
||||||
|
|
||||||
|
# Definir el manejador para el cierre de la ventana
|
||||||
|
def on_closing():
|
||||||
|
"""Cerrar correctamente la aplicación."""
|
||||||
|
stop_event.set() # Detener los hilos
|
||||||
|
root.destroy() # Destruir la ventana principal
|
||||||
|
|
||||||
|
# Configurar el manejador de cierre
|
||||||
|
root.protocol("WM_DELETE_WINDOW", on_closing)
|
||||||
|
|
||||||
|
# Crear la barra de estado
|
||||||
|
barra_estado = tk.Label(root, text="Barra de estado", bg="lightgray", anchor="w")
|
||||||
|
barra_estado.grid(row=1, column=0, columnspan=3, sticky="ew")
|
||||||
|
|
||||||
|
# Inicializar el monitor del sistema
|
||||||
|
system_monitor = SystemMonitor(barra_estado, stop_event)
|
||||||
|
|
||||||
|
# Notebook para las pestañas
|
||||||
|
style = ttk.Style()
|
||||||
|
style.configure("CustomNotebook.TNotebook.Tab", font=("Arial", 12, "bold"))
|
||||||
|
notebook = ttk.Notebook(frame_superior, style="CustomNotebook.TNotebook")
|
||||||
|
notebook.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
# Crear la Solapa 1 y añadir el downloader
|
||||||
|
tab1 = ttk.Frame(notebook)
|
||||||
|
notebook.add(tab1, text="Solapa 1", padding=4)
|
||||||
|
|
||||||
|
# Añadir el downloader a la Solapa 1
|
||||||
|
music_downloader = MusicDownloader(tab1)
|
||||||
|
|
||||||
|
# Crear la Solapa 2 y añadir los gráficos
|
||||||
|
tab2 = ttk.Frame(notebook)
|
||||||
|
notebook.add(tab2, text="Solapa 2", padding=4)
|
||||||
|
|
||||||
|
# Añadir los gráficos de economía mundial y Bitcoin a la Solapa 2
|
||||||
|
economy_bitcoin_chart = EconomyBitcoinChart(tab2)
|
||||||
|
|
||||||
|
# Crear la Solapa 3 y añadir el Tic Tac Toe
|
||||||
|
tab3 = ttk.Frame(notebook)
|
||||||
|
notebook.add(tab3, text="Solapa 3", padding=4)
|
||||||
|
|
||||||
|
# Añadir el juego de Tic Tac Toe a la Solapa 3
|
||||||
|
tic_tac_toe = TicTacToe(tab3)
|
||||||
|
|
||||||
|
# Crear la Solapa 4 y añadir el SQL Query Executor
|
||||||
|
tab4 = ttk.Frame(notebook)
|
||||||
|
notebook.add(tab4, text="Solapa 4", padding=4)
|
||||||
|
|
||||||
|
# Añadir el ejecutor de consultas SQL a la Solapa 4
|
||||||
|
sql_query_executor = SQLQueryExecutor(tab4)
|
||||||
|
|
||||||
|
# Crear la Solapa 5 y añadir el Web Scraper
|
||||||
|
tab5 = ttk.Frame(notebook)
|
||||||
|
notebook.add(tab5, text="Solapa 5", padding=4)
|
||||||
|
|
||||||
|
# Añadir el widget de Web Scraper a la Solapa 5
|
||||||
|
web_scraper = WebScraperToDB(tab5)
|
||||||
|
|
||||||
|
# Barra de estado
|
||||||
|
# Dividir la barra de estado en 4 labels
|
||||||
|
|
||||||
|
# Usar pack para alinear los labels horizontalmente
|
||||||
|
label_fecha_hora = tk.Label(barra_estado, text="Hilo fecha-hora", font=("Helvetica", 14), bd=1, fg="blue", relief="sunken", anchor="w", width=20, padx=10)
|
||||||
|
|
||||||
|
|
||||||
|
label_fecha_hora.pack(side="right", fill="x", expand=True)
|
||||||
|
# barra_estado.grid(row=1, column=0, columnspan=3, sticky="ew")
|
||||||
|
|
||||||
|
|
||||||
|
update_thread = threading.Thread(target=update_time, args=(label_fecha_hora,))
|
||||||
|
update_thread.daemon = True # Hacemos el hilo un demonio para que termine con la app
|
||||||
|
update_thread.start()
|
||||||
|
|
||||||
|
# Ejecución de la aplicación
|
||||||
|
root.mainloop()
|
|
@ -0,0 +1,95 @@
|
||||||
|
import tkinter as tk
|
||||||
|
import threading
|
||||||
|
import subprocess
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
class ApplicationLauncher:
|
||||||
|
def __init__(self, parent):
|
||||||
|
"""
|
||||||
|
Inicializa los botones para lanzar aplicaciones con detección automática de rutas.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent (tk.Frame): Frame donde se colocarán los botones.
|
||||||
|
"""
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
# Detectar rutas automáticamente
|
||||||
|
self.vscode_path = self.detect_path(["C:\\Program Files\\Microsoft VS Code\\Code.exe",
|
||||||
|
"C:\\Users\\%USERNAME%\\AppData\\Local\\Programs\\Microsoft VS Code\\Code.exe"])
|
||||||
|
self.eclipse_path = self.detect_path(["C:\\eclipse\\eclipse.exe",
|
||||||
|
"C:\\Program Files\\Eclipse Foundation\\eclipse.exe"])
|
||||||
|
self.pycharm_path = self.detect_path(["C:\\Program Files\\JetBrains\\PyCharm\\bin\\pycharm64.exe",
|
||||||
|
"C:\\Program Files\\JetBrains\\PyCharm Community Edition 2023.1.4\\bin\\pycharm64.exe"])
|
||||||
|
|
||||||
|
# Título para el grupo de botones
|
||||||
|
title = tk.Label(self.parent, text="Aplicaciones", font=("Helvetica", 14, "bold"), bg="lightblue")
|
||||||
|
title.pack(pady=10)
|
||||||
|
|
||||||
|
# Botón para abrir Visual Studio Code
|
||||||
|
self.vscode_button = tk.Button(self.parent, text="Visual Code", command=self.launch_vscode, bg="lightgreen",
|
||||||
|
font=("Helvetica", 10))
|
||||||
|
self.vscode_button.pack(fill="x", pady=2)
|
||||||
|
|
||||||
|
# Botón para abrir Eclipse
|
||||||
|
self.eclipse_button = tk.Button(self.parent, text="Eclipse", command=self.launch_eclipse, bg="lightgreen",
|
||||||
|
font=("Helvetica", 10))
|
||||||
|
self.eclipse_button.pack(fill="x", pady=2)
|
||||||
|
|
||||||
|
# Botón para abrir PyCharm
|
||||||
|
self.pycharm_button = tk.Button(self.parent, text="PyCharm", command=self.launch_pycharm, bg="lightgreen",
|
||||||
|
font=("Helvetica", 10))
|
||||||
|
self.pycharm_button.pack(fill="x", pady=2)
|
||||||
|
|
||||||
|
def detect_path(self, paths):
|
||||||
|
"""
|
||||||
|
Detecta automáticamente la primera ruta existente de una lista de posibles rutas.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
paths (list): Lista de rutas posibles para un ejecutable.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
str: La primera ruta válida encontrada, o None si no se encuentra ninguna.
|
||||||
|
"""
|
||||||
|
for path in paths:
|
||||||
|
path = os.path.expandvars(path) # Expande variables como %USERNAME%
|
||||||
|
if os.path.exists(path):
|
||||||
|
return path
|
||||||
|
return None
|
||||||
|
|
||||||
|
def launch_vscode(self):
|
||||||
|
"""Lanza Visual Studio Code si se encuentra la ruta."""
|
||||||
|
self.launch_application(self.vscode_path, "Visual Studio Code")
|
||||||
|
|
||||||
|
def launch_eclipse(self):
|
||||||
|
"""Lanza Eclipse si se encuentra la ruta."""
|
||||||
|
self.launch_application(self.eclipse_path, "Eclipse")
|
||||||
|
|
||||||
|
def launch_pycharm(self):
|
||||||
|
"""Lanza PyCharm si se encuentra la ruta."""
|
||||||
|
self.launch_application(self.pycharm_path, "PyCharm")
|
||||||
|
|
||||||
|
def launch_application(self, path, name):
|
||||||
|
"""
|
||||||
|
Lanza una aplicación si la ruta es válida.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
path (str): Ruta al ejecutable.
|
||||||
|
name (str): Nombre de la aplicación (para mensajes de error).
|
||||||
|
"""
|
||||||
|
if path:
|
||||||
|
threading.Thread(target=self.run_command, args=([path],), daemon=True).start()
|
||||||
|
else:
|
||||||
|
print(f"No se encontró {name}. Por favor, instálalo o configura la ruta.")
|
||||||
|
|
||||||
|
def run_command(self, command):
|
||||||
|
"""
|
||||||
|
Ejecuta un comando del sistema operativo para abrir una aplicación.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command (list): Comando a ejecutar (lista de argumentos).
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
subprocess.run(command, check=True)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error al intentar abrir la aplicación: {e}")
|
|
@ -0,0 +1,74 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import scrolledtext
|
||||||
|
import socket
|
||||||
|
import threading
|
||||||
|
|
||||||
|
|
||||||
|
class ChatWidget:
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.parent = parent
|
||||||
|
self.frame = tk.Frame(self.parent, bg="lightgreen", width=200, height=300) # Ajustar tamaño del frame
|
||||||
|
self.frame.pack(fill="x", expand=False, padx=10, pady=10)
|
||||||
|
|
||||||
|
# Label superior
|
||||||
|
self.label = tk.Label(self.frame, text="Chat", font=("Arial", 14, "bold"), fg="red", bg="lightgreen")
|
||||||
|
self.label.pack(pady=5)
|
||||||
|
|
||||||
|
# Caja de texto para los mensajes
|
||||||
|
self.chat_display = scrolledtext.ScrolledText(
|
||||||
|
self.frame, wrap=tk.WORD, state="disabled", width=40, height=10 # Reducir dimensiones
|
||||||
|
)
|
||||||
|
self.chat_display.pack(pady=5)
|
||||||
|
|
||||||
|
# Campo de entrada para escribir mensajes
|
||||||
|
self.message_entry = tk.Entry(self.frame, width=35) # Reducir ancho
|
||||||
|
self.message_entry.pack(pady=5)
|
||||||
|
self.message_entry.bind("<Return>", self.send_message)
|
||||||
|
|
||||||
|
# Botón para enviar mensajes
|
||||||
|
self.send_button = tk.Button(self.frame, text="Enviar", command=self.send_message, width=10) # Reducir tamaño
|
||||||
|
self.send_button.pack(pady=5)
|
||||||
|
|
||||||
|
# Configuración del cliente socket
|
||||||
|
self.client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||||
|
self.server_address = ("127.0.0.1", 3333) # Cambiar a la IP del servidor si es necesario
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.client_socket.connect(self.server_address)
|
||||||
|
threading.Thread(target=self.receive_messages, daemon=True).start()
|
||||||
|
except Exception as e:
|
||||||
|
self.display_message(f"[ERROR] No se pudo conectar al servidor: {e}")
|
||||||
|
|
||||||
|
def send_message(self, event=None):
|
||||||
|
message = self.message_entry.get()
|
||||||
|
if message:
|
||||||
|
try:
|
||||||
|
self.client_socket.send(message.encode("utf-8"))
|
||||||
|
self.message_entry.delete(0, tk.END)
|
||||||
|
except Exception as e:
|
||||||
|
self.display_message(f"[ERROR] No se pudo enviar el mensaje: {e}")
|
||||||
|
|
||||||
|
def receive_messages(self):
|
||||||
|
"""Recibe mensajes del servidor y los muestra en el chat."""
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
message = self.client_socket.recv(1024).decode("utf-8")
|
||||||
|
if message:
|
||||||
|
self.display_message(message) # Mostrar mensaje en la caja de chat
|
||||||
|
else:
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
self.display_message("[DESCONECTADO] Conexión perdida con el servidor.")
|
||||||
|
break
|
||||||
|
|
||||||
|
def display_message(self, message):
|
||||||
|
self.chat_display.config(state="normal")
|
||||||
|
self.chat_display.insert(tk.END, message + "\n")
|
||||||
|
self.chat_display.config(state="disabled")
|
||||||
|
self.chat_display.see(tk.END)
|
||||||
|
|
||||||
|
def close_connection(self):
|
||||||
|
try:
|
||||||
|
self.client_socket.close()
|
||||||
|
except:
|
||||||
|
pass
|
|
@ -0,0 +1,57 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from matplotlib.figure import Figure
|
||||||
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
|
||||||
|
class LanguageChart:
|
||||||
|
def __init__(self, parent):
|
||||||
|
"""
|
||||||
|
Inicializa el gráfico de los lenguajes de programación más usados.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent (tk.Frame): Frame donde se colocará el gráfico.
|
||||||
|
"""
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
# Datos iniciales (puedes actualizar esto dinámicamente)
|
||||||
|
self.languages = ["Python", "JavaScript", "Java", "C++", "C#"]
|
||||||
|
self.usage = [30, 25, 20, 15, 10] # Porcentajes de uso
|
||||||
|
|
||||||
|
# Crear figura para el gráfico
|
||||||
|
self.figure = Figure(figsize=(4, 3), dpi=100)
|
||||||
|
self.ax = self.figure.add_subplot(111)
|
||||||
|
self.ax.bar(self.languages, self.usage, color="skyblue")
|
||||||
|
self.ax.set_title("Lenguajes más usados")
|
||||||
|
self.ax.set_ylabel("Porcentaje de uso")
|
||||||
|
|
||||||
|
# Embebiendo el gráfico en Tkinter
|
||||||
|
self.canvas = FigureCanvasTkAgg(self.figure, master=self.parent)
|
||||||
|
self.canvas.get_tk_widget().pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
# Iniciar hilo para actualizar el gráfico
|
||||||
|
threading.Thread(target=self.update_chart, daemon=True).start()
|
||||||
|
|
||||||
|
def fetch_data(self):
|
||||||
|
"""
|
||||||
|
Simula la obtención de datos actualizados de lenguajes de programación.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list: Lista de nuevos porcentajes de uso.
|
||||||
|
"""
|
||||||
|
# Simulación: aquí puedes conectar a una API real
|
||||||
|
self.usage = [value + 1 if value < 50 else value - 10 for value in self.usage]
|
||||||
|
time.sleep(5) # Simular retraso de actualización
|
||||||
|
|
||||||
|
def update_chart(self):
|
||||||
|
"""
|
||||||
|
Actualiza el gráfico periódicamente en un hilo.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
self.fetch_data()
|
||||||
|
self.ax.clear()
|
||||||
|
self.ax.bar(self.languages, self.usage, color="skyblue")
|
||||||
|
self.ax.set_title("Lenguajes más usados")
|
||||||
|
self.ax.set_ylabel("Porcentaje de uso")
|
||||||
|
self.canvas.draw()
|
||||||
|
time.sleep(5) # Actualizar cada 5 segundos
|
|
@ -0,0 +1,77 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import filedialog
|
||||||
|
import threading
|
||||||
|
import pygame # Necesitas instalar pygame: pip install pygame
|
||||||
|
|
||||||
|
|
||||||
|
class MusicPlayer:
|
||||||
|
def __init__(self, parent):
|
||||||
|
self.parent = parent
|
||||||
|
self.is_playing = False
|
||||||
|
|
||||||
|
# Inicializar el reproductor de música
|
||||||
|
pygame.mixer.init()
|
||||||
|
|
||||||
|
# Crear marco para el reproductor
|
||||||
|
self.frame = tk.Frame(self.parent, bg="lightgreen", width=200, height=100)
|
||||||
|
self.frame.pack(side="bottom", padx=10, pady=10, fill="both", expand=False)
|
||||||
|
|
||||||
|
# Etiqueta de título
|
||||||
|
self.title_label = tk.Label(
|
||||||
|
self.frame, text="Reproductor de Música", font=("Arial", 12, "bold"), bg="lightgreen"
|
||||||
|
)
|
||||||
|
self.title_label.pack(pady=5)
|
||||||
|
|
||||||
|
# Botón para seleccionar archivo
|
||||||
|
self.select_button = tk.Button(
|
||||||
|
self.frame, text="Seleccionar Archivo", command=self.select_file, width=20
|
||||||
|
)
|
||||||
|
self.select_button.pack(pady=5)
|
||||||
|
|
||||||
|
# Crear un marco para los botones de control
|
||||||
|
self.controls_frame = tk.Frame(self.frame, bg="lightgreen")
|
||||||
|
self.controls_frame.pack(pady=10)
|
||||||
|
|
||||||
|
# Botones de control (centrados)
|
||||||
|
self.play_button = tk.Button(
|
||||||
|
self.controls_frame, text="▶ Reproducir", command=self.play_music, width=12
|
||||||
|
)
|
||||||
|
self.play_button.grid(row=0, column=0, padx=5)
|
||||||
|
|
||||||
|
self.stop_button = tk.Button(
|
||||||
|
self.controls_frame, text="■ Detener", command=self.stop_music, state="disabled", width=12
|
||||||
|
)
|
||||||
|
self.stop_button.grid(row=0, column=1, padx=5)
|
||||||
|
|
||||||
|
def select_file(self):
|
||||||
|
"""Abrir el selector de archivos para elegir un archivo de música."""
|
||||||
|
self.music_file = filedialog.askopenfilename(
|
||||||
|
filetypes=[("Archivos de audio", "*.mp3 *.wav"), ("Todos los archivos", "*.*")]
|
||||||
|
)
|
||||||
|
if self.music_file:
|
||||||
|
self.title_label.config(text=f"Archivo: {self.music_file.split('/')[-1]}")
|
||||||
|
|
||||||
|
def play_music(self):
|
||||||
|
"""Iniciar la reproducción de música."""
|
||||||
|
if hasattr(self, "music_file"):
|
||||||
|
self.is_playing = True
|
||||||
|
self.play_button.config(state="disabled")
|
||||||
|
self.stop_button.config(state="normal")
|
||||||
|
|
||||||
|
threading.Thread(target=self._play_music_thread, daemon=True).start()
|
||||||
|
|
||||||
|
def _play_music_thread(self):
|
||||||
|
"""Hilo que reproduce la música."""
|
||||||
|
pygame.mixer.music.load(self.music_file)
|
||||||
|
pygame.mixer.music.play()
|
||||||
|
while pygame.mixer.music.get_busy():
|
||||||
|
if not self.is_playing:
|
||||||
|
pygame.mixer.music.stop()
|
||||||
|
break
|
||||||
|
|
||||||
|
def stop_music(self):
|
||||||
|
"""Detener la reproducción de música."""
|
||||||
|
self.is_playing = False
|
||||||
|
self.play_button.config(state="normal")
|
||||||
|
self.stop_button.config(state="disabled")
|
||||||
|
pygame.mixer.music.stop()
|
|
@ -0,0 +1,69 @@
|
||||||
|
import psutil
|
||||||
|
import threading
|
||||||
|
import tkinter as tk
|
||||||
|
|
||||||
|
class SystemMonitor:
|
||||||
|
def __init__(self, parent, stop_event):
|
||||||
|
self.parent = parent
|
||||||
|
self.stop_event = stop_event
|
||||||
|
|
||||||
|
# Crear labels para cada métrica
|
||||||
|
self.cpu_label = tk.Label(parent, text="CPU: 0%", bg="lightgreen", font=("Helvetica", 12), relief="groove")
|
||||||
|
self.ram_label = tk.Label(parent, text="RAM: 0%", bg="lightcoral", font=("Helvetica", 12), relief="groove")
|
||||||
|
self.battery_label = tk.Label(parent, text="Battery: N/A", bg="lightblue", font=("Helvetica", 12), relief="groove")
|
||||||
|
self.network_label = tk.Label(parent, text="Net: N/A", bg="lightpink", font=("Helvetica", 12), relief="groove")
|
||||||
|
|
||||||
|
# Posicionar los labels
|
||||||
|
self.cpu_label.pack(side="left", fill="both", expand=True)
|
||||||
|
self.ram_label.pack(side="left", fill="both", expand=True)
|
||||||
|
self.battery_label.pack(side="left", fill="both", expand=True)
|
||||||
|
self.network_label.pack(side="left", fill="both", expand=True)
|
||||||
|
|
||||||
|
# Iniciar hilos
|
||||||
|
threading.Thread(target=self.update_cpu, daemon=True).start()
|
||||||
|
threading.Thread(target=self.update_ram, daemon=True).start()
|
||||||
|
threading.Thread(target=self.update_battery, daemon=True).start()
|
||||||
|
threading.Thread(target=self.update_network, daemon=True).start()
|
||||||
|
|
||||||
|
def update_cpu(self):
|
||||||
|
"""Actualizar el uso de CPU."""
|
||||||
|
while not self.stop_event.is_set():
|
||||||
|
cpu_usage = psutil.cpu_percent()
|
||||||
|
self.cpu_label.config(text=f"CPU: {cpu_usage}%")
|
||||||
|
self.cpu_label.after(1000, lambda: None) # Evitar bloqueo
|
||||||
|
self.stop_event.wait(1)
|
||||||
|
|
||||||
|
def update_ram(self):
|
||||||
|
"""Actualizar el uso de RAM."""
|
||||||
|
while not self.stop_event.is_set():
|
||||||
|
ram_usage = psutil.virtual_memory().percent
|
||||||
|
self.ram_label.config(text=f"RAM: {ram_usage}%")
|
||||||
|
self.ram_label.after(1000, lambda: None) # Evitar bloqueo
|
||||||
|
self.stop_event.wait(1)
|
||||||
|
|
||||||
|
def update_battery(self):
|
||||||
|
"""Actualizar el estado de la batería."""
|
||||||
|
while not self.stop_event.is_set():
|
||||||
|
battery = psutil.sensors_battery()
|
||||||
|
if battery:
|
||||||
|
percent = battery.percent
|
||||||
|
time_left = battery.secsleft // 3600 if battery.secsleft > 0 else "N/A"
|
||||||
|
self.battery_label.config(text=f"Battery: {percent}%, ({time_left}h left)")
|
||||||
|
else:
|
||||||
|
self.battery_label.config(text="Battery: N/A")
|
||||||
|
self.battery_label.after(1000, lambda: None) # Evitar bloqueo
|
||||||
|
self.stop_event.wait(5)
|
||||||
|
|
||||||
|
def update_network(self):
|
||||||
|
"""Actualizar el uso de red."""
|
||||||
|
old_sent = psutil.net_io_counters().bytes_sent
|
||||||
|
old_recv = psutil.net_io_counters().bytes_recv
|
||||||
|
while not self.stop_event.is_set():
|
||||||
|
new_sent = psutil.net_io_counters().bytes_sent
|
||||||
|
new_recv = psutil.net_io_counters().bytes_recv
|
||||||
|
sent_mb = (new_sent - old_sent) / (1024 * 1024)
|
||||||
|
recv_mb = (new_recv - old_recv) / (1024 * 1024)
|
||||||
|
self.network_label.config(text=f"Net: {sent_mb:.2f} MB sent, {recv_mb:.2f} MB recv")
|
||||||
|
old_sent, old_recv = new_sent, new_recv
|
||||||
|
self.network_label.after(1000, lambda: None) # Evitar bloqueo
|
||||||
|
self.stop_event.wait(1)
|
|
@ -0,0 +1,129 @@
|
||||||
|
import tkinter as tk
|
||||||
|
import threading
|
||||||
|
import requests
|
||||||
|
import time
|
||||||
|
|
||||||
|
|
||||||
|
class WeatherWidget:
|
||||||
|
def __init__(self, parent, api_key):
|
||||||
|
"""
|
||||||
|
Inicializa el widget del clima con detalles adicionales.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent (tk.Frame): Frame en el que se colocará el widget.
|
||||||
|
api_key (str): Clave de la API de OpenWeatherMap.
|
||||||
|
"""
|
||||||
|
self.parent = parent
|
||||||
|
self.api_key = api_key
|
||||||
|
|
||||||
|
# Crear un Frame para contener los datos
|
||||||
|
self.frame = tk.Frame(self.parent, bg="white", bd=2, relief="groove")
|
||||||
|
self.frame.pack(padx=10, pady=10, fill="x", anchor="n")
|
||||||
|
|
||||||
|
# Encabezado del clima
|
||||||
|
self.header_label = tk.Label(self.frame, text="Weather in ...", font=("Helvetica", 14, "bold"), bg="white")
|
||||||
|
self.header_label.pack(pady=5)
|
||||||
|
|
||||||
|
# Temperatura principal
|
||||||
|
self.temp_label = tk.Label(self.frame, text="--°C", font=("Helvetica", 28, "bold"), bg="white")
|
||||||
|
self.temp_label.pack()
|
||||||
|
|
||||||
|
# Detalles adicionales
|
||||||
|
self.details_label = tk.Label(self.frame, text="", font=("Helvetica", 12), bg="white", justify="left")
|
||||||
|
self.details_label.pack(pady=5)
|
||||||
|
|
||||||
|
# Iniciar el hilo para actualizar el clima
|
||||||
|
self.start_weather_updates()
|
||||||
|
|
||||||
|
def get_location(self):
|
||||||
|
"""
|
||||||
|
Obtiene la ubicación actual (latitud y longitud) usando ip-api.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
response = requests.get("http://ip-api.com/json/")
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
return data["lat"], data["lon"], data["city"]
|
||||||
|
except Exception as e:
|
||||||
|
return None, None, f"Error al obtener ubicación: {e}"
|
||||||
|
|
||||||
|
def get_weather(self, lat, lon):
|
||||||
|
"""
|
||||||
|
Obtiene el clima actual usando OpenWeatherMap.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lat (float): Latitud de la ubicación.
|
||||||
|
lon (float): Longitud de la ubicación.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
weather_url = f"http://api.openweathermap.org/data/2.5/weather?lat={lat}&lon={lon}&appid={self.api_key}&units=metric"
|
||||||
|
response = requests.get(weather_url)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
|
||||||
|
# Información principal
|
||||||
|
city = data["name"]
|
||||||
|
temp = data["main"]["temp"]
|
||||||
|
real_feel = data["main"]["feels_like"]
|
||||||
|
wind_speed = data["wind"]["speed"]
|
||||||
|
wind_gusts = data["wind"].get("gust", "N/A")
|
||||||
|
weather = data["weather"][0]["description"].capitalize()
|
||||||
|
|
||||||
|
# Obtener calidad del aire (Air Quality)
|
||||||
|
air_quality = self.get_air_quality(lat, lon)
|
||||||
|
|
||||||
|
# Formatear detalles adicionales
|
||||||
|
details = (
|
||||||
|
f"RealFeel: {real_feel}°\n"
|
||||||
|
f"Wind: {wind_speed} km/h\n"
|
||||||
|
f"Wind Gusts: {wind_gusts} km/h\n"
|
||||||
|
f"Air Quality: {air_quality}"
|
||||||
|
)
|
||||||
|
|
||||||
|
return city, temp, details
|
||||||
|
except Exception as e:
|
||||||
|
return None, None, f"Error al obtener el clima: {e}"
|
||||||
|
|
||||||
|
def get_air_quality(self, lat, lon):
|
||||||
|
"""
|
||||||
|
Obtiene la calidad del aire usando OpenWeatherMap.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
lat (float): Latitud.
|
||||||
|
lon (float): Longitud.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
aqi_url = f"http://api.openweathermap.org/data/2.5/air_pollution?lat={lat}&lon={lon}&appid={self.api_key}"
|
||||||
|
response = requests.get(aqi_url)
|
||||||
|
response.raise_for_status()
|
||||||
|
data = response.json()
|
||||||
|
aqi = data["list"][0]["main"]["aqi"]
|
||||||
|
|
||||||
|
# Mapear AQI a descripciones
|
||||||
|
aqi_mapping = {1: "Good", 2: "Fair", 3: "Moderate", 4: "Poor", 5: "Very Poor"}
|
||||||
|
return aqi_mapping.get(aqi, "Unknown")
|
||||||
|
except Exception as e:
|
||||||
|
return f"Error: {e}"
|
||||||
|
|
||||||
|
def update_weather(self):
|
||||||
|
"""
|
||||||
|
Actualiza la información del clima periódicamente.
|
||||||
|
"""
|
||||||
|
while True:
|
||||||
|
lat, lon, location_info = self.get_location()
|
||||||
|
if lat and lon:
|
||||||
|
city, temp, details = self.get_weather(lat, lon)
|
||||||
|
self.header_label.config(text=f"Weather in {city}")
|
||||||
|
self.temp_label.config(text=f"{temp}°C")
|
||||||
|
self.details_label.config(text=details)
|
||||||
|
else:
|
||||||
|
self.header_label.config(text=location_info) # Error de ubicación
|
||||||
|
|
||||||
|
time.sleep(60) # Actualizar cada 60 segundos
|
||||||
|
|
||||||
|
def start_weather_updates(self):
|
||||||
|
"""
|
||||||
|
Inicia el hilo para actualizar el clima.
|
||||||
|
"""
|
||||||
|
weather_thread = threading.Thread(target=self.update_weather, daemon=True)
|
||||||
|
weather_thread.start()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,69 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from matplotlib.figure import Figure
|
||||||
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import random
|
||||||
|
|
||||||
|
class EconomyBitcoinChart:
|
||||||
|
def __init__(self, parent):
|
||||||
|
"""
|
||||||
|
Inicializa los gráficos de economía mundial y Bitcoin en disposición vertical.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent (tk.Frame): Frame donde se colocarán los gráficos.
|
||||||
|
"""
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
# Crear la figura para los gráficos
|
||||||
|
self.figure = Figure(figsize=(8, 6), dpi=100)
|
||||||
|
|
||||||
|
# Subgráficos: Economía mundial y Bitcoin
|
||||||
|
self.ax_economy = self.figure.add_subplot(211) # Gráfico superior
|
||||||
|
self.ax_bitcoin = self.figure.add_subplot(212) # Gráfico inferior
|
||||||
|
|
||||||
|
# Inicializar datos simulados
|
||||||
|
self.economy_data = [random.randint(50, 100) for _ in range(10)] # Economía en meses
|
||||||
|
self.bitcoin_data = [random.randint(20000, 60000) for _ in range(10)] # Bitcoin en días
|
||||||
|
|
||||||
|
self.update_economy_chart()
|
||||||
|
self.update_bitcoin_chart()
|
||||||
|
|
||||||
|
# Embebiendo los gráficos en Tkinter
|
||||||
|
self.canvas = FigureCanvasTkAgg(self.figure, master=self.parent)
|
||||||
|
self.canvas.get_tk_widget().pack(fill="both", expand=True, padx=10, pady=10)
|
||||||
|
|
||||||
|
# Iniciar hilos para actualizar los gráficos
|
||||||
|
threading.Thread(target=self.update_charts, daemon=True).start()
|
||||||
|
|
||||||
|
def update_economy_chart(self):
|
||||||
|
"""Actualiza el gráfico de economía mundial."""
|
||||||
|
self.ax_economy.clear()
|
||||||
|
self.ax_economy.plot(self.economy_data, marker="o", color="blue")
|
||||||
|
self.ax_economy.set_title("Economía Mundial")
|
||||||
|
self.ax_economy.set_ylabel("Índice económico")
|
||||||
|
self.ax_economy.grid(True)
|
||||||
|
|
||||||
|
def update_bitcoin_chart(self):
|
||||||
|
"""Actualiza el gráfico de Bitcoin."""
|
||||||
|
self.ax_bitcoin.clear()
|
||||||
|
self.ax_bitcoin.plot(self.bitcoin_data, marker="o", color="green")
|
||||||
|
self.ax_bitcoin.set_title("Precio de Bitcoin")
|
||||||
|
self.ax_bitcoin.set_ylabel("Precio en USD")
|
||||||
|
self.ax_bitcoin.set_xlabel("Días") # Etiqueta para los días
|
||||||
|
self.ax_bitcoin.grid(True)
|
||||||
|
|
||||||
|
def update_charts(self):
|
||||||
|
"""Actualiza ambos gráficos periódicamente."""
|
||||||
|
while True:
|
||||||
|
# Actualizar datos simulados
|
||||||
|
self.economy_data = self.economy_data[1:] + [random.randint(50, 100)] # Economía en meses
|
||||||
|
self.bitcoin_data = self.bitcoin_data[1:] + [random.randint(20000, 60000)] # Bitcoin en días
|
||||||
|
|
||||||
|
# Actualizar gráficos
|
||||||
|
self.update_economy_chart()
|
||||||
|
self.update_bitcoin_chart()
|
||||||
|
self.canvas.draw()
|
||||||
|
|
||||||
|
# Esperar 5 segundos antes de la próxima actualización
|
||||||
|
time.sleep(5)
|
|
@ -0,0 +1,68 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk
|
||||||
|
import threading
|
||||||
|
from pytube import YouTube
|
||||||
|
|
||||||
|
|
||||||
|
class MusicDownloader:
|
||||||
|
def __init__(self, parent):
|
||||||
|
"""
|
||||||
|
Inicializa la interfaz para descargar música de YouTube en MP3.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent (tk.Frame): Frame donde se colocará el downloader.
|
||||||
|
"""
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
# Etiqueta de título
|
||||||
|
title = tk.Label(self.parent, text="Descargar Música MP3", font=("Helvetica", 14, "bold"))
|
||||||
|
title.pack(pady=10)
|
||||||
|
|
||||||
|
# Entrada para la URL
|
||||||
|
self.url_label = tk.Label(self.parent, text="URL de YouTube:")
|
||||||
|
self.url_label.pack(pady=5)
|
||||||
|
self.url_entry = tk.Entry(self.parent, width=50)
|
||||||
|
self.url_entry.pack(pady=5)
|
||||||
|
|
||||||
|
# Botón para iniciar la descarga
|
||||||
|
self.download_button = tk.Button(self.parent, text="Descargar MP3", command=self.start_download, bg="lightblue")
|
||||||
|
self.download_button.pack(pady=10)
|
||||||
|
|
||||||
|
# Barra de progreso
|
||||||
|
self.progress = ttk.Progressbar(self.parent, orient="horizontal", length=300, mode="determinate")
|
||||||
|
self.progress.pack(pady=10)
|
||||||
|
|
||||||
|
# Etiqueta de estado
|
||||||
|
self.status_label = tk.Label(self.parent, text="", font=("Helvetica", 10))
|
||||||
|
self.status_label.pack(pady=5)
|
||||||
|
|
||||||
|
def start_download(self):
|
||||||
|
"""Inicia la descarga en un hilo separado."""
|
||||||
|
url = self.url_entry.get()
|
||||||
|
if not url:
|
||||||
|
self.status_label.config(text="Por favor, ingrese una URL válida.", fg="red")
|
||||||
|
return
|
||||||
|
self.status_label.config(text="Iniciando descarga...", fg="blue")
|
||||||
|
threading.Thread(target=self.download_music, args=(url,), daemon=True).start()
|
||||||
|
|
||||||
|
def download_music(self, url):
|
||||||
|
"""Descarga el audio de YouTube como MP3."""
|
||||||
|
try:
|
||||||
|
# Inicializa la descarga
|
||||||
|
yt = YouTube(url, on_progress_callback=self.update_progress)
|
||||||
|
stream = yt.streams.filter(only_audio=True).first()
|
||||||
|
|
||||||
|
# Descargar archivo
|
||||||
|
self.status_label.config(text="Descargando...")
|
||||||
|
self.progress["value"] = 0
|
||||||
|
stream.download(filename=f"{yt.title}.mp3")
|
||||||
|
self.status_label.config(text="¡Descarga completada!", fg="green")
|
||||||
|
except Exception as e:
|
||||||
|
self.status_label.config(text=f"Error: {str(e)}", fg="red")
|
||||||
|
|
||||||
|
def update_progress(self, stream, chunk, bytes_remaining):
|
||||||
|
"""Actualiza la barra de progreso durante la descarga."""
|
||||||
|
total_size = stream.filesize
|
||||||
|
bytes_downloaded = total_size - bytes_remaining
|
||||||
|
percentage = (bytes_downloaded / total_size) * 100
|
||||||
|
self.progress["value"] = percentage
|
|
@ -0,0 +1,113 @@
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import ttk, messagebox
|
||||||
|
import threading
|
||||||
|
import mysql.connector
|
||||||
|
from mysql.connector import Error
|
||||||
|
|
||||||
|
|
||||||
|
class SQLQueryExecutor:
|
||||||
|
def __init__(self, parent):
|
||||||
|
"""
|
||||||
|
Clase para ejecutar consultas SQL en una base de datos MySQL.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent (tk.Frame): Frame donde se colocarán los widgets.
|
||||||
|
"""
|
||||||
|
self.parent = parent
|
||||||
|
|
||||||
|
# Campos para ingresar información de conexión
|
||||||
|
self.db_info_frame = tk.Frame(self.parent)
|
||||||
|
self.db_info_frame.pack(pady=10, padx=10, fill="x")
|
||||||
|
|
||||||
|
tk.Label(self.db_info_frame, text="Host:").grid(row=0, column=0, sticky="w")
|
||||||
|
self.host_entry = tk.Entry(self.db_info_frame)
|
||||||
|
self.host_entry.insert(0, "localhost")
|
||||||
|
self.host_entry.grid(row=0, column=1)
|
||||||
|
|
||||||
|
tk.Label(self.db_info_frame, text="Usuario:").grid(row=1, column=0, sticky="w")
|
||||||
|
self.user_entry = tk.Entry(self.db_info_frame)
|
||||||
|
self.user_entry.insert(0, "root")
|
||||||
|
self.user_entry.grid(row=1, column=1)
|
||||||
|
|
||||||
|
tk.Label(self.db_info_frame, text="Contraseña:").grid(row=2, column=0, sticky="w")
|
||||||
|
self.password_entry = tk.Entry(self.db_info_frame, show="*")
|
||||||
|
self.password_entry.grid(row=2, column=1)
|
||||||
|
|
||||||
|
tk.Label(self.db_info_frame, text="Base de datos:").grid(row=3, column=0, sticky="w")
|
||||||
|
self.database_entry = tk.Entry(self.db_info_frame)
|
||||||
|
self.database_entry.grid(row=3, column=1)
|
||||||
|
|
||||||
|
# Botón para conectar a la base de datos
|
||||||
|
self.connect_button = tk.Button(self.db_info_frame, text="Conectar", command=self.connect_to_database)
|
||||||
|
self.connect_button.grid(row=4, column=0, columnspan=2, pady=5)
|
||||||
|
|
||||||
|
# Área para ingresar consultas SQL
|
||||||
|
self.query_frame = tk.Frame(self.parent)
|
||||||
|
self.query_frame.pack(pady=10, padx=10, fill="both", expand=True)
|
||||||
|
|
||||||
|
tk.Label(self.query_frame, text="Consulta SQL:").pack(anchor="w")
|
||||||
|
self.query_text = tk.Text(self.query_frame, height=10)
|
||||||
|
self.query_text.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
# Botón para ejecutar consultas
|
||||||
|
self.execute_button = tk.Button(self.query_frame, text="Ejecutar", command=self.execute_query)
|
||||||
|
self.execute_button.pack(pady=5)
|
||||||
|
|
||||||
|
# Área para mostrar resultados
|
||||||
|
self.result_frame = tk.Frame(self.parent)
|
||||||
|
self.result_frame.pack(pady=10, padx=10, fill="both", expand=True)
|
||||||
|
|
||||||
|
tk.Label(self.result_frame, text="Resultados:").pack(anchor="w")
|
||||||
|
self.result_text = tk.Text(self.result_frame, height=10, state="disabled")
|
||||||
|
self.result_text.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
def connect_to_database(self):
|
||||||
|
"""Conecta a la base de datos utilizando los datos proporcionados."""
|
||||||
|
self.host = self.host_entry.get()
|
||||||
|
self.user = self.user_entry.get()
|
||||||
|
self.password = self.password_entry.get()
|
||||||
|
self.database = self.database_entry.get()
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.connection = mysql.connector.connect(
|
||||||
|
host=self.host,
|
||||||
|
user=self.user,
|
||||||
|
password=self.password,
|
||||||
|
database=self.database
|
||||||
|
)
|
||||||
|
if self.connection.is_connected():
|
||||||
|
messagebox.showinfo("Conexión Exitosa", "Conectado a la base de datos")
|
||||||
|
except Error as e:
|
||||||
|
messagebox.showerror("Error de Conexión", str(e))
|
||||||
|
|
||||||
|
def execute_query(self):
|
||||||
|
"""Ejecuta la consulta SQL en un hilo separado."""
|
||||||
|
query = self.query_text.get("1.0", tk.END).strip()
|
||||||
|
if not query:
|
||||||
|
messagebox.showwarning("Consulta Vacía", "Por favor, ingrese una consulta SQL.")
|
||||||
|
return
|
||||||
|
|
||||||
|
threading.Thread(target=self.run_query, args=(query,), daemon=True).start()
|
||||||
|
|
||||||
|
def run_query(self, query):
|
||||||
|
"""Ejecuta la consulta y muestra los resultados."""
|
||||||
|
try:
|
||||||
|
cursor = self.connection.cursor()
|
||||||
|
cursor.execute(query)
|
||||||
|
|
||||||
|
if query.strip().lower().startswith("select"):
|
||||||
|
rows = cursor.fetchall()
|
||||||
|
column_names = [desc[0] for desc in cursor.description]
|
||||||
|
|
||||||
|
# Mostrar los resultados
|
||||||
|
self.result_text.config(state="normal")
|
||||||
|
self.result_text.delete("1.0", tk.END)
|
||||||
|
self.result_text.insert(tk.END, "\t".join(column_names) + "\n")
|
||||||
|
for row in rows:
|
||||||
|
self.result_text.insert(tk.END, "\t".join(map(str, row)) + "\n")
|
||||||
|
self.result_text.config(state="disabled")
|
||||||
|
else:
|
||||||
|
self.connection.commit()
|
||||||
|
messagebox.showinfo("Éxito", "Consulta ejecutada correctamente.")
|
||||||
|
except Error as e:
|
||||||
|
messagebox.showerror("Error de Consulta", str(e))
|
|
@ -0,0 +1,123 @@
|
||||||
|
import time
|
||||||
|
import tkinter as tk
|
||||||
|
from tkinter import messagebox
|
||||||
|
import threading
|
||||||
|
import random
|
||||||
|
|
||||||
|
|
||||||
|
class TicTacToe:
|
||||||
|
def __init__(self, parent):
|
||||||
|
"""
|
||||||
|
Inicializa el juego de Tic Tac Toe.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
parent (tk.Frame): Frame donde se colocará el juego.
|
||||||
|
"""
|
||||||
|
self.parent = parent
|
||||||
|
self.board = [""] * 9 # Tablero de 3x3 representado como una lista
|
||||||
|
self.current_player = "X" # Jugador inicial
|
||||||
|
self.vs_computer = False # Modo jugador vs máquina
|
||||||
|
|
||||||
|
# Etiqueta para el título
|
||||||
|
title = tk.Label(self.parent, text="Tic Tac Toe", font=("Helvetica", 16, "bold"))
|
||||||
|
title.pack(pady=10)
|
||||||
|
|
||||||
|
# Botón para alternar entre modos
|
||||||
|
self.mode_button = tk.Button(self.parent, text="Modo: Jugador vs Jugador", command=self.toggle_mode)
|
||||||
|
self.mode_button.pack(pady=5)
|
||||||
|
|
||||||
|
# Crear el tablero
|
||||||
|
self.buttons = []
|
||||||
|
self.board_frame = tk.Frame(self.parent)
|
||||||
|
self.board_frame.pack()
|
||||||
|
|
||||||
|
for i in range(9):
|
||||||
|
button = tk.Button(
|
||||||
|
self.board_frame,
|
||||||
|
text="",
|
||||||
|
font=("Helvetica", 20),
|
||||||
|
width=5,
|
||||||
|
height=2,
|
||||||
|
command=self.create_button_command(i) # Aquí usamos la función auxiliar
|
||||||
|
)
|
||||||
|
button.grid(row=i // 3, column=i % 3)
|
||||||
|
self.buttons.append(button)
|
||||||
|
|
||||||
|
# Etiqueta para el estado del juego
|
||||||
|
self.status_label = tk.Label(self.parent, text="Turno: X", font=("Helvetica", 12))
|
||||||
|
self.status_label.pack(pady=5)
|
||||||
|
|
||||||
|
def toggle_mode(self):
|
||||||
|
"""Alterna entre los modos Jugador vs Jugador y Jugador vs Máquina."""
|
||||||
|
self.vs_computer = not self.vs_computer
|
||||||
|
mode_text = "Modo: Jugador vs Máquina" if self.vs_computer else "Modo: Jugador vs Jugador"
|
||||||
|
self.mode_button.config(text=mode_text)
|
||||||
|
self.reset_game()
|
||||||
|
|
||||||
|
def reset_game(self):
|
||||||
|
"""Reinicia el tablero y el estado del juego."""
|
||||||
|
self.board = [""] * 9
|
||||||
|
self.current_player = "X"
|
||||||
|
for button in self.buttons:
|
||||||
|
button.config(text="", state=tk.NORMAL)
|
||||||
|
self.status_label.config(text="Turno: X")
|
||||||
|
|
||||||
|
def make_move(self, index):
|
||||||
|
"""Realiza un movimiento en el tablero."""
|
||||||
|
if self.board[index] == "":
|
||||||
|
self.board[index] = self.current_player
|
||||||
|
self.buttons[index].config(text=self.current_player)
|
||||||
|
|
||||||
|
# Verificar si hay un ganador
|
||||||
|
winner = self.check_winner()
|
||||||
|
if winner:
|
||||||
|
self.end_game(f"¡Ganador: {winner}!")
|
||||||
|
return
|
||||||
|
elif "" not in self.board:
|
||||||
|
self.end_game("¡Empate!")
|
||||||
|
return
|
||||||
|
|
||||||
|
# Cambiar de jugador
|
||||||
|
self.current_player = "O" if self.current_player == "X" else "X"
|
||||||
|
self.status_label.config(text=f"Turno: {self.current_player}")
|
||||||
|
|
||||||
|
# Si está en modo Jugador vs Máquina y es el turno de la máquina
|
||||||
|
if self.vs_computer and self.current_player == "O":
|
||||||
|
threading.Thread(target=self.computer_move).start()
|
||||||
|
|
||||||
|
def computer_move(self):
|
||||||
|
"""Simula el movimiento de la máquina."""
|
||||||
|
self.status_label.config(text="Turno: Máquina (O)")
|
||||||
|
available_moves = [i for i, v in enumerate(self.board) if v == ""]
|
||||||
|
move = random.choice(available_moves)
|
||||||
|
|
||||||
|
def delayed_move():
|
||||||
|
time.sleep(1) # Simular el tiempo de "pensar"
|
||||||
|
self.make_move(move)
|
||||||
|
|
||||||
|
threading.Thread(target=delayed_move).start()
|
||||||
|
|
||||||
|
def check_winner(self):
|
||||||
|
"""Verifica si hay un ganador."""
|
||||||
|
winning_combinations = [
|
||||||
|
(0, 1, 2), (3, 4, 5), (6, 7, 8), # Filas
|
||||||
|
(0, 3, 6), (1, 4, 7), (2, 5, 8), # Columnas
|
||||||
|
(0, 4, 8), (2, 4, 6) # Diagonales
|
||||||
|
]
|
||||||
|
for a, b, c in winning_combinations:
|
||||||
|
if self.board[a] == self.board[b] == self.board[c] and self.board[a] != "":
|
||||||
|
return self.board[a]
|
||||||
|
return None
|
||||||
|
|
||||||
|
def end_game(self, message):
|
||||||
|
"""Finaliza el juego mostrando un mensaje."""
|
||||||
|
messagebox.showinfo("Fin del Juego", message)
|
||||||
|
self.reset_game()
|
||||||
|
|
||||||
|
def create_button_command(self, index):
|
||||||
|
"""Crea un comando para un botón con un índice específico."""
|
||||||
|
|
||||||
|
def command():
|
||||||
|
self.make_move(index)
|
||||||
|
|
||||||
|
return command
|
|
@ -0,0 +1,195 @@
|
||||||
|
import tkinter as tk
|
||||||
|
import threading
|
||||||
|
import time
|
||||||
|
import mysql.connector
|
||||||
|
import requests
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
from tkinter import messagebox
|
||||||
|
|
||||||
|
class WebScraperToDB:
|
||||||
|
def __init__(self, parent):
|
||||||
|
"""
|
||||||
|
Inicializa el widget de scraping con integración a base de datos.
|
||||||
|
"""
|
||||||
|
self.parent = parent
|
||||||
|
self.scraping_thread = None
|
||||||
|
self.stop_event = threading.Event()
|
||||||
|
|
||||||
|
# Crear campos de conexión para la base de datos
|
||||||
|
db_frame = tk.Frame(self.parent)
|
||||||
|
db_frame.pack(pady=5)
|
||||||
|
|
||||||
|
tk.Label(db_frame, text="Host:").grid(row=0, column=0)
|
||||||
|
self.host_entry = tk.Entry(db_frame)
|
||||||
|
self.host_entry.insert(0, "localhost")
|
||||||
|
self.host_entry.grid(row=0, column=1)
|
||||||
|
|
||||||
|
tk.Label(db_frame, text="Usuario:").grid(row=1, column=0)
|
||||||
|
self.user_entry = tk.Entry(db_frame)
|
||||||
|
self.user_entry.insert(0, "root")
|
||||||
|
self.user_entry.grid(row=1, column=1)
|
||||||
|
|
||||||
|
tk.Label(db_frame, text="Contraseña:").grid(row=2, column=0)
|
||||||
|
self.password_entry = tk.Entry(db_frame, show="*")
|
||||||
|
self.password_entry.grid(row=2, column=1)
|
||||||
|
|
||||||
|
tk.Label(db_frame, text="Nombre BD:").grid(row=3, column=0)
|
||||||
|
self.database_entry = tk.Entry(db_frame)
|
||||||
|
self.database_entry.insert(0, "scraping_db")
|
||||||
|
self.database_entry.grid(row=3, column=1)
|
||||||
|
|
||||||
|
tk.Button(db_frame, text="Crear Base de Datos", command=self.create_database).grid(row=4, column=0, columnspan=2, pady=5)
|
||||||
|
|
||||||
|
# Área para URL y botones de control
|
||||||
|
control_frame = tk.Frame(self.parent)
|
||||||
|
control_frame.pack(pady=5)
|
||||||
|
|
||||||
|
tk.Label(control_frame, text="URL para Scraping:").grid(row=0, column=0)
|
||||||
|
self.url_entry = tk.Entry(control_frame, width=50)
|
||||||
|
self.url_entry.insert(0, "https://quotes.toscrape.com/")
|
||||||
|
self.url_entry.grid(row=0, column=1)
|
||||||
|
|
||||||
|
# Campo para Selector HTML
|
||||||
|
tk.Label(control_frame, text="Selector HTML:").grid(row=2, column=0)
|
||||||
|
self.selector_entry = tk.Entry(control_frame, width=50)
|
||||||
|
self.selector_entry.insert(0, "h1") # Valor predeterminado
|
||||||
|
self.selector_entry.grid(row=2, column=1)
|
||||||
|
|
||||||
|
self.start_button = tk.Button(control_frame, text="Iniciar Scraping", command=self.start_scraping)
|
||||||
|
self.start_button.grid(row=1, column=0, pady=5)
|
||||||
|
|
||||||
|
self.stop_button = tk.Button(control_frame, text="Parar Scraping", command=self.stop_scraping, state="disabled")
|
||||||
|
self.stop_button.grid(row=1, column=1, pady=5)
|
||||||
|
|
||||||
|
self.reset_button = tk.Button(control_frame, text="Resetear Scraping", command=self.reset_database)
|
||||||
|
self.reset_button.grid(row=1, column=2, pady=5)
|
||||||
|
|
||||||
|
# Área para mostrar el estado
|
||||||
|
self.status_label = tk.Label(self.parent, text="Estado: Inactivo", fg="red")
|
||||||
|
self.status_label.pack(pady=5)
|
||||||
|
|
||||||
|
# Área para mostrar los datos scrapeados
|
||||||
|
self.scraped_data_frame = tk.Frame(self.parent)
|
||||||
|
self.scraped_data_frame.pack(pady=5, fill="both", expand=True)
|
||||||
|
|
||||||
|
tk.Label(self.scraped_data_frame, text="Datos Scrapeados:").pack(anchor="w")
|
||||||
|
|
||||||
|
self.scraped_data_text = tk.Text(self.scraped_data_frame, height=10, state="disabled")
|
||||||
|
self.scraped_data_text.pack(fill="both", expand=True)
|
||||||
|
|
||||||
|
def create_database(self):
|
||||||
|
"""Crea la base de datos y la tabla para almacenar datos de scraping."""
|
||||||
|
try:
|
||||||
|
connection = mysql.connector.connect(
|
||||||
|
host=self.host_entry.get(),
|
||||||
|
user=self.user_entry.get(),
|
||||||
|
password=self.password_entry.get()
|
||||||
|
)
|
||||||
|
cursor = connection.cursor()
|
||||||
|
cursor.execute(f"CREATE DATABASE IF NOT EXISTS {self.database_entry.get()}")
|
||||||
|
connection.database = self.database_entry.get()
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS scraped_data (
|
||||||
|
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
link TEXT NOT NULL,
|
||||||
|
scraped_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||||
|
)
|
||||||
|
""")
|
||||||
|
connection.close()
|
||||||
|
messagebox.showinfo("Éxito", "Base de datos y tabla creadas correctamente.")
|
||||||
|
except mysql.connector.Error as e:
|
||||||
|
messagebox.showerror("Error", str(e))
|
||||||
|
|
||||||
|
def start_scraping(self):
|
||||||
|
"""Inicia el scraping en un hilo separado."""
|
||||||
|
if self.scraping_thread and self.scraping_thread.is_alive():
|
||||||
|
messagebox.showwarning("Aviso", "El scraping ya está en ejecución.")
|
||||||
|
return
|
||||||
|
self.stop_event.clear()
|
||||||
|
self.scraping_thread = threading.Thread(target=self.scrape_data, daemon=True)
|
||||||
|
self.scraping_thread.start()
|
||||||
|
self.status_label.config(text="Estado: Ejecutando...", fg="green")
|
||||||
|
self.start_button.config(state="disabled")
|
||||||
|
self.stop_button.config(state="normal")
|
||||||
|
|
||||||
|
def scrape_data(self):
|
||||||
|
"""Realiza el scraping de manera continua y guarda los datos en la base de datos."""
|
||||||
|
url = self.url_entry.get()
|
||||||
|
selector = self.selector_entry.get()
|
||||||
|
|
||||||
|
try:
|
||||||
|
headers = {
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36"
|
||||||
|
}
|
||||||
|
connection = mysql.connector.connect(
|
||||||
|
host=self.host_entry.get(),
|
||||||
|
user=self.user_entry.get(),
|
||||||
|
password=self.password_entry.get(),
|
||||||
|
database=self.database_entry.get()
|
||||||
|
)
|
||||||
|
cursor = connection.cursor()
|
||||||
|
|
||||||
|
while not self.stop_event.is_set():
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
soup = BeautifulSoup(response.text, "html.parser")
|
||||||
|
|
||||||
|
# Busca elementos según el selector ingresado por el usuario
|
||||||
|
elements = soup.select(selector)
|
||||||
|
if not elements:
|
||||||
|
self.status_label.config(text="Estado: Sin datos encontrados.", fg="orange")
|
||||||
|
time.sleep(5) # Pausa antes de intentar de nuevo
|
||||||
|
continue
|
||||||
|
|
||||||
|
for element in elements:
|
||||||
|
title_text = element.get_text(strip=True)
|
||||||
|
link = element.get("href", "Sin enlace") # Asegúrate de que el selector apunte a elementos <a>
|
||||||
|
|
||||||
|
# Insertar en la base de datos
|
||||||
|
cursor.execute("INSERT INTO scraped_data (title, link) VALUES (%s, %s)", (title_text, link))
|
||||||
|
connection.commit()
|
||||||
|
|
||||||
|
# Mostrar en la interfaz
|
||||||
|
self.scraped_data_text.config(state="normal")
|
||||||
|
self.scraped_data_text.insert("end", f"{title_text} - {link}\n")
|
||||||
|
self.scraped_data_text.see("end")
|
||||||
|
self.scraped_data_text.config(state="disabled")
|
||||||
|
|
||||||
|
self.status_label.config(text=f"Estado: Scrapeando {title_text}...", fg="green")
|
||||||
|
|
||||||
|
# Pausa entre iteraciones
|
||||||
|
time.sleep(5)
|
||||||
|
|
||||||
|
connection.close()
|
||||||
|
self.status_label.config(text="Estado: Inactivo", fg="red")
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", str(e))
|
||||||
|
self.status_label.config(text="Estado: Error", fg="red")
|
||||||
|
|
||||||
|
def stop_scraping(self):
|
||||||
|
"""Detiene el proceso de scraping."""
|
||||||
|
self.stop_event.set()
|
||||||
|
self.start_button.config(state="normal")
|
||||||
|
self.stop_button.config(state="disabled")
|
||||||
|
|
||||||
|
def reset_database(self):
|
||||||
|
"""Elimina todos los datos de la tabla."""
|
||||||
|
try:
|
||||||
|
connection = mysql.connector.connect(
|
||||||
|
host=self.host_entry.get(),
|
||||||
|
user=self.user_entry.get(),
|
||||||
|
password=self.password_entry.get(),
|
||||||
|
database=self.database_entry.get()
|
||||||
|
)
|
||||||
|
cursor = connection.cursor()
|
||||||
|
cursor.execute("TRUNCATE TABLE scraped_data")
|
||||||
|
connection.commit()
|
||||||
|
connection.close()
|
||||||
|
messagebox.showinfo("Éxito", "Datos reseteados correctamente.")
|
||||||
|
|
||||||
|
# Limpiar el cuadro de texto
|
||||||
|
self.scraped_data_text.config(state="normal")
|
||||||
|
self.scraped_data_text.delete("1.0", "end")
|
||||||
|
self.scraped_data_text.config(state="disabled")
|
||||||
|
except Exception as e:
|
||||||
|
messagebox.showerror("Error", str(e))
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue