Files
Projekcija/projector.py

428 lines
16 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import sqlite3
import tkinter as tk
import json
import os
import math
import subprocess
DB_PATH = 'songs.db'
SETTINGS_PATH = 'settings.json'
class SongProjector:
def __init__(self, root):
self.root = root
self.conn = sqlite3.connect(DB_PATH)
self.cursor = self.conn.cursor()
self.current_song = None
self.pages = []
self.current_page_index = 0
self.song_number = ""
self.song_number_last = ""
self.all_caps_mode = False
# --------------------------------------------------
# Nastavitve
# --------------------------------------------------
if not os.path.exists(SETTINGS_PATH):
self.settings = {
"font_name": "Times New Roman",
"bg_color": "#000000",
"fg_color": "#FFFFFF",
"font_size": 32,
"screen_width_percent": 60,
"font_bold": True,
"show_song_info": True
}
with open(SETTINGS_PATH, "w", encoding="utf-8") as f:
json.dump(self.settings, f, indent=4)
else:
with open(SETTINGS_PATH, "r", encoding="utf-8") as f:
self.settings = json.load(f)
self.line_height = int(int(self.settings["font_size"]) * 1.5)
# --------------------------------------------------
# Okno
# --------------------------------------------------
root.attributes('-fullscreen', True)
root.configure(bg=self.settings["bg_color"])
root.bind("<Return>", self.enter_pressed)
root.bind("<KP_Enter>", self.enter_pressed)
root.bind("<plus>", self.clear_screen)
root.bind("<minus>", self.prev_page)
root.bind("<slash>", self.search_song)
root.bind("<Key>", self.key_pressed)
screen_width = root.winfo_screenwidth()
screen_height = root.winfo_screenheight()
screen_width_percent = self.settings["screen_width_percent"]
color_width = int(screen_width * screen_width_percent / 100)
self.screen_height = screen_height
self.color_width = color_width
black_side_width = (screen_width - color_width) // 2
self.left_frame = tk.Frame(root, bg="black", width=black_side_width, height=screen_height)
self.left_frame.pack(side="left", fill="y")
self.color_frame = tk.Frame(root, bg=self.settings["bg_color"], width=color_width, height=screen_height)
self.color_frame.place(relx=0.5, rely=0.5, anchor="center")
self.right_frame = tk.Frame(root, bg="black", width=black_side_width, height=screen_height)
self.right_frame.pack(side="right", fill="y")
font_weight = "bold" if self.settings.get("font_bold", False) else "normal"
self.label = tk.Label(
self.color_frame,
text="",
bg=self.settings["bg_color"],
fg=self.settings["fg_color"],
font=(self.settings["font_name"], int(self.settings["font_size"]), font_weight),
wraplength=color_width,
justify="center"
)
self.label.pack(expand=True)
right_edge_x = int((screen_width - color_width) / 2 + color_width)
self.song_info_label = tk.Label(
root,
text="",
bg=self.settings["bg_color"],
fg=self.settings["fg_color"],
font=(self.settings["font_name"], int(self.settings["font_size"]) - 5),
anchor="se",
justify="right"
)
self.song_info_label.place(x=right_edge_x - 10, y=screen_height - 10, anchor="se")
self.search_label = None
self.search_entry = None
self.waiting_for_song = True
# --------------------------------------------------
# Samodejno skrivanje kazalca
# --------------------------------------------------
def hide_cursor_after_delay():
self.root.config(cursor="none")
def reset_cursor_timer(event=None):
self.root.config(cursor="")
if hasattr(self, "cursor_job"):
self.root.after_cancel(self.cursor_job)
self.cursor_job = self.root.after(0, hide_cursor_after_delay)
self.root.bind_all('<Any-Motion>', reset_cursor_timer)
self.root.bind_all('<Key>', reset_cursor_timer)
self.cursor_job = self.root.after(0, hide_cursor_after_delay)
self.clear_screen()
# ------------------------------------------------------
# NOVA METODA: enakomeren prelom predolgih vrstic
# ------------------------------------------------------
def split_long_line(self, line):
"""
Vrne seznam podvrstic, ki skupaj tvorijo `line`,
pri čemer so (približno) enako dolge glede na število znakov.
"""
avg_char_width = int(self.settings["font_size"]) * 0.57
wraplength_px = self.color_width
# Koliko znakov približno gre v eno vrstico
approx_chars_per_line = max(1, int(wraplength_px / avg_char_width))
if len(line) <= approx_chars_per_line:
return [line]
# Potrebno število podvrstic
n_sub = math.ceil(len(line) / approx_chars_per_line)
words = line.split()
total_chars = sum(len(w) for w in words) + len(words) - 1 # vključno s presledki
target_len = total_chars / n_sub
sub_lines = []
current_words = []
current_len = 0
for word in words:
added_len = len(word) + (1 if current_words else 0) # presledek pred besedo, če ni prva
if current_len + added_len > target_len and len(sub_lines) < n_sub - 1:
# Začni novo vrstico
sub_lines.append(" ".join(current_words))
current_words = [word]
current_len = len(word)
else:
current_words.append(word)
current_len += added_len
if current_words:
sub_lines.append(" ".join(current_words))
return sub_lines
# ------------------------------------------------------
# Upravljanje tipkovnice
# ------------------------------------------------------
def key_pressed(self, event):
if event.keysym.isdigit():
self.song_number += event.char
elif event.keysym == "BackSpace":
self.song_number = self.song_number[:-1]
elif event.char == "*":
self.all_caps_mode = not self.all_caps_mode
self.show_page()
def enter_pressed(self, event=None):
if self.song_number:
self.load_song()
elif not self.waiting_for_song:
self.next_page()
# ------------------------------------------------------
# Nalaganje in obdelava pesmi
# ------------------------------------------------------
def load_song(self):
if not self.song_number:
return
if self.song_number == "0":
self.clear_screen()
self.song_number = ""
return
# Ponastavitev izgleda barvnega območja
self.color_frame.config(bg=self.settings["bg_color"], width=self.color_width, height=self.screen_height)
self.color_frame.place(relx=0.5, rely=0.5, anchor="center")
self.label.config(bg=self.settings["bg_color"], fg=self.settings["fg_color"])
self.label.pack(expand=True)
# Posebni ukazi
if self.song_number == "9999":
subprocess.Popen(["shutdown", "/s", "/f", "/t", "0"])
return
elif self.song_number == "7777":
subprocess.Popen(["python", "nastavitve.py"])
self.exit_program()
return
elif self.song_number == "8888":
self.exit_program()
return
elif self.song_number == "6666":
subprocess.Popen(["python", "add_song.py"])
self.exit_program()
return
try:
song_id = int(self.song_number)
self.cursor.execute("SELECT lyrics FROM songs WHERE id=?", (song_id,))
result = self.cursor.fetchone()
if result:
lyrics = result[0]
max_height = self.screen_height - (self.line_height * 1.5)
avg_char_width = int(self.settings["font_size"]) * 0.57
wraplength = self.color_width
# -------------------------------------------
# 1. Razbijanje besedila na (pod-)vrstice
# -------------------------------------------
raw_lines = lyrics.strip().splitlines()
processed_lines = []
for raw in raw_lines:
if raw.strip() == "":
processed_lines.append("") # ohranimo prazno vrstico
else:
processed_lines.extend(self.split_long_line(raw))
# -------------------------------------------
# 2. Razdeljevanje vrstic na strani
# -------------------------------------------
pages = []
current_page_lines = []
current_height = 0
blank_line_count = 0
for line in processed_lines:
is_blank = (line.strip() == "")
if is_blank:
blank_line_count += 1
else:
blank_line_count = 0
approx_line_length = len(line)
approx_line_count = 1 if is_blank else math.ceil((approx_line_length * avg_char_width) / wraplength)
needed_height = self.line_height * approx_line_count
# Prazna vrstica več kot 1krat pomeni nova kitica ⇒ nova stran
if blank_line_count >= 2:
if current_page_lines:
pages.append("\n".join(current_page_lines).strip())
current_page_lines = []
current_height = 0
blank_line_count = 0
continue
# Če čez spodnji rob strani …
if current_height + needed_height > max_height + self.line_height // 2:
# poskusimo prelomiti na prazni vrstici znotraj strani
found_split = False
for i in reversed(range(len(current_page_lines))):
if current_page_lines[i].strip() == "":
pages.append("\n".join(current_page_lines[:i]).strip())
current_page_lines = current_page_lines[i + 1:]
current_height = sum(
self.line_height * (
1 if l.strip() == "" else math.ceil(len(l) * avg_char_width / wraplength)
)
for l in current_page_lines
)
found_split = True
break
if not found_split:
pages.append("\n".join(current_page_lines).strip())
current_page_lines = []
current_height = 0
current_page_lines.append(line)
current_height += needed_height
if current_page_lines:
pages.append("\n".join(current_page_lines).strip())
self.pages = pages
self.current_page_index = 0
self.waiting_for_song = False
self.song_number_last = self.song_number
self.show_page()
else:
self.label.config(text="")
self.pages = []
self.current_page_index = 0
self.waiting_for_song = True
self.song_number_last = self.song_number
self.song_info_label.config(text=self.song_number_last)
self.song_info_label.lift()
except Exception as e:
self.label.config(text=f"Napaka: {e}")
finally:
self.song_number = ""
# ------------------------------------------------------
# Prikaz trenutne strani
# ------------------------------------------------------
def show_page(self):
if self.pages:
text = self.pages[self.current_page_index]
if self.all_caps_mode:
text = text.upper()
self.label.config(text=text)
if self.settings.get("show_song_info", False):
current_page = self.current_page_index + 1
total_pages = len(self.pages)
self.song_info_label.config(text=f"{self.song_number_last} {current_page}/{total_pages}")
self.song_info_label.lift()
else:
self.song_info_label.config(text="")
# ------------------------------------------------------
# Navigacija po straneh
# ------------------------------------------------------
def next_page(self, event=None):
if self.pages and self.current_page_index + 1 < len(self.pages):
self.current_page_index += 1
self.show_page()
def prev_page(self, event=None):
if self.pages and self.current_page_index > 0:
self.current_page_index -= 1
self.show_page()
# ------------------------------------------------------
# Očisti zaslon
# ------------------------------------------------------
def clear_screen(self, event=None):
self.label.config(text="")
self.label.pack_forget()
self.color_frame.config(bg="black", width=self.color_width, height=self.screen_height)
self.color_frame.place(relx=0.5, rely=0.5, anchor="center")
self.song_info_label.config(text="")
self.waiting_for_song = True
# ------------------------------------------------------
# Iskanje pesmi
# ------------------------------------------------------
def search_song(self, event=None):
self.clear_screen()
if self.search_label:
self.search_label.destroy()
if self.search_entry:
self.search_entry.destroy()
self.search_label = tk.Label(
self.color_frame,
text="Vpiši del naslova:",
bg=self.settings["bg_color"],
fg=self.settings["fg_color"],
font=(self.settings["font_name"], int(self.settings["font_size"])),
wraplength=self.color_width,
justify="center"
)
self.search_label.pack(pady=(40, 10))
self.search_entry = tk.Entry(
self.color_frame,
font=(self.settings["font_name"], int(self.settings["font_size"])),
justify="center",
bg=self.settings["bg_color"],
fg=self.settings["fg_color"],
insertbackground=self.settings["fg_color"],
borderwidth=0,
highlightthickness=0
)
self.search_entry.pack()
self.search_entry.focus()
self.search_entry.bind("<Return>", self.perform_search)
def perform_search(self, event=None):
query = self.search_entry.get().strip().lower()
self.search_label.destroy()
self.search_entry.destroy()
if query:
self.cursor.execute("SELECT id, title FROM songs")
all_results = self.cursor.fetchall()
matched = []
for song_id, title in all_results:
if query in title.lower():
matched.append((song_id, title))
if matched:
found = "\n".join(f"{id}: {title}" for id, title in matched)
self.label.pack(expand=True)
self.label.config(text=found)
else:
self.label.pack(expand=True)
self.label.config(text="Ni zadetkov.")
# ------------------------------------------------------
# Izhod
# ------------------------------------------------------
def exit_program(self, event=None):
self.conn.close()
self.root.destroy()
# ----------------------------------------------------------
# Zagon aplikacije
# ----------------------------------------------------------
if __name__ == "__main__":
root = tk.Tk()
app = SongProjector(root)
root.mainloop()