Compare commits
No commits in common. "168a2a99848319663ea50cf5531f18f0a0157334" and "d3a7ab33969e000d762cf4ae6bb43963dcd9a909" have entirely different histories.
168a2a9984
...
d3a7ab3396
|
@ -1,15 +0,0 @@
|
|||
<?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" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.13 (Final)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module version="4">
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
</component>
|
||||
</module>
|
|
@ -1,7 +1,4 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11.2 WSL (Debian): (/home/santi/.virtualenvs/Final/bin/python)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (Final)" project-jdk-type="Python SDK" />
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (ProyectoFinalProcesosServicios)" project-jdk-type="Python SDK" />
|
||||
</project>
|
|
@ -1,8 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Final.iml" filepath="$PROJECT_DIR$/.idea/Final.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -1,6 +0,0 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
Binary file not shown.
Before Width: | Height: | Size: 69 KiB |
BIN
Imagen/img.png
BIN
Imagen/img.png
Binary file not shown.
Before Width: | Height: | Size: 695 KiB |
80
README.md
80
README.md
|
@ -1,80 +0,0 @@
|
|||

|
||||

|
||||
|
||||
Descripción General
|
||||
|
||||
La Aplicación Responsive Window es una aplicación de escritorio multifuncional con las siguientes características principales:
|
||||
|
||||
Panel del Clima: Muestra información climática actual de Denia, incluyendo temperatura, condiciones de viento y calidad del aire.
|
||||
|
||||
Interfaz con Pestañas:
|
||||
|
||||
Descarga de Música: Descarga y gestiona archivos de música.
|
||||
|
||||
Lista de Tareas: Organiza tareas y gestiona actividades diarias.
|
||||
|
||||
Tres en Raya: Un juego simple para entretenimiento.
|
||||
|
||||
Web Scraping: Ingresa una URL para extraer datos de la web y ver los resultados.
|
||||
|
||||
Función de Chat: Ventana de chat integrada para la interacción del usuario.
|
||||
|
||||
Monitor de Recursos: Muestra información del sistema como uso de CPU, RAM, estado de la batería y actividad de red.
|
||||
|
||||
Fecha y Hora: Muestra la fecha y hora actuales en tiempo real.
|
||||
|
||||
Características
|
||||
|
||||
Panel del Clima
|
||||
|
||||
Ubicación: Muestra información climática específica de Denia.
|
||||
|
||||
Detalles: Indica temperatura, sensación térmica, velocidad y dirección del viento, ráfagas y índice de calidad del aire.
|
||||
|
||||
Interfaz con Pestañas
|
||||
|
||||
Descarga de Música
|
||||
|
||||
Ingresa y descarga archivos de música desde URLs especificadas.
|
||||
|
||||
Gestiona y organiza la música descargada.
|
||||
|
||||
Lista de Tareas
|
||||
|
||||
Añade, visualiza y gestiona tareas.
|
||||
|
||||
Lleva un seguimiento eficiente de las actividades diarias.
|
||||
|
||||
Tres en Raya
|
||||
|
||||
Juega una partida simple de Tres en Raya.
|
||||
|
||||
Web Scraping
|
||||
|
||||
Ingresa una URL para extraer contenido de páginas web.
|
||||
|
||||
Visualiza y analiza los datos extraídos dentro de la aplicación.
|
||||
|
||||
Función de Chat
|
||||
|
||||
Sistema de chat integrado para la comunicación del usuario.
|
||||
|
||||
Muestra mensajes con usuario y marca de tiempo.
|
||||
|
||||
Monitor de Recursos
|
||||
|
||||
Uso de CPU: Muestra el porcentaje de uso actual del CPU.
|
||||
|
||||
Uso de RAM: Indica el consumo de memoria.
|
||||
|
||||
Batería: Monitorea el nivel de batería y el tiempo estimado restante.
|
||||
|
||||
Actividad de Red: Rastrea los datos enviados y recibidos a través de la red.
|
||||
|
||||
Fecha y Hora
|
||||
|
||||
Muestra la fecha y hora actuales en un formato fácil de leer.
|
||||
|
||||
|
||||
|
||||
LINK PARA VIDEO DE YOUTUBE https://www.youtube.com/watch?v=f5WWiZ_IZv0
|
118
app/ConfigMgr.py
118
app/ConfigMgr.py
|
@ -1,118 +0,0 @@
|
|||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import os
|
||||
import configparser
|
||||
|
||||
class ConfigMgr:
|
||||
|
||||
def __init__(self, top_level, config_changed_listener=None):
|
||||
self.top_level = top_level
|
||||
self.config_window = None
|
||||
self.config = configparser.ConfigParser()
|
||||
self.__load_config()
|
||||
self.config_changed_listener=config_changed_listener
|
||||
self.config.read('config.ini')
|
||||
|
||||
def __load_config(self):
|
||||
if os.path.exists('config.ini'):
|
||||
self.config.read('config.ini')
|
||||
self.__check_config()
|
||||
else:
|
||||
print("Config file not found, creating default config file")
|
||||
with open('config.ini', 'w') as f:
|
||||
self.__write_default_config(f)
|
||||
self.config.read('config.ini')
|
||||
|
||||
def __check_config(self):
|
||||
print("Checking config file")
|
||||
if "Chat" not in self.config:
|
||||
self.config["Chat"] = {
|
||||
"server": "http://localhost:2020",
|
||||
"name": "User"
|
||||
}
|
||||
if "Weather" not in self.config:
|
||||
self.config["Weather"] = {
|
||||
"city": "Denia, Alicante"
|
||||
}
|
||||
|
||||
def __write_default_config(self, file):
|
||||
chat_config = ("[Chat]\n"
|
||||
"server=http://localhost:2020\n"
|
||||
"name=User\n")
|
||||
|
||||
weather_config = ("[Weather]\n"
|
||||
"city=Denia, Alicante\n")
|
||||
|
||||
file.write(chat_config + weather_config)
|
||||
|
||||
def display_config_window(self):
|
||||
if (self.config_window is None
|
||||
or not tk.Toplevel.winfo_exists(self.config_window)):
|
||||
self.config_window = self.__build_config_window()
|
||||
else:
|
||||
self.config_window.lift()
|
||||
|
||||
def __build_config_window(self):
|
||||
config_window = tk.Toplevel(self.top_level)
|
||||
config_window.title("Config")
|
||||
config_window.geometry("400x300")
|
||||
|
||||
notebook = ttk.Notebook(config_window)
|
||||
notebook.pack(expand=True, fill="both")
|
||||
|
||||
# Chat Config Tab
|
||||
chat_tab = ttk.Frame(notebook)
|
||||
notebook.add(chat_tab, text="Chat Config")
|
||||
|
||||
chat_server_label = tk.Label(chat_tab, text="Chat Server URL")
|
||||
chat_server_label.pack()
|
||||
self.chat_server_variable = tk.StringVar()
|
||||
try:
|
||||
self.chat_server_variable.set(self.config["Chat"]["server"])
|
||||
except KeyError:
|
||||
self.chat_server_variable.set("")
|
||||
chat_server_input = tk.Entry(chat_tab, textvariable=self.chat_server_variable)
|
||||
chat_server_input.pack()
|
||||
|
||||
chat_name_label = tk.Label(chat_tab, text="Name in the Chat")
|
||||
chat_name_label.pack()
|
||||
self.chat_name_variable = tk.StringVar()
|
||||
try:
|
||||
self.chat_name_variable.set(self.config["Chat"]["name"])
|
||||
except KeyError:
|
||||
self.chat_name_variable.set("")
|
||||
chat_name_input = tk.Entry(chat_tab, textvariable=self.chat_name_variable)
|
||||
chat_name_input.pack()
|
||||
|
||||
# Weather Config Tab
|
||||
weather_tab = ttk.Frame(notebook)
|
||||
notebook.add(weather_tab, text="Weather Config")
|
||||
|
||||
weather_city_label = tk.Label(weather_tab, text="City")
|
||||
weather_city_label.pack()
|
||||
self.weather_city_variable = tk.StringVar()
|
||||
try:
|
||||
self.weather_city_variable.set(self.config["Weather"]["city"])
|
||||
except KeyError:
|
||||
self.weather_city_variable.set("")
|
||||
weather_city_input = tk.Entry(weather_tab, textvariable=self.weather_city_variable)
|
||||
weather_city_input.pack()
|
||||
|
||||
self.save_button = tk.Button(config_window, text="Save", command=self.save_config)
|
||||
self.save_button.pack(pady=10)
|
||||
|
||||
return config_window
|
||||
|
||||
def save_config(self):
|
||||
self.config["Chat"] = {"server": self.chat_server_variable.get(),
|
||||
"name": self.chat_name_variable.get()}
|
||||
|
||||
self.config["Weather"] = {"city": self.weather_city_variable.get()}
|
||||
|
||||
with open('config.ini', 'w') as configfile:
|
||||
self.config.write(configfile)
|
||||
|
||||
self.config_changed_listener()
|
||||
|
||||
# Close window
|
||||
self.config_window.destroy()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,94 +0,0 @@
|
|||
import threading
|
||||
import time
|
||||
import flask
|
||||
from werkzeug.serving import make_server
|
||||
import logging
|
||||
|
||||
class Server:
|
||||
"""
|
||||
This server does NOT use Flask's built-in development server.
|
||||
Instead, it uses Werkzeug's make_server to create a WSGI server.
|
||||
|
||||
This server is thread-safe and can be stopped by setting the stop_event.
|
||||
|
||||
This server works as a chat server by polling, not by using websockets (to keep it simple).
|
||||
"""
|
||||
|
||||
def __init__(self, host: str, port: int, stop_event: threading.Event):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.stop_event = stop_event
|
||||
|
||||
# Flask definition
|
||||
self.flask = flask.Flask(__name__)
|
||||
self.flask.add_url_rule('/status', methods=['POST'], view_func=self.status)
|
||||
self.flask.add_url_rule('/send_message', methods=['POST'], view_func=self.send_message)
|
||||
self.flask.add_url_rule('/get_messages', methods=['POST'], view_func=self.get_messages)
|
||||
self.server = make_server(self.host, self.port, self.flask)
|
||||
self.watcher_thread = threading.Thread(target=self.__watcher)
|
||||
self.server_thread = threading.Thread(target=self.server.serve_forever)
|
||||
self.server_thread.start()
|
||||
self.watcher_thread.start()
|
||||
|
||||
|
||||
log = logging.getLogger('werkzeug')
|
||||
log.setLevel(logging.ERROR)
|
||||
self.flask.logger.setLevel(logging.ERROR)
|
||||
|
||||
# Message initialization
|
||||
self.message_id = 0
|
||||
self.messages = self.__init_messages()
|
||||
|
||||
def __watcher(self):
|
||||
while not self.stop_event.is_set():
|
||||
time.sleep(1)
|
||||
|
||||
self.shutdown()
|
||||
|
||||
def shutdown(self):
|
||||
self.__persist_messages()
|
||||
self.server.shutdown()
|
||||
|
||||
def __init_messages(self):
|
||||
messages = []
|
||||
try:
|
||||
with open('chat_server/messages.txt', 'r') as f:
|
||||
for line in f:
|
||||
parts = line.strip().split('|')
|
||||
messages.append({
|
||||
'id': int(parts[0]),
|
||||
'sender': parts[1],
|
||||
'content': parts[2]
|
||||
})
|
||||
self.message_id = messages[-1]['id'] if messages else 0
|
||||
except FileNotFoundError:
|
||||
with open('chat_server/messages.txt', 'w+') as f:
|
||||
pass
|
||||
return messages
|
||||
|
||||
def __persist_messages(self):
|
||||
with open('chat_server/messages.txt', 'w') as f:
|
||||
for message in self.messages:
|
||||
f.write(f"{message['id']}|{message['sender']}|{message['content']}\n")
|
||||
|
||||
def status(self):
|
||||
return 'OK'
|
||||
|
||||
def send_message(self):
|
||||
sender = flask.request.json['sender']
|
||||
content = flask.request.json['content']
|
||||
self.message_id += 1
|
||||
message = {
|
||||
'id': self.message_id,
|
||||
'sender': sender,
|
||||
'content': content
|
||||
}
|
||||
self.messages.append(message)
|
||||
return {'id': self.message_id}
|
||||
|
||||
def get_messages(self):
|
||||
try: last_id = flask.request.json.get('last_id')
|
||||
# last_id is a mandatory parameter
|
||||
except AttributeError: return flask.Response('Last ID not specified', status=400)
|
||||
new_messages = [msg for msg in self.messages if msg['id'] > last_id]
|
||||
return {'messages': new_messages}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,4 +0,0 @@
|
|||
1|User|Hola
|
||||
2|feo|hola
|
||||
3|feo|uybsdfuhbsd
|
||||
4|kevion|hola
|
|
@ -1,7 +0,0 @@
|
|||
[Chat]
|
||||
server = http://localhost:2020
|
||||
name = kevion
|
||||
|
||||
[Weather]
|
||||
city = Denia
|
||||
|
300
app/main.py
300
app/main.py
|
@ -1,180 +1,194 @@
|
|||
import threading
|
||||
import tkinter as tk
|
||||
from tkinter import Menu
|
||||
from tkinter import ttk
|
||||
import threading
|
||||
import time
|
||||
import datetime
|
||||
from tkinter import Menu # Importar el widget Menu
|
||||
from tkinter import ttk # Importar el widget ttk
|
||||
|
||||
from app.ConfigMgr import ConfigMgr
|
||||
from app.chat_server.Server import Server
|
||||
from app.widgets import ClockLabel
|
||||
from app.widgets.ChatTab import ChatTab
|
||||
from app.widgets.MusicDownloadTab import MusicDownloadTab
|
||||
from app.widgets.MusicPlayerTab import MusicPlayerTab
|
||||
from app.widgets.TicTacToeTab import TicTacToeTab
|
||||
from app.widgets.TodoTab import TodoTab
|
||||
from app.widgets.UsageLabels import CPULabel, RAMLabel, BatteryLabel, NetworkLabel
|
||||
from app.widgets.WeatherTab import WeatherTab
|
||||
from app.widgets.WebScrapingTab import WebScrapingTab
|
||||
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
|
||||
|
||||
stop_event = threading.Event()
|
||||
# Clave de API de OpenWeatherMap
|
||||
API_KEY = "1fa8fd05b650773bbc3f2130657e808a"
|
||||
|
||||
def on_closing():
|
||||
# Kill all threads that are linked to the stop_event
|
||||
# This ensures that execution is thread-safe
|
||||
stop_event.set()
|
||||
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}"
|
||||
|
||||
# Close the main window
|
||||
root.quit()
|
||||
root.destroy()
|
||||
# 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})
|
||||
|
||||
def on_config_changed():
|
||||
chat_frame.change_server_url(config_manager.config["Chat"]["server"])
|
||||
chat_frame.change_sender_name(config_manager.config["Chat"]["name"])
|
||||
weather_tab.changeCity(config_manager.config["Weather"]["city"])
|
||||
# Espera 1 segundo antes de actualizar de nuevo
|
||||
time.sleep(1)
|
||||
|
||||
# Create the main window
|
||||
# Crear la ventana principal
|
||||
root = tk.Tk()
|
||||
root.title("Responsive Window")
|
||||
root.geometry("1150x700")
|
||||
root.title("Ventana Responsive")
|
||||
root.geometry("1000x700") # Tamaño inicial
|
||||
|
||||
config_manager = ConfigMgr(root, config_changed_listener=on_config_changed)
|
||||
server = Server(host="localhost", port=2020, stop_event=stop_event)
|
||||
# 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
|
||||
|
||||
# Configure the main window to be responsive
|
||||
root.columnconfigure(0, weight=0)
|
||||
root.columnconfigure(1, weight=1)
|
||||
root.columnconfigure(2, weight=0)
|
||||
root.rowconfigure(0, weight=1)
|
||||
root.rowconfigure(1, weight=0)
|
||||
|
||||
# Create the top menu
|
||||
# Crear el menú superior
|
||||
menu_bar = Menu(root)
|
||||
|
||||
file_menu = Menu(menu_bar, tearoff=0)
|
||||
file_menu.add_command(label="New")
|
||||
file_menu.add_command(label="Open")
|
||||
file_menu.add_command(label="Nuevo")
|
||||
file_menu.add_command(label="Abrir")
|
||||
file_menu.add_separator()
|
||||
file_menu.add_command(label="Config", command=config_manager.display_config_window)
|
||||
file_menu.add_command(label="Exit", command=on_closing)
|
||||
file_menu.add_command(label="Salir", command=root.quit)
|
||||
|
||||
edit_menu = Menu(menu_bar, tearoff=0)
|
||||
edit_menu.add_command(label="Copy")
|
||||
edit_menu.add_command(label="Paste")
|
||||
edit_menu.add_command(label="Copiar")
|
||||
edit_menu.add_command(label="Pegar")
|
||||
|
||||
menu_bar.add_cascade(label="File", menu=file_menu)
|
||||
menu_bar.add_cascade(label="Edit", menu=edit_menu)
|
||||
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)
|
||||
|
||||
# Create the side and central frames
|
||||
frame_left = tk.Frame(root, bg="lightblue", width=300)
|
||||
frame_center = tk.Frame(root, bg="white")
|
||||
frame_right = tk.Frame(root, bg="lightgreen", width=300)
|
||||
# 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)
|
||||
|
||||
# Place the side and central frames
|
||||
frame_left.grid(row=0, column=0, sticky="ns")
|
||||
frame_center.grid(row=0, column=1, sticky="nsew")
|
||||
frame_right.grid(row=0, column=2, sticky="ns")
|
||||
# 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")
|
||||
|
||||
# Configure the fixed sizes of the side frames
|
||||
frame_left.grid_propagate(False)
|
||||
frame_right.grid_propagate(False)
|
||||
# Configurar los tamaños fijos de los frames laterales
|
||||
frame_izquierdo.grid_propagate(False)
|
||||
frame_derecho.grid_propagate(False)
|
||||
|
||||
# Configure the left frame to have three rows
|
||||
frame_left.rowconfigure(0, weight=1)
|
||||
frame_left.rowconfigure(1, weight=0)
|
||||
frame_left.rowconfigure(2, weight=0)
|
||||
# Integrar el widget del clima en el panel izquierdo
|
||||
weather_widget = WeatherWidget(frame_izquierdo, API_KEY)
|
||||
|
||||
# Add weather tab to the left frame, occupying 1/3 of the height
|
||||
weather_tab = WeatherTab(frame_left, stop_event=stop_event,
|
||||
city=config_manager.config["Weather"]["city"], refresh_rate=300)
|
||||
weather_tab.grid(row=0, column=0, sticky="nsew")
|
||||
# Añadir el lanzador de aplicaciones al panel izquierdo
|
||||
app_launcher = ApplicationLauncher(frame_izquierdo)
|
||||
|
||||
# Adjust the remaining rows to occupy the rest of the space
|
||||
frame_left.rowconfigure(1, weight=2)
|
||||
frame_left.rowconfigure(2, weight=2)
|
||||
# Añadir gráfico de lenguajes al panel izquierdo
|
||||
language_chart = LanguageChart(frame_izquierdo)
|
||||
|
||||
# Divide the central frame into two parts (top variable and bottom fixed)
|
||||
frame_center.rowconfigure(0, weight=1)
|
||||
frame_center.rowconfigure(1, weight=0)
|
||||
frame_center.columnconfigure(0, weight=1)
|
||||
# Crear el widget de Chat en el panel derecho con más espacio
|
||||
chat_widget = ChatWidget(frame_derecho)
|
||||
|
||||
# Create subframes within the central frame
|
||||
frame_top = tk.Frame(frame_center, bg="lightyellow")
|
||||
frame_bottom = tk.Frame(frame_center, bg="lightgray", height=100)
|
||||
# Agregar el reproductor de música al panel derecho, en la parte inferior
|
||||
music_player = MusicPlayer(frame_derecho)
|
||||
|
||||
# Place the subframes within the central frame
|
||||
frame_top.grid(row=0, column=0, sticky="nsew")
|
||||
frame_bottom.grid(row=1, column=0, sticky="ew")
|
||||
# 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
|
||||
|
||||
# Fix the size of the bottom part
|
||||
frame_bottom.grid_propagate(False)
|
||||
# Crear subframes dentro del frame central
|
||||
frame_superior = tk.Frame(frame_central, bg="lightyellow")
|
||||
frame_inferior = tk.Frame(frame_central, bg="lightgray", height=100)
|
||||
|
||||
# Create the status bar
|
||||
status_bar = tk.Label(root, text="Status bar", bg="lightgray", anchor="w")
|
||||
status_bar.grid(row=1, column=0, columnspan=3, sticky="ew")
|
||||
# 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")
|
||||
|
||||
# Notebook for widgets
|
||||
style = ttk.Style()
|
||||
style.configure("CustomNotebook.TNotebook.Tab", font=("Arial", 12, "bold"))
|
||||
notebook = ttk.Notebook(frame_top, style="CustomNotebook.TNotebook")
|
||||
notebook.pack(fill="both", expand=True)
|
||||
# Fijar el tamaño de la parte inferior
|
||||
frame_inferior.grid_propagate(False)
|
||||
|
||||
# Add the tabs to the notebook
|
||||
music_download_tab = MusicDownloadTab(notebook, stop_event=stop_event)
|
||||
music_download_tab.pack(fill="both", expand=True)
|
||||
notebook.add(music_download_tab, text="Music Download")
|
||||
# Crear un evento de parada
|
||||
stop_event = threading.Event()
|
||||
|
||||
# Add the TodoTab to the notebook
|
||||
todo_tab = TodoTab(notebook, stop_event=stop_event)
|
||||
todo_tab.pack(fill="both", expand=True)
|
||||
notebook.add(todo_tab, text="Todo List")
|
||||
|
||||
# Add the TodoTab to the notebook
|
||||
tic_tac_toe_tab = TicTacToeTab(notebook, stop_event=stop_event)
|
||||
tic_tac_toe_tab.pack(fill="both", expand=True)
|
||||
notebook.add(tic_tac_toe_tab, text="Tic Tac Toe")
|
||||
|
||||
# Add the TodoTab to the notebook
|
||||
web_scraping_tab = WebScrapingTab(notebook, stop_event=stop_event)
|
||||
web_scraping_tab.pack(fill="both", expand=True)
|
||||
notebook.add(web_scraping_tab, text="Web Scraping")
|
||||
|
||||
# Create the chat and music player frames within the right frame
|
||||
frame_chat = tk.Frame(frame_right, bg="lightgreen")
|
||||
frame_music_player = tk.Frame(frame_right)
|
||||
|
||||
# Place the chat and music player frames within the right frame
|
||||
frame_chat.grid(row=0, column=0, sticky="nsew")
|
||||
frame_music_player.grid(row=1, column=0, sticky="nsew")
|
||||
|
||||
# Configure the right frame to be responsive
|
||||
frame_right.rowconfigure(0, weight=3)
|
||||
frame_right.rowconfigure(1, weight=1)
|
||||
frame_right.columnconfigure(0, weight=1)
|
||||
|
||||
# Create and place the chat frame and music player tab update
|
||||
chat_frame = ChatTab(frame_chat, chat_server_url=config_manager.config["Chat"]["server"],
|
||||
sender_name=config_manager.config["Chat"]["name"],
|
||||
stop_event=stop_event, width=200, bg="lightgreen", refresh_rate=1)
|
||||
|
||||
chat_frame.pack(fill="both", expand=True)
|
||||
|
||||
music_player = MusicPlayerTab(frame_music_player, stop_event=stop_event, refresh_rate=5)
|
||||
music_player.pack(fill="both", expand=True)
|
||||
|
||||
label_cpu = CPULabel(status_bar, bg="lightgreen", font=("Helvetica", 10), relief="groove", anchor="center", width=10, stop_event=stop_event, refresh_rate=1)
|
||||
label_ram = RAMLabel(status_bar, bg="lightcoral", font=("Helvetica", 10), relief="groove", anchor="center", width=10, stop_event=stop_event, refresh_rate=3)
|
||||
label_battery = BatteryLabel(status_bar, bg="lightblue", font=("Helvetica", 10), relief="groove", anchor="center", width=20, stop_event=stop_event, refresh_rate=10)
|
||||
label_net = NetworkLabel(status_bar, text="Network", bg="lightpink", font=("Helvetica", 10), relief="groove", anchor="center", width=20, stop_event=stop_event, refresh_rate=5)
|
||||
label_time = ClockLabel(status_bar, font=("Helvetica", 12), bd=1, fg="darkblue", relief="sunken", anchor="center", width=20, stop_event=stop_event, refresh_rate=0.5)
|
||||
|
||||
label_cpu.pack(side="left", fill="both", expand=True)
|
||||
label_ram.pack(side="left", fill="both", expand=True)
|
||||
label_battery.pack(side="left", fill="both", expand=True)
|
||||
label_net.pack(side="left", fill="both", expand=True)
|
||||
label_time.pack(side="right", fill="both", expand=True)
|
||||
# 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)
|
||||
|
||||
# Run the application
|
||||
# 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()
|
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.
|
@ -1,28 +0,0 @@
|
|||
beautifulsoup4==4.12.3
|
||||
blinker==1.9.0
|
||||
cairocffi==1.7.1
|
||||
CairoSVG==2.7.1
|
||||
certifi==2024.8.30
|
||||
cffi==1.17.1
|
||||
charset-normalizer==3.4.0
|
||||
click==8.1.7
|
||||
cssselect2==0.7.0
|
||||
defusedxml==0.7.1
|
||||
Flask==3.1.0
|
||||
geographiclib==2.0
|
||||
geopy==2.4.1
|
||||
idna==3.10
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.4
|
||||
MarkupSafe==3.0.2
|
||||
psutil==6.1.0
|
||||
pycparser==2.22
|
||||
pygame==2.6.1
|
||||
requests==2.32.3
|
||||
soupsieve==2.6
|
||||
tinycss2==1.4.0
|
||||
urllib3==2.2.3
|
||||
weatherkit==1.1.1
|
||||
webencodings==0.5.1
|
||||
Werkzeug==3.1.3
|
||||
yt-dlp==2024.12.6
|
|
@ -1 +0,0 @@
|
|||
ertdgfjkg
|
|
@ -1,110 +0,0 @@
|
|||
import time
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter.ttk import Notebook
|
||||
from tkinter import Tk
|
||||
|
||||
import requests
|
||||
|
||||
from app.widgets.abc import ThreadedTab
|
||||
|
||||
|
||||
class ChatTab(ThreadedTab):
|
||||
|
||||
def __init__(self, root: Notebook | Tk, chat_server_url: str, sender_name: str, **kwargs):
|
||||
self.chat_server_url = chat_server_url
|
||||
self.sender_name = sender_name
|
||||
self.conn = False
|
||||
self.last_msg = 0
|
||||
super().__init__(root, **kwargs)
|
||||
|
||||
def task(self):
|
||||
try:
|
||||
self.get_messages()
|
||||
if not self.conn:
|
||||
self.connected()
|
||||
self.conn = True
|
||||
except requests.ConnectionError:
|
||||
self.disconnected()
|
||||
|
||||
def build(self):
|
||||
# Create the main frame for the chat interface
|
||||
self.chat_frame = tk.Frame(self)
|
||||
self.chat_frame.pack(fill="both", expand=True)
|
||||
|
||||
# Create the status label
|
||||
self.status_label = tk.Label(self.chat_frame, text="", font=("Helvetica", 16))
|
||||
self.status_label.pack(fill="x")
|
||||
|
||||
# Create the history frame with a scrollbar
|
||||
self.history_frame = tk.Frame(self.chat_frame)
|
||||
self.history_frame.pack(fill="both", expand=True)
|
||||
|
||||
self.history_canvas = tk.Canvas(self.history_frame)
|
||||
self.history_canvas.pack(side="left", fill="both", expand=True)
|
||||
|
||||
self.scrollbar = ttk.Scrollbar(self.history_frame, orient="vertical", command=self.history_canvas.yview)
|
||||
self.scrollbar.pack(side="right", fill="y")
|
||||
|
||||
self.history_canvas.configure(yscrollcommand=self.scrollbar.set)
|
||||
self.history_canvas.bind('<Configure>', lambda e: self.history_canvas.configure(scrollregion=self.history_canvas.bbox("all")))
|
||||
|
||||
self.history_container = tk.Frame(self.history_canvas)
|
||||
self.history_container.bind("<Configure>", self.on_frame_configure)
|
||||
self.history_canvas.create_window((0, 0), window=self.history_container, anchor="nw")
|
||||
|
||||
# Create the input frame at the bottom
|
||||
self.input_frame = tk.Frame(self.chat_frame)
|
||||
self.input_frame.pack(fill="x")
|
||||
|
||||
self.message_entry = tk.Entry(self.input_frame)
|
||||
self.message_entry.pack(side="left", fill="x", expand=True, padx=5, pady=5)
|
||||
self.message_entry.bind("<Return>", lambda event: self.send_message()) # Bind Enter key to send_message
|
||||
|
||||
self.send_button = tk.Button(self.input_frame, text="Send", command=self.send_message)
|
||||
self.send_button.pack(side="right", padx=5, pady=5)
|
||||
|
||||
def on_frame_configure(self, event):
|
||||
self.history_canvas.configure(scrollregion=self.history_canvas.bbox("all"))
|
||||
|
||||
def send_message(self):
|
||||
message = self.message_entry.get()
|
||||
if message and self.conn:
|
||||
response = requests.post(f"{self.chat_server_url}/send_message", json={"content": message, "sender": self.sender_name})
|
||||
self.last_msg = response.json().get("id")
|
||||
self.display_message(self.sender_name, message)
|
||||
self.message_entry.delete(0, tk.END)
|
||||
|
||||
def display_message(self, sender, message):
|
||||
message_frame = tk.Frame(self.history_container, bg="lightgray", pady=5)
|
||||
message_frame.pack(fill="x", padx=5, pady=5)
|
||||
|
||||
message_label = tk.Label(message_frame, text=f"{sender}: {message}", anchor="w", justify="left", wraplength=300)
|
||||
message_label.pack(fill="x")
|
||||
|
||||
self.history_canvas.update_idletasks()
|
||||
self.history_canvas.yview_moveto(1)
|
||||
|
||||
def get_messages(self):
|
||||
response = requests.post(f"{self.chat_server_url}/get_messages", json={"last_id": self.last_msg})
|
||||
messages = response.json().get("messages")
|
||||
for message in messages:
|
||||
self.display_message(message["sender"], message["content"])
|
||||
|
||||
self.last_msg = messages[-1]["id"] if messages else self.last_msg
|
||||
|
||||
def connected(self):
|
||||
self.conn = True
|
||||
self.status_label.config(text="Connected to chat", fg="green")
|
||||
self.send_button.config(state="normal")
|
||||
|
||||
def disconnected(self):
|
||||
self.conn = False
|
||||
self.status_label.config(text="Disconnected from Chat", fg="red")
|
||||
self.send_button.config(state="disabled")
|
||||
|
||||
def change_sender_name(self, new_name: str):
|
||||
self.sender_name = new_name
|
||||
|
||||
def change_server_url(self, new_url: str):
|
||||
self.chat_server_url = new_url
|
|
@ -1,14 +0,0 @@
|
|||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from .abc import ThreadedLabel
|
||||
|
||||
class ClockLabel(ThreadedLabel):
|
||||
|
||||
def task(self):
|
||||
now = datetime.now()
|
||||
day_of_week = now.strftime("%A")
|
||||
time_str = now.strftime("%H:%M:%S")
|
||||
date_str = now.strftime("%Y-%m-%d")
|
||||
label_text = f"{day_of_week}, {date_str} - {time_str}"
|
||||
self.config(text=label_text)
|
|
@ -1,71 +0,0 @@
|
|||
import os
|
||||
import time
|
||||
from tkinter import Tk, Frame, Entry, Button, Label, StringVar, messagebox
|
||||
from yt_dlp import YoutubeDL
|
||||
from urllib.parse import urlparse, parse_qs, urlunparse, urlencode
|
||||
from app.widgets.abc import ThreadedTab
|
||||
|
||||
class MusicDownloadTab(ThreadedTab):
|
||||
|
||||
def __init__(self, root: Frame | Tk, **kwargs):
|
||||
self.download_url = StringVar()
|
||||
self.status = StringVar()
|
||||
self.download_queue = []
|
||||
super().__init__(root, **kwargs)
|
||||
|
||||
def build(self):
|
||||
# Create the main frame for the download interface
|
||||
self.download_frame = Frame(self)
|
||||
self.download_frame.pack(fill="both", expand=True)
|
||||
|
||||
# Create the input field for the YouTube link
|
||||
self.url_entry = Entry(self.download_frame, textvariable=self.download_url)
|
||||
self.url_entry.pack(fill="x", padx=5, pady=5)
|
||||
|
||||
# Create the download button
|
||||
self.download_button = Button(self.download_frame, text="Download", command=self.queue_download)
|
||||
self.download_button.pack(padx=5, pady=5)
|
||||
|
||||
# Create the status label
|
||||
self.status_label = Label(self.download_frame, textvariable=self.status)
|
||||
self.status_label.pack(fill="x", padx=5, pady=5)
|
||||
|
||||
def queue_download(self):
|
||||
url = self.download_url.get()
|
||||
if url:
|
||||
parsed_url = urlparse(url)
|
||||
query_params = parse_qs(parsed_url.query)
|
||||
if 'list' in query_params:
|
||||
response = messagebox.askyesno("Download Playlist", "Do you want to download the whole playlist?")
|
||||
if not response:
|
||||
query_params.pop('list', None)
|
||||
query_params.pop('index', None)
|
||||
new_query = urlencode(query_params, doseq=True)
|
||||
url = urlunparse(parsed_url._replace(query=new_query))
|
||||
self.download_queue.append(url)
|
||||
self.status.set("Queued for download")
|
||||
|
||||
def task(self):
|
||||
if self.download_queue:
|
||||
url = self.download_queue.pop(0)
|
||||
self.download_music(url)
|
||||
time.sleep(1)
|
||||
|
||||
def download_music(self, url):
|
||||
try:
|
||||
ydl_opts = {
|
||||
'format': 'bestaudio/best',
|
||||
'outtmpl': os.path.join("music", '%(title)s.%(ext)s'),
|
||||
'postprocessors': [{
|
||||
'key': 'FFmpegExtractAudio',
|
||||
'preferredcodec': 'mp3',
|
||||
'preferredquality': '192',
|
||||
}],
|
||||
}
|
||||
with YoutubeDL(ydl_opts) as ydl:
|
||||
ydl.download([url])
|
||||
self.status.set("Downloaded successfully")
|
||||
except Exception as e:
|
||||
print(e)
|
||||
self.status.set(f"Error: {str(e)}")
|
||||
time.sleep(1)
|
|
@ -1,82 +0,0 @@
|
|||
import os
|
||||
import time
|
||||
import threading
|
||||
|
||||
import pygame
|
||||
from tkinter import Tk, Frame, Button, Label, StringVar
|
||||
|
||||
from app.widgets.abc import ThreadedTab
|
||||
|
||||
|
||||
class MusicPlayerTab(ThreadedTab):
|
||||
|
||||
def __init__(self, root: Frame | Tk, **kwargs):
|
||||
self.music_dir = "music"
|
||||
self.music_files = []
|
||||
self.current_index = 0
|
||||
self.is_playing = False
|
||||
self.current_song = StringVar()
|
||||
super().__init__(root, **kwargs)
|
||||
pygame.mixer.init()
|
||||
|
||||
def build(self):
|
||||
# Create the main frame for the music player interface
|
||||
self.player_frame = Frame(self)
|
||||
self.player_frame.pack(fill="both", expand=True)
|
||||
|
||||
# Create the label to display the current song
|
||||
self.song_label = Label(self.player_frame, textvariable=self.current_song, anchor="center", wraplength=200)
|
||||
self.song_label.pack(padx=5, pady=5)
|
||||
|
||||
# Create the control buttons frame
|
||||
self.controls_frame = Frame(self.player_frame)
|
||||
self.controls_frame.pack(expand=True)
|
||||
|
||||
# Create the control buttons
|
||||
self.prev_button = Button(self.controls_frame, text="<", command=self.previous_song)
|
||||
self.prev_button.pack(side="left", padx=5)
|
||||
|
||||
self.play_pause_button = Button(self.controls_frame, text="|| / >", command=self.play_pause_music)
|
||||
self.play_pause_button.pack(side="left", padx=5)
|
||||
|
||||
self.next_button = Button(self.controls_frame, text=">", command=self.next_song)
|
||||
self.next_button.pack(side="left", padx=5)
|
||||
|
||||
self.load_music()
|
||||
|
||||
def load_music(self):
|
||||
if not os.path.exists(self.music_dir):
|
||||
os.makedirs(self.music_dir)
|
||||
|
||||
self.music_files = [f for f in os.listdir(self.music_dir) if f.endswith('.mp3')]
|
||||
if self.music_files:
|
||||
self.current_song.set(self.music_files[self.current_index])
|
||||
|
||||
def play_pause_music(self):
|
||||
if self.is_playing:
|
||||
pygame.mixer.music.pause()
|
||||
self.is_playing = False
|
||||
else:
|
||||
if pygame.mixer.music.get_busy():
|
||||
pygame.mixer.music.unpause()
|
||||
else:
|
||||
pygame.mixer.music.load(os.path.join(self.music_dir, self.music_files[self.current_index]))
|
||||
pygame.mixer.music.play()
|
||||
self.is_playing = True
|
||||
|
||||
def next_song(self):
|
||||
self.current_index = (self.current_index + 1) % len(self.music_files)
|
||||
self.current_song.set(self.music_files[self.current_index])
|
||||
pygame.mixer.music.load(os.path.join(self.music_dir, self.music_files[self.current_index]))
|
||||
pygame.mixer.music.play()
|
||||
self.is_playing = True
|
||||
|
||||
def previous_song(self):
|
||||
self.current_index = (self.current_index - 1) % len(self.music_files)
|
||||
self.current_song.set(self.music_files[self.current_index])
|
||||
pygame.mixer.music.load(os.path.join(self.music_dir, self.music_files[self.current_index]))
|
||||
pygame.mixer.music.play()
|
||||
self.is_playing = True
|
||||
|
||||
def task(self):
|
||||
self.load_music()
|
|
@ -1,83 +0,0 @@
|
|||
import tkinter as tk
|
||||
from tkinter import Frame, Button, Label, StringVar, messagebox
|
||||
from app.widgets.abc import ThreadedTab
|
||||
|
||||
class TicTacToeTab(ThreadedTab):
|
||||
|
||||
def __init__(self, root: Frame | tk.Tk, **kwargs):
|
||||
self.current_player = StringVar(value="X") # Start with player X
|
||||
self.board = [[None for _ in range(3)] for _ in range(3)] # 3x3 board
|
||||
super().__init__(root, **kwargs)
|
||||
|
||||
def build(self):
|
||||
# Create the main frame for the Tic Tac Toe interface
|
||||
self.game_frame = Frame(self)
|
||||
self.game_frame.pack(fill="both", expand=True)
|
||||
|
||||
# Create the label to display the current player
|
||||
self.player_label = Label(self.game_frame, textvariable=self.current_player, font=("Arial", 16))
|
||||
self.player_label.pack(padx=5, pady=5)
|
||||
|
||||
# Create the grid for the Tic Tac Toe board
|
||||
self.board_frame = Frame(self.game_frame)
|
||||
self.board_frame.pack(expand=True)
|
||||
|
||||
for row in range(3):
|
||||
for col in range(3):
|
||||
button = Button(
|
||||
self.board_frame,
|
||||
text="",
|
||||
font=("Arial", 24),
|
||||
width=5,
|
||||
height=2,
|
||||
command=lambda r=row, c=col: self.make_move(r, c)
|
||||
)
|
||||
button.grid(row=row, column=col, padx=5, pady=5)
|
||||
self.board[row][col] = button
|
||||
|
||||
def make_move(self, row, col):
|
||||
button = self.board[row][col]
|
||||
# Check if the cell is already occupied
|
||||
if button["text"] == "":
|
||||
button["text"] = self.current_player.get()
|
||||
if self.check_winner():
|
||||
messagebox.showinfo("Game Over", f"Player {self.current_player.get()} wins!")
|
||||
self.reset_board()
|
||||
elif self.is_draw():
|
||||
messagebox.showinfo("Game Over", "It's a draw!")
|
||||
self.reset_board()
|
||||
else:
|
||||
self.switch_player()
|
||||
else:
|
||||
messagebox.showwarning("Invalid Move", "This cell is already taken!")
|
||||
|
||||
def switch_player(self):
|
||||
self.current_player.set("O" if self.current_player.get() == "X" else "X")
|
||||
|
||||
def check_winner(self):
|
||||
# Check rows, columns, and diagonals
|
||||
for i in range(3):
|
||||
if self.board[i][0]["text"] == self.board[i][1]["text"] == self.board[i][2]["text"] != "":
|
||||
return True
|
||||
if self.board[0][i]["text"] == self.board[1][i]["text"] == self.board[2][i]["text"] != "":
|
||||
return True
|
||||
if self.board[0][0]["text"] == self.board[1][1]["text"] == self.board[2][2]["text"] != "":
|
||||
return True
|
||||
if self.board[0][2]["text"] == self.board[1][1]["text"] == self.board[2][0]["text"] != "":
|
||||
return True
|
||||
return False
|
||||
|
||||
def is_draw(self):
|
||||
# Check if all cells are filled
|
||||
return all(self.board[row][col]["text"] != "" for row in range(3) for col in range(3))
|
||||
|
||||
def reset_board(self):
|
||||
# Clear the board
|
||||
for row in range(3):
|
||||
for col in range(3):
|
||||
self.board[row][col]["text"] = ""
|
||||
self.current_player.set("X")
|
||||
|
||||
def task(self):
|
||||
# Placeholder for threaded behavior if needed
|
||||
pass
|
|
@ -1,59 +0,0 @@
|
|||
import os
|
||||
import time
|
||||
from tkinter import Tk, Frame, Entry, Button, Label, Listbox, StringVar, messagebox
|
||||
from app.widgets.abc import ThreadedTab
|
||||
|
||||
class TodoListTab(ThreadedTab):
|
||||
|
||||
def __init__(self, root: Frame | Tk, **kwargs):
|
||||
self.task = StringVar()
|
||||
self.tasks = []
|
||||
super().__init__(root, **kwargs)
|
||||
|
||||
def build(self):
|
||||
# Create the main frame for the TODO list interface
|
||||
self.todo_frame = Frame(self)
|
||||
self.todo_frame.pack(fill="both", expand=True)
|
||||
|
||||
# Entry field for adding a task
|
||||
self.task_entry = Entry(self.todo_frame, textvariable=self.task)
|
||||
self.task_entry.pack(fill="x", padx=5, pady=5)
|
||||
|
||||
# Button to add a task
|
||||
self.add_task_button = Button(self.todo_frame, text="Add Task", command=self.add_task)
|
||||
self.add_task_button.pack(padx=5, pady=5)
|
||||
|
||||
# Listbox to display tasks
|
||||
self.task_listbox = Listbox(self.todo_frame)
|
||||
self.task_listbox.pack(fill="both", expand=True, padx=5, pady=5)
|
||||
|
||||
# Button to delete a selected task
|
||||
self.delete_task_button = Button(self.todo_frame, text="Delete Selected", command=self.delete_task)
|
||||
self.delete_task_button.pack(padx=5, pady=5)
|
||||
|
||||
def add_task(self):
|
||||
task_text = self.task.get()
|
||||
if task_text:
|
||||
self.tasks.append(task_text)
|
||||
self.update_task_listbox()
|
||||
self.task.set("") # Clear the entry field
|
||||
else:
|
||||
messagebox.showwarning("Warning", "Task cannot be empty!")
|
||||
|
||||
def delete_task(self):
|
||||
selected_indices = self.task_listbox.curselection()
|
||||
if selected_indices:
|
||||
for index in selected_indices[::-1]: # Reverse to avoid index shifting issues
|
||||
del self.tasks[index]
|
||||
self.update_task_listbox()
|
||||
else:
|
||||
messagebox.showwarning("Warning", "No task selected to delete!")
|
||||
|
||||
def update_task_listbox(self):
|
||||
self.task_listbox.delete(0, "end")
|
||||
for task in self.tasks:
|
||||
self.task_listbox.insert("end", task)
|
||||
|
||||
def task(self):
|
||||
# Placeholder for threaded behavior if needed in the future
|
||||
time.sleep(1)
|
|
@ -1,62 +0,0 @@
|
|||
import os
|
||||
from tkinter import Frame, Entry, Button, Listbox, END, StringVar, Tk
|
||||
from app.widgets.abc import ThreadedTab
|
||||
|
||||
class TodoTab(ThreadedTab):
|
||||
|
||||
def __init__(self, root: Frame | Tk, **kwargs):
|
||||
self.todo_file = 'todo.list'
|
||||
self.todo_items = []
|
||||
self.load_todo_list()
|
||||
super().__init__(root, **kwargs)
|
||||
|
||||
def build(self):
|
||||
self.todo_frame = Frame(self)
|
||||
self.todo_frame.pack(fill="both", expand=True)
|
||||
|
||||
self.todo_listbox = Listbox(self.todo_frame)
|
||||
self.todo_listbox.pack(fill="both", expand=True, padx=5, pady=5)
|
||||
self.todo_listbox.bind("<Double-Button-1>", self.remove_todo)
|
||||
|
||||
self.new_todo_var = StringVar()
|
||||
self.new_todo_entry = Entry(self.todo_frame, textvariable=self.new_todo_var)
|
||||
self.new_todo_entry.pack(fill="x", padx=5, pady=5)
|
||||
self.new_todo_entry.bind("<Return>", self.add_todo)
|
||||
|
||||
self.add_button = Button(self.todo_frame, text="Add", command=self.add_todo)
|
||||
self.add_button.pack(padx=5, pady=5)
|
||||
|
||||
self.update_listbox()
|
||||
|
||||
def load_todo_list(self):
|
||||
if os.path.exists(self.todo_file):
|
||||
with open(self.todo_file, 'r') as file:
|
||||
self.todo_items = [line.strip() for line in file.readlines()]
|
||||
|
||||
def save_todo_list(self):
|
||||
with open(self.todo_file, 'w') as file:
|
||||
for item in self.todo_items:
|
||||
file.write(f"{item}\n")
|
||||
|
||||
def add_todo(self, event=None):
|
||||
new_todo = self.new_todo_var.get().strip()
|
||||
if new_todo:
|
||||
self.todo_items.append(new_todo)
|
||||
self.new_todo_var.set("")
|
||||
self.update_listbox()
|
||||
self.save_todo_list()
|
||||
|
||||
def remove_todo(self, event=None):
|
||||
selected_indices = self.todo_listbox.curselection()
|
||||
for index in selected_indices[::-1]:
|
||||
del self.todo_items[index]
|
||||
self.update_listbox()
|
||||
self.save_todo_list()
|
||||
|
||||
def update_listbox(self):
|
||||
self.todo_listbox.delete(0, END)
|
||||
for item in self.todo_items:
|
||||
self.todo_listbox.insert(END, item)
|
||||
|
||||
def task(self, *args):
|
||||
pass
|
|
@ -1,50 +0,0 @@
|
|||
import time
|
||||
import psutil
|
||||
|
||||
from .abc import ThreadedLabel
|
||||
|
||||
|
||||
class CPULabel(ThreadedLabel):
|
||||
|
||||
def task(self, *args):
|
||||
cpu_percent = psutil.cpu_percent()
|
||||
self.config(text=f'CPU: {cpu_percent}%')
|
||||
|
||||
|
||||
class RAMLabel(ThreadedLabel):
|
||||
|
||||
def task(self, *args):
|
||||
memory = psutil.virtual_memory()
|
||||
self.config(text=f'RAM: {memory.percent}%')
|
||||
|
||||
|
||||
class BatteryLabel(ThreadedLabel):
|
||||
|
||||
def task(self, *args):
|
||||
battery = psutil.sensors_battery()
|
||||
if battery is None:
|
||||
self.config(text='Battery: N/A')
|
||||
return
|
||||
battery_percent = battery.percent
|
||||
is_charging = battery.power_plugged
|
||||
time_left = battery.secsleft
|
||||
text = f'Battery: {battery_percent:.0f}%'
|
||||
if is_charging:
|
||||
text += ', Plugged in'
|
||||
else:
|
||||
text += f', ({time_left // 3600}h {time_left % 3600 // 60}m left)'
|
||||
|
||||
try:
|
||||
self.config(text=text)
|
||||
except RuntimeError:
|
||||
pass # Catch update on closed widget
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
|
||||
class NetworkLabel(ThreadedLabel):
|
||||
|
||||
def task(self, *args):
|
||||
network = psutil.net_io_counters()
|
||||
self.config(text=f'Net: {network.bytes_sent / 1024 / 1024:.2f} MB snt,'
|
||||
f' {network.bytes_recv / 1024 / 1024:.2f} MB rcv')
|
|
@ -1,125 +0,0 @@
|
|||
import threading
|
||||
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from tkinter import Frame, Label, PhotoImage, Tk
|
||||
from app.widgets.abc import ThreadedTab
|
||||
import cairosvg
|
||||
from io import BytesIO
|
||||
|
||||
class WeatherTab(ThreadedTab):
|
||||
|
||||
def __init__(self, root: Frame | Tk, city: str, **kwargs):
|
||||
self.city = city
|
||||
self.weather_info = {}
|
||||
self.weather_image = None
|
||||
self.weather_frame = None
|
||||
self.weather_label = None
|
||||
self.weather_image_label = None
|
||||
self.city_label = None
|
||||
self.real_feel_label = None
|
||||
self.wind_label = None
|
||||
self.wind_gusts_label = None
|
||||
self.air_quality_label = None
|
||||
super().__init__(root, **kwargs)
|
||||
|
||||
def build(self):
|
||||
self.weather_frame = Frame(self)
|
||||
self.weather_frame.pack(fill="both", expand=True)
|
||||
|
||||
self.city_label = Label(self.weather_frame, text=f"Weather in {self.city}", font=("Helvetica", 16))
|
||||
self.city_label.grid(row=0, column=0, columnspan=2, pady=10, sticky="ew")
|
||||
|
||||
self.weather_image_label = Label(self.weather_frame)
|
||||
self.weather_image_label.grid(row=1, column=0, padx=10, sticky="ew")
|
||||
|
||||
self.weather_label = Label(self.weather_frame, text="", font=("Helvetica", 14))
|
||||
self.weather_label.grid(row=1, column=1, padx=10, sticky="ew")
|
||||
|
||||
self.real_feel_label = Label(self.weather_frame, text="", font=("Helvetica", 12))
|
||||
self.real_feel_label.grid(row=2, column=1, padx=10, sticky="ew")
|
||||
|
||||
self.wind_label = Label(self.weather_frame, text="", font=("Helvetica", 12))
|
||||
self.wind_label.grid(row=3, column=1, padx=10, sticky="ew")
|
||||
|
||||
self.wind_gusts_label = Label(self.weather_frame, text="", font=("Helvetica", 12))
|
||||
self.wind_gusts_label.grid(row=4, column=1, padx=10, sticky="ew")
|
||||
|
||||
self.air_quality_label = Label(self.weather_frame, text="", font=("Helvetica", 12))
|
||||
self.air_quality_label.grid(row=5, column=1, padx=10, sticky="ew")
|
||||
|
||||
self.weather_frame.columnconfigure(0, weight=1)
|
||||
self.weather_frame.columnconfigure(1, weight=1)
|
||||
|
||||
# Ensure the frame fills the entire parent space
|
||||
self.grid(row=0, column=0, sticky="nsew")
|
||||
self.master.rowconfigure(0, weight=1)
|
||||
self.master.columnconfigure(0, weight=1)
|
||||
|
||||
def task(self, *args):
|
||||
self.fetch_weather_data()
|
||||
self.update_ui()
|
||||
|
||||
def fetch_weather_data(self):
|
||||
try:
|
||||
headers = {
|
||||
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36'
|
||||
}
|
||||
search_url = f"https://www.accuweather.com/en/search-locations?query={self.city}"
|
||||
search_response = requests.get(search_url, headers=headers)
|
||||
search_soup = BeautifulSoup(search_response.text, 'html.parser')
|
||||
location_list = search_soup.find('div', class_='locations-list content-module')
|
||||
if not location_list:
|
||||
print("Location list not found")
|
||||
return
|
||||
|
||||
location_link = location_list.find('a')['href']
|
||||
weather_url = f"https://www.accuweather.com{location_link}"
|
||||
weather_response = requests.get(weather_url, headers=headers)
|
||||
weather_soup = BeautifulSoup(weather_response.text, 'html.parser')
|
||||
|
||||
weather_icon_path = weather_soup.find('svg', class_='weather-icon')['data-src']
|
||||
weather_icon_url = f"https://www.accuweather.com{weather_icon_path}"
|
||||
weather_icon_response = requests.get(weather_icon_url, headers=headers)
|
||||
weather_icon_svg = weather_icon_response.content
|
||||
|
||||
# Convert SVG to PNG and resize to 50x50
|
||||
weather_icon_png = cairosvg.svg2png(bytestring=weather_icon_svg, output_width=50, output_height=50)
|
||||
weather_icon_image = PhotoImage(data=BytesIO(weather_icon_png).getvalue())
|
||||
|
||||
temperature = weather_soup.find('div', class_='temp').text
|
||||
real_feel = weather_soup.find('div', class_='real-feel').text.strip().replace('RealFeel®', '').strip()
|
||||
|
||||
details_container = weather_soup.find('div', class_='details-container')
|
||||
wind = details_container.find('span', text='Wind').find_next('span', class_='value').text.strip() if details_container else 'N/A'
|
||||
wind_gusts = details_container.find('span', text='Wind Gusts').find_next('span', class_='value').text.strip() if details_container else 'N/A'
|
||||
air_quality = details_container.find('span', text='Air Quality').find_next('span', class_='value').text.strip() if details_container else 'N/A'
|
||||
|
||||
self.weather_info = {
|
||||
'icon': weather_icon_image,
|
||||
'temperature': temperature,
|
||||
'real_feel': real_feel,
|
||||
'wind': wind,
|
||||
'wind_gusts': wind_gusts,
|
||||
'air_quality': air_quality
|
||||
}
|
||||
except Exception as e:
|
||||
print(f"Error fetching weather data: {e}")
|
||||
|
||||
def update_ui(self):
|
||||
if self.weather_info:
|
||||
weather_text = f"{self.weather_info['temperature']}"
|
||||
self.weather_label.config(text=weather_text)
|
||||
|
||||
self.weather_image_label.config(image=self.weather_info['icon'])
|
||||
self.weather_image_label.image = self.weather_info['icon']
|
||||
|
||||
self.real_feel_label.config(text=f"RealFeel: {self.weather_info['real_feel']}")
|
||||
self.wind_label.config(text=f"Wind: {self.weather_info['wind']}")
|
||||
self.wind_gusts_label.config(text=f"Wind Gusts: {self.weather_info['wind_gusts']}")
|
||||
self.air_quality_label.config(text=f"Air Quality: {self.weather_info['air_quality']}")
|
||||
|
||||
def changeCity(self, city):
|
||||
self.city = city
|
||||
self.city_label.config(text=f"Weather in {self.city}")
|
||||
threading.Thread(target=self.task).start()
|
|
@ -1,123 +0,0 @@
|
|||
import tkinter as tk
|
||||
from tkinter import Frame, Button, Label, Entry, Listbox, StringVar, messagebox
|
||||
import mysql.connector
|
||||
import requests
|
||||
from bs4 import BeautifulSoup
|
||||
from app.widgets.abc import ThreadedTab
|
||||
|
||||
class WebScrapingTab(ThreadedTab):
|
||||
|
||||
def __init__(self, root: Frame | tk.Tk, stop_event, **kwargs):
|
||||
# Inicializa los atributos necesarios antes de llamar a la clase base
|
||||
self.url = StringVar()
|
||||
self.data = []
|
||||
self.conn = None # La conexión se inicializa después
|
||||
super().__init__(root, stop_event, **kwargs) # Llama al constructor de ThreadedTab
|
||||
self.conn = self.create_database() # Crea o conecta a la base de datos
|
||||
|
||||
def build(self):
|
||||
# Main frame
|
||||
self.scraping_frame = Frame(self)
|
||||
self.scraping_frame.pack(fill="both", expand=True)
|
||||
|
||||
# Input field for URL
|
||||
Label(self.scraping_frame, text="Enter URL:", font=("Arial", 12)).pack(pady=5)
|
||||
Entry(self.scraping_frame, textvariable=self.url, font=("Arial", 12), width=50).pack(pady=5)
|
||||
|
||||
# Buttons for actions
|
||||
Button(self.scraping_frame, text="Scrape", command=self.scrape_website).pack(pady=5)
|
||||
Button(self.scraping_frame, text="View Data", command=self.view_data).pack(pady=5)
|
||||
|
||||
# Listbox to display scraped data
|
||||
self.data_listbox = Listbox(self.scraping_frame, font=("Arial", 10), width=80, height=20)
|
||||
self.data_listbox.pack(pady=10)
|
||||
|
||||
def create_database(self):
|
||||
try:
|
||||
# Conectar sin especificar la base de datos
|
||||
conn = mysql.connector.connect(
|
||||
host="127.0.0.1",
|
||||
user="santipy",
|
||||
password="1234"
|
||||
)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Crear la base de datos si no existe
|
||||
cursor.execute("CREATE DATABASE IF NOT EXISTS scraping_db CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci")
|
||||
conn.commit()
|
||||
|
||||
# Conectar a la base de datos
|
||||
conn = mysql.connector.connect(
|
||||
host="127.0.0.1",
|
||||
user="santipy",
|
||||
password="1234",
|
||||
database="scraping_db"
|
||||
)
|
||||
|
||||
# Crear la tabla si no existe
|
||||
cursor = conn.cursor()
|
||||
cursor.execute("""
|
||||
CREATE TABLE IF NOT EXISTS scraped_data (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
title VARCHAR(255),
|
||||
link TEXT
|
||||
)
|
||||
""")
|
||||
conn.commit()
|
||||
|
||||
return conn
|
||||
except mysql.connector.Error as err:
|
||||
print(f"Error al conectar o crear la base de datos: {err}")
|
||||
messagebox.showerror("Database Error", f"Error al conectar o crear la base de datos: {err}")
|
||||
return None
|
||||
|
||||
|
||||
def save_to_database(self):
|
||||
cursor = self.conn.cursor()
|
||||
query = "INSERT INTO scraped_data (title, link) VALUES (%s, %s)"
|
||||
cursor.executemany(query, self.data)
|
||||
self.conn.commit()
|
||||
|
||||
def scrape_website(self):
|
||||
url = self.url.get()
|
||||
if not url:
|
||||
messagebox.showwarning("Warning", "Please enter a URL.")
|
||||
return
|
||||
|
||||
try:
|
||||
response = requests.get(url)
|
||||
response.raise_for_status()
|
||||
except requests.RequestException as e:
|
||||
messagebox.showerror("Error", f"Failed to fetch URL: {e}")
|
||||
return
|
||||
|
||||
soup = BeautifulSoup(response.text, "html.parser")
|
||||
items = soup.select("h2 a") # Modify selector based on website structure
|
||||
|
||||
self.data = [(item.get_text(strip=True), item.get("href")) for item in items]
|
||||
|
||||
if self.data:
|
||||
self.save_to_database()
|
||||
messagebox.showinfo("Success", f"Scraped {len(self.data)} items and saved to database.")
|
||||
else:
|
||||
messagebox.showinfo("No Data", "No data found on the page.")
|
||||
|
||||
self.update_listbox()
|
||||
|
||||
def update_listbox(self):
|
||||
self.data_listbox.delete(0, "end")
|
||||
for title, link in self.data:
|
||||
self.data_listbox.insert("end", f"Title: {title} | Link: {link}")
|
||||
|
||||
def view_data(self):
|
||||
cursor = self.conn.cursor()
|
||||
cursor.execute("SELECT title, link FROM scraped_data")
|
||||
rows = cursor.fetchall()
|
||||
|
||||
self.data_listbox.delete(0, "end")
|
||||
for title, link in rows:
|
||||
self.data_listbox.insert("end", f"Title: {title} | Link: {link}")
|
||||
|
||||
def task(self):
|
||||
# Placeholder for any background task
|
||||
pass
|
|
@ -1,5 +0,0 @@
|
|||
from .ClockLabel import ClockLabel
|
||||
from .UsageLabels import CPULabel, RAMLabel
|
||||
from .WebScrapingTab import WebScrapingTab
|
||||
|
||||
__all__ = ['ClockLabel', 'CPULabel', 'RAMLabel', 'WebScrapingTab']
|
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.
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.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,28 +0,0 @@
|
|||
import threading
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
from threading import Thread
|
||||
from tkinter import Label
|
||||
|
||||
|
||||
class ThreadedLabel(ABC, Label):
|
||||
|
||||
def __init__(self, root: Label, stop_event: threading.Event, refresh_rate = None, **kwargs):
|
||||
super().__init__(root, **kwargs)
|
||||
self.stop_event = stop_event
|
||||
self.refresh_rate = refresh_rate
|
||||
self.declared_thread: Thread = threading.Thread(target=self.__loop)
|
||||
self.declared_thread.start()
|
||||
|
||||
def __loop(self):
|
||||
while not self.stop_event.is_set():
|
||||
self.task()
|
||||
start = time.time()
|
||||
while time.time() - start < self.refresh_rate:
|
||||
if self.stop_event.is_set():
|
||||
break
|
||||
time.sleep(0.2)
|
||||
|
||||
@abstractmethod
|
||||
def task(self, *args):
|
||||
pass
|
|
@ -1,33 +0,0 @@
|
|||
import threading
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
from tkinter import Tk
|
||||
from tkinter.ttk import Notebook
|
||||
from tkinter import Frame
|
||||
|
||||
class ThreadedTab(ABC, Frame):
|
||||
|
||||
def __init__(self, root: Notebook | Tk, stop_event: threading.Event, refresh_rate=1, **kwargs):
|
||||
super().__init__(root, **kwargs)
|
||||
self.stop_event = stop_event
|
||||
self.refresh_rate = refresh_rate
|
||||
self._thread = threading.Thread(target=self.__loop)
|
||||
self.build()
|
||||
self._thread.start()
|
||||
|
||||
def __loop(self):
|
||||
while not self.stop_event.is_set():
|
||||
self.task()
|
||||
start = time.time()
|
||||
while time.time() - start < self.refresh_rate:
|
||||
if self.stop_event.is_set():
|
||||
break
|
||||
time.sleep(0.2)
|
||||
|
||||
@abstractmethod
|
||||
def build(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def task(self, *args):
|
||||
raise NotImplementedError("Method not implemented")
|
|
@ -1,4 +0,0 @@
|
|||
from .ThreadedLabel import ThreadedLabel
|
||||
from .ThreadedTab import ThreadedTab
|
||||
|
||||
__all__ = ['ThreadedLabel', 'ThreadedTab']
|
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,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,73 @@
|
|||
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):
|
||||
while True:
|
||||
try:
|
||||
message = self.client_socket.recv(1024).decode("utf-8")
|
||||
if message:
|
||||
self.display_message(message)
|
||||
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