Implemetacion de 2 solapas el tiempo y musica
This commit is contained in:
parent
9d1bbdaa85
commit
19b77c2349
|
@ -1,8 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/app" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -16,17 +16,34 @@ class ConfigMgr:
|
|||
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")
|
||||
file.write(chat_config)
|
||||
|
||||
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
|
||||
|
@ -43,6 +60,7 @@ class ConfigMgr:
|
|||
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")
|
||||
|
||||
|
@ -66,6 +84,20 @@ class ConfigMgr:
|
|||
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)
|
||||
|
||||
|
@ -74,6 +106,9 @@ class ConfigMgr:
|
|||
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)
|
||||
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -2,7 +2,7 @@ import threading
|
|||
import time
|
||||
import flask
|
||||
from werkzeug.serving import make_server
|
||||
|
||||
import logging
|
||||
|
||||
class Server:
|
||||
"""
|
||||
|
@ -30,6 +30,11 @@ class Server:
|
|||
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()
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,2 +1,2 @@
|
|||
1|Santi|Hola como estas?
|
||||
2|Santi|Andres no me copies el TO DO
|
||||
1|User|Hola
|
||||
2|feo|hola
|
||||
|
|
|
@ -1,4 +1,7 @@
|
|||
[Chat]
|
||||
server = http://localhost:2020
|
||||
name = Santi
|
||||
name = feo
|
||||
|
||||
[Weather]
|
||||
city = Denia
|
||||
|
||||
|
|
167
app/main.py
167
app/main.py
|
@ -7,124 +7,163 @@ from app.chat_server.Server import Server
|
|||
from app.widgets import ClockLabel
|
||||
from app.ConfigMgr import ConfigMgr
|
||||
from app.widgets.ChatTab import ChatTab
|
||||
from app.widgets.MusicDownloadTab import MusicDownloadTab
|
||||
from app.widgets.MusicPlayerTab import MusicPlayerTab
|
||||
from app.widgets.TodoTab import TodoTab
|
||||
from app.widgets.UsageLabels import CPULabel, RAMLabel, BatteryLabel, NetworkLabel
|
||||
from app.widgets.WeatherTab import WeatherTab
|
||||
|
||||
|
||||
# Evento para detener threads de manera segura
|
||||
stop_event = threading.Event()
|
||||
|
||||
def on_closing():
|
||||
"""Gestiona el cierre de la aplicación de manera segura."""
|
||||
# Detiene todos los threads relacionados con stop_event
|
||||
# Kill all threads that are linked to the stop_event
|
||||
# This ensures that execution is thread-safe
|
||||
stop_event.set()
|
||||
|
||||
# Cierra la ventana principal
|
||||
# Close the main window
|
||||
root.quit()
|
||||
root.destroy()
|
||||
|
||||
def on_config_changed():
|
||||
"""Actualiza la configuración del servidor y nombre de usuario en el chat."""
|
||||
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"])
|
||||
|
||||
# Crea la ventana principal
|
||||
# Create the main window
|
||||
root = tk.Tk()
|
||||
root.title("Responsive Window") # Título de la ventana
|
||||
root.geometry("1150x700") # Tamaño inicial de la ventana
|
||||
root.title("Responsive Window")
|
||||
root.geometry("1150x700")
|
||||
|
||||
# Inicializa el gestor de configuración
|
||||
config_manager = ConfigMgr(root, config_changed_listener=on_config_changed)
|
||||
|
||||
# Inicializa el servidor de chat
|
||||
server = Server(host="localhost", port=2020, stop_event=stop_event)
|
||||
|
||||
# Configura la ventana principal para que sea responsive
|
||||
root.columnconfigure(0, weight=0) # Columna izquierda con tamaño fijo
|
||||
root.columnconfigure(1, weight=1) # Columna central ajustable
|
||||
root.columnconfigure(2, weight=0) # Columna derecha con tamaño fijo
|
||||
root.rowconfigure(0, weight=1) # Fila principal ajustable
|
||||
root.rowconfigure(1, weight=0) # Barra de estado con 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)
|
||||
|
||||
# Crea el menú superior
|
||||
# Create the top menu
|
||||
menu_bar = Menu(root)
|
||||
|
||||
# Menú Archivo
|
||||
file_menu = Menu(menu_bar, tearoff=0)
|
||||
file_menu.add_command(label="New") # Comando Nuevo
|
||||
file_menu.add_command(label="Open") # Comando Abrir
|
||||
file_menu.add_separator() # Separador visual
|
||||
file_menu.add_command(label="Config", command=config_manager.display_config_window) # Configuración
|
||||
file_menu.add_command(label="Exit", command=on_closing) # Salir
|
||||
file_menu.add_command(label="New")
|
||||
file_menu.add_command(label="Open")
|
||||
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)
|
||||
|
||||
# Menú Edición
|
||||
edit_menu = Menu(menu_bar, tearoff=0)
|
||||
edit_menu.add_command(label="Copy") # Comando Copiar
|
||||
edit_menu.add_command(label="Paste") # Comando Pegar
|
||||
edit_menu.add_command(label="Copy")
|
||||
edit_menu.add_command(label="Paste")
|
||||
|
||||
# Añade los menús al menú principal
|
||||
menu_bar.add_cascade(label="File", menu=file_menu)
|
||||
menu_bar.add_cascade(label="Edit", menu=edit_menu)
|
||||
|
||||
# Asigna el menú a la ventana principal
|
||||
root.config(menu=menu_bar)
|
||||
|
||||
# Crea los marcos laterales y central
|
||||
frame_left = tk.Frame(root, bg="lightblue", width=200) # Marco izquierdo
|
||||
frame_center = tk.Frame(root, bg="white") # Marco central
|
||||
chat_frame = ChatTab(root, chat_server_url=config_manager.config["Chat"]["server"],
|
||||
sender_name=config_manager.config["Chat"]["name"],
|
||||
stop_event=stop_event, width=200, bg="lightgreen") # Marco derecho para el chat
|
||||
# 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)
|
||||
|
||||
# Coloca los marcos en la cuadrícula
|
||||
frame_left.grid(row=0, column=0, sticky="ns") # Marco izquierdo
|
||||
frame_center.grid(row=0, column=1, sticky="nsew") # Marco central
|
||||
chat_frame.grid(row=0, column=2, sticky="ns") # Marco derecho
|
||||
# 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")
|
||||
|
||||
# Configura tamaños fijos para los marcos laterales
|
||||
# Configure the fixed sizes of the side frames
|
||||
frame_left.grid_propagate(False)
|
||||
chat_frame.grid_propagate(False)
|
||||
frame_right.grid_propagate(False)
|
||||
|
||||
# Divide el marco central en dos partes (superior ajustable e inferior fijo)
|
||||
frame_center.rowconfigure(0, weight=1) # Parte superior ajustable
|
||||
frame_center.rowconfigure(1, weight=0) # Parte inferior fija
|
||||
frame_center.columnconfigure(0, weight=1) # Ancho ajustable
|
||||
# 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)
|
||||
|
||||
# Crea sub-marcos dentro del marco central
|
||||
frame_top = tk.Frame(frame_center, bg="lightyellow") # Parte superior
|
||||
frame_bottom = tk.Frame(frame_center, bg="lightgray", height=100) # Parte inferior
|
||||
# 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")
|
||||
|
||||
# Coloca los sub-marcos en el marco central
|
||||
frame_top.grid(row=0, column=0, sticky="nsew") # Parte superior
|
||||
frame_bottom.grid(row=1, column=0, sticky="ew") # Parte inferior
|
||||
# Adjust the remaining rows to occupy the rest of the space
|
||||
frame_left.rowconfigure(1, weight=2)
|
||||
frame_left.rowconfigure(2, weight=2)
|
||||
|
||||
# Fija el tamaño de la parte inferior
|
||||
# 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)
|
||||
|
||||
# Create subframes within the central frame
|
||||
frame_top = tk.Frame(frame_center, bg="lightyellow")
|
||||
frame_bottom = tk.Frame(frame_center, bg="lightgray", height=100)
|
||||
|
||||
# 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")
|
||||
|
||||
# Fix the size of the bottom part
|
||||
frame_bottom.grid_propagate(False)
|
||||
|
||||
# Crea la barra de estado
|
||||
status_bar = tk.Label(root, text="Status bar", bg="lightgray", anchor="w") # Barra de estado
|
||||
# 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")
|
||||
|
||||
# Configura un cuaderno (notebook) para widgets
|
||||
# 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)
|
||||
|
||||
# Añade etiquetas de uso del sistema
|
||||
label_cpu = CPULabel(status_bar, bg="lightgreen", font=("Helvetica", 10), relief="groove", anchor="center", width=10, stop_event=stop_event) # Uso de CPU
|
||||
label_ram = RAMLabel(status_bar, bg="lightcoral", font=("Helvetica", 10), relief="groove", anchor="center", width=10, stop_event=stop_event) # Uso de RAM
|
||||
label_battery = BatteryLabel(status_bar, bg="lightblue", font=("Helvetica", 10), relief="groove", anchor="center", width=20, stop_event=stop_event) # Batería
|
||||
label_net = NetworkLabel(status_bar, text="Network", bg="lightpink", font=("Helvetica", 10), relief="groove", anchor="center", width=20, stop_event=stop_event) # Red
|
||||
label_time = ClockLabel(status_bar, font=("Helvetica", 12), bd=1, fg="darkblue", relief="sunken", anchor="center", width=20, stop_event=stop_event) # Reloj
|
||||
# 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")
|
||||
|
||||
# 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")
|
||||
|
||||
# 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
|
||||
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)
|
||||
|
||||
# Coloca las etiquetas en la barra de estado
|
||||
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)
|
||||
|
||||
# Configura la acción para el cierre de la ventana
|
||||
root.protocol("WM_DELETE_WINDOW", on_closing)
|
||||
|
||||
# Inicia la aplicación
|
||||
# Run the application
|
||||
root.mainloop()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1,13 +1,28 @@
|
|||
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
|
||||
|
|
|
@ -26,7 +26,6 @@ class ChatTab(ThreadedTab):
|
|||
self.conn = True
|
||||
except requests.ConnectionError:
|
||||
self.disconnected()
|
||||
time.sleep(1)
|
||||
|
||||
def build(self):
|
||||
# Create the main frame for the chat interface
|
||||
|
|
|
@ -11,6 +11,4 @@ class ClockLabel(ThreadedLabel):
|
|||
time_str = now.strftime("%H:%M:%S")
|
||||
date_str = now.strftime("%Y-%m-%d")
|
||||
label_text = f"{day_of_week}, {date_str} - {time_str}"
|
||||
try: self.config(text=label_text)
|
||||
except RuntimeError: pass
|
||||
time.sleep(0.5)
|
||||
self.config(text=label_text)
|
|
@ -0,0 +1,71 @@
|
|||
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)
|
|
@ -0,0 +1,82 @@
|
|||
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()
|
|
@ -0,0 +1,59 @@
|
|||
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)
|
|
@ -0,0 +1,62 @@
|
|||
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
|
|
@ -3,21 +3,20 @@ import psutil
|
|||
|
||||
from .abc import ThreadedLabel
|
||||
|
||||
|
||||
class CPULabel(ThreadedLabel):
|
||||
|
||||
def task(self, *args):
|
||||
cpu_percent = psutil.cpu_percent()
|
||||
try: self.config(text=f'CPU: {cpu_percent}%')
|
||||
except RuntimeError: pass
|
||||
time.sleep(1)
|
||||
self.config(text=f'CPU: {cpu_percent}%')
|
||||
|
||||
|
||||
class RAMLabel(ThreadedLabel):
|
||||
|
||||
def task(self, *args):
|
||||
memory = psutil.virtual_memory()
|
||||
try: self.config(text=f'RAM: {memory.percent}%')
|
||||
except RuntimeError: pass
|
||||
time.sleep(1)
|
||||
self.config(text=f'RAM: {memory.percent}%')
|
||||
|
||||
|
||||
class BatteryLabel(ThreadedLabel):
|
||||
|
||||
|
@ -35,16 +34,17 @@ class BatteryLabel(ThreadedLabel):
|
|||
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
|
||||
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()
|
||||
try: self.config(text=f'Net: {network.bytes_sent / 1024 / 1024:.2f} MB snt,'
|
||||
self.config(text=f'Net: {network.bytes_sent / 1024 / 1024:.2f} MB snt,'
|
||||
f' {network.bytes_recv / 1024 / 1024:.2f} MB rcv')
|
||||
except RuntimeError: pass # Catch update on closed widget
|
||||
time.sleep(1)
|
|
@ -0,0 +1,125 @@
|
|||
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()
|
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,4 +1,5 @@
|
|||
import threading
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
from threading import Thread
|
||||
from tkinter import Label
|
||||
|
@ -6,16 +7,21 @@ from tkinter import Label
|
|||
|
||||
class ThreadedLabel(ABC, Label):
|
||||
|
||||
def __init__(self, root: Label, stop_event: threading.Event, **kwargs):
|
||||
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):
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import threading
|
||||
import time
|
||||
from abc import ABC, abstractmethod
|
||||
from tkinter import Tk
|
||||
from tkinter.ttk import Notebook
|
||||
|
@ -6,9 +7,10 @@ from tkinter import Frame
|
|||
|
||||
class ThreadedTab(ABC, Frame):
|
||||
|
||||
def __init__(self, root: Notebook | Tk, stop_event: threading.Event, **kwargs):
|
||||
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()
|
||||
|
@ -16,6 +18,11 @@ class ThreadedTab(ABC, Frame):
|
|||
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):
|
||||
|
|
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