commit 79fa50fae486cccd743f41d9d3dff1acb6c86a66 Author: Valentin Korenjak Date: Wed Mar 4 20:18:24 2026 +0100 Začetna verzija (avtor U. Urbanija) diff --git a/add_song.py b/add_song.py new file mode 100644 index 0000000..f84a501 --- /dev/null +++ b/add_song.py @@ -0,0 +1,417 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +import sqlite3 +import json +import os + +DB_PATH = 'songs.db' +SETTINGS_PATH = 'settings.json' +EXPORT_PATH = 'pesmi_export.txt' + +# --------------------------------------------------------------------------- +# PREDHODNE NASTAVITVE IN POVEZAVA NA BAZO +# --------------------------------------------------------------------------- + +if not os.path.exists(SETTINGS_PATH): + print("Nastavitve manjkajo! Zaženi 'nastavitve.bat' najprej.") + exit() + +with open(SETTINGS_PATH, "r", encoding="utf-8") as f: + settings = json.load(f) + +conn = sqlite3.connect(DB_PATH) +cursor = conn.cursor() + +cursor.execute('''CREATE TABLE IF NOT EXISTS songs ( + id INTEGER PRIMARY KEY, + title TEXT, + lyrics TEXT +)''') +conn.commit() + +# --------------------------------------------------------------------------- +# POMOŽNE FUNKCIJE +# --------------------------------------------------------------------------- + +def get_next_free_id(start: int = 792) -> int: + """Vrne prvo prosto številko pesmi, ki je ≥ start (privzeto 792).""" + next_id = start + while True: + cursor.execute("SELECT 1 FROM songs WHERE id = ?", (next_id,)) + if cursor.fetchone() is None: + return next_id + next_id += 1 + +def slovenski_kljuc(naslov: str) -> str: + nadom = {'č': 'cz', 'Č': 'Cz', 'š': 'sz', 'Š': 'Sz', 'ž': 'zz', 'Ž': 'Zz'} + return ''.join(nadom.get(z, z) for z in naslov).lower() + +def prikazi_meni(): + print("\nIzberi možnost:") + print("1. Dodaj pesem") + print("2. Uredi pesem") + print("3. Prestavi pesem") + print("4. Izbriši pesem") + print("5. Izvozi bazo v TXT") + print("6. Uvozi bazo iz TXT") + print("7. Izpiši kazalo") + print("8. Zaženi projekcijo") + print("9. Zapri program") + +def uredi_vnos(lines): + processed, prazne = [], 0 + for line in lines: + if line.strip() == "": + prazne += 1 + continue + if prazne: + processed.extend([""] * prazne) + prazne = 0 + if line.strip().startswith("- "): + line = line.replace("- ", "• ", 1) + processed.append(line) + if prazne: + processed.extend([""] * prazne) + return "\n".join(processed) + +# --------------------------------------------------------------------------- +# FUNKCIJE POSAMEZNIH MENIJSKIH OPCIJ +# --------------------------------------------------------------------------- + +def dodaj_pesem(): + # -- izbira št. pesmi ---------------------------------------------------- + while True: + try: + song_id = int(input("Vnesi številko pesmi: ")) + break + except ValueError: + print("Vnesi veljavno celo število!") + + cursor.execute("SELECT title FROM songs WHERE id = ?", (song_id,)) + row = cursor.fetchone() + if row: + print(f"Želiš prepisati obstoječo pesem {song_id} – {row[0]}?") + predlagana = get_next_free_id() + print(f"Prva prosta številka nad 791 je {predlagana}.") + izbira = input("D = prepiši, N ali Enter = uporabi predlagano, " + "ali vnesi drugo številko: ").strip() + + if izbira.lower() == "d": + pass + elif izbira.lower() == "n" or izbira == "": + song_id = predlagana + else: + try: + song_id = int(izbira) + except ValueError: + print("Neveljaven vnos – uporabljam predlagano številko.") + song_id = predlagana + + # -- vnos besedila ------------------------------------------------------- + title = input("Vnesi naslov pesmi: ") + print("Vnesi besedilo (prazna vrstica = presledek, dve prazni = nova kitica).\n" + "Ko končaš, vpiši 'konec'.") + + lines = [] + while True: + line = input() + if line.strip().lower() == "konec": + break + if line.strip().startswith("- "): + line = line.replace("- ", "• ", 1) + lines.append(line) + + full_lyrics = uredi_vnos(lines) + cursor.execute("INSERT OR REPLACE INTO songs (id, title, lyrics) VALUES (?,?,?)", + (song_id, title, full_lyrics)) + conn.commit() + print("Pesem uspešno shranjena!") + +def uredi_pesem(): + try: + song_id = int(input("Vnesi številko pesmi za urejanje: ")) + except ValueError: + print("Uporabi celo število!") + return + + cursor.execute("SELECT title, lyrics FROM songs WHERE id = ?", (song_id,)) + result = cursor.fetchone() + if not result: + print("Pesem s to številko ne obstaja.") + return + + print(f"Trenutni naslov: {result[0]}") + print(f"Trenutno besedilo:\n{result[1]}") + + new_title = input("Nov naslov (pusti prazno za obdržanje): ") + print("Novo besedilo (vpiši 'konec' za zaključek, pusti prazno za obdržanje):") + + lines = [] + while True: + line = input() + if line.strip().lower() == "konec": + break + if line.strip().startswith("- "): + line = line.replace("- ", "• ", 1) + lines.append(line) + + if new_title.strip() == "": + new_title = result[0] + new_lyrics = result[1] if len(lines) == 0 else uredi_vnos(lines) + + cursor.execute("UPDATE songs SET title = ?, lyrics = ? WHERE id = ?", + (new_title, new_lyrics, song_id)) + conn.commit() + print("Pesem uspešno posodobljena!") + +def prestavi_pesem(): + try: + old_id = int(input("Vnesi številko pesmi, ki jo želiš prestaviti: ")) + new_id = int(input("Vnesi novo številko pesmi: ")) + except ValueError: + print("Uporabi cela števila!") + return + + cursor.execute("SELECT title, lyrics FROM songs WHERE id = ?", (old_id,)) + vir = cursor.fetchone() + if vir is None: + print("Pesem s to številko ne obstaja.") + return + + cursor.execute("SELECT title FROM songs WHERE id = ?", (new_id,)) + cilj = cursor.fetchone() + if cilj: + print(f"Želiš prepisati obstoječo pesem {new_id} – {cilj[0]}?") + predlagana = get_next_free_id() + print(f"Prva prosta številka nad 791 je {predlagana}.") + izbira = input("D = prepiši, N ali Enter = predlagana, ali vnesi drugo številko: ").strip() + if izbira.lower() == "d": + pass + elif izbira.lower() == "n" or izbira == "": + new_id = predlagana + else: + try: + new_id = int(izbira) + except ValueError: + print("Neveljaven vnos – uporabljam predlagano številko.") + new_id = predlagana + + cursor.execute("INSERT OR REPLACE INTO songs (id, title, lyrics) VALUES (?,?,?)", + (new_id, vir[0], vir[1])) + cursor.execute("DELETE FROM songs WHERE id = ?", (old_id,)) + conn.commit() + print("Pesem uspešno prestavljena!") + +def izbrisi_pesem(): + try: + song_id = int(input("Vnesi številko pesmi za izbris: ")) + except ValueError: + print("Uporabi celo število!") + return + + cursor.execute("SELECT title FROM songs WHERE id = ?", (song_id,)) + row = cursor.fetchone() + if row is None: + print("Pesem s to številko ne obstaja.") + return + + potrdi = input(f"Želiš res izbrisati pesem {song_id} – {row[0]}? (D/N): ").strip().lower() + if potrdi == "d": + cursor.execute("DELETE FROM songs WHERE id = ?", (song_id,)) + conn.commit() + print("Pesem uspešno izbrisana!") + else: + print("Brisanje preklicano.") + +def izvozi_bazo(): + cursor.execute("SELECT id, title, lyrics FROM songs ORDER BY id") + pesmi = cursor.fetchall() + with open(EXPORT_PATH, "w", encoding="utf-8") as f: + for pesem in pesmi: + f.write("===\n") + f.write(f"{pesem[0]}\n{pesem[1]}\n{pesem[2]}\n") + print(f"Baza je bila izvožena v '{EXPORT_PATH}'.") + +# --------------------------------------------------------------------------- +# 6. UVOZ BAZE Z NAPREDNO KONTROLO PODVOJENIH PESMI +# --------------------------------------------------------------------------- + +def uvozi_bazo(): + if not os.path.exists(EXPORT_PATH): + print(f"Datoteka '{EXPORT_PATH}' ne obstaja.") + return + + with open(EXPORT_PATH, "r", encoding="utf-8") as f: + vsebina = f.read().replace('\r\n', '\n').replace('\r', '\n') + + pesmi_raw = [p for p in vsebina.strip().split("===\n") if p.strip()] + + duplicate_mode = None # 'd', 'n', 'p' ==> vedno prepiši / preskoči / nova ID + + for pesem_raw in pesmi_raw: + vrstice = pesem_raw.strip().split("\n") + if len(vrstice) < 3: + print("Preskočena nepopolna pesem.") + continue + + try: + song_id = int(vrstice[0]) + except ValueError: + print("Neveljaven ID – preskočeno.") + continue + + title = vrstice[1] + raw_lyrics_lines = vrstice[2:] + + # -- predobdelava besedila ------------------------------------------ + processed_lines, prazne = [], 0 + for line in raw_lyrics_lines: + if line.strip().startswith("- "): + line = line.replace("- ", "• ", 1) + if line.strip() == "": + prazne += 1 + continue + if prazne: + processed_lines.extend([""] * prazne) + prazne = 0 + processed_lines.append(line) + if prazne: + processed_lines.extend([""] * prazne) + + lyrics = "\n".join(processed_lines) + + # -- obravnava podvojene številke ----------------------------------- + cursor.execute("SELECT title FROM songs WHERE id = ?", (song_id,)) + exists = cursor.fetchone() + + if exists: + # Če imamo globalni način, ga upoštevamo + if duplicate_mode is not None: + choice = duplicate_mode + else: + print(f"\nPozor: pesem št. {song_id} – \"{exists[0]}\" že obstaja.") + print("Izberi dejanje:") + print(" d … prepiši") + print(" n … preskoči") + print(" p … shrani pod prvo prosto številko ≥ 792") + print(" da … prepiši to in vse naslednje podvojene pesmi") + print(" na … preskoči to in vse naslednje podvojene pesmi") + print(" pa … shrani to in vse naslednje podvojene pesmi pod novo ID") + izbira = input("Tvoja izbira: ").strip().lower() + + if izbira in {"da", "na", "pa"}: + duplicate_mode = izbira[0] # 'd', 'n', 'p' + choice = duplicate_mode + elif izbira in {"d", "n", "p"}: + choice = izbira + else: + print("Neznana izbira – pesem preskočena.") + continue + + # izvajanje po izbrani logiki + if choice == "n": + print("Pesem preskočena.") + continue + elif choice == "p": + song_id = get_next_free_id() + print(f"Shranjujem pod novo številko {song_id}.") + # choice == 'd' -> prepis + + # -- shranjevanje pesmi --------------------------------------------- + cursor.execute("INSERT OR REPLACE INTO songs (id, title, lyrics) VALUES (?,?,?)", + (song_id, title, lyrics)) + + conn.commit() + print("\nUvoz pesmi uspešen!") + +# --------------------------------------------------------------------------- +# DODATNE FUNKCIJE (kazalo, projekcija) – nespremenjene +# --------------------------------------------------------------------------- + +def izpisi_kazalo(): + poglavja = [ + (13, " -- SLAVIMO GOSPODA -- "), + (13, "ADVENTNE"), + (27, "BOŽIČNA DEVETDNEVNICA"), + (39, "BOŽIČNE"), + (65, "POSTNE"), + (119, "VELIKONOČNE"), + (142, "ČAS MED LETOM"), + (160, "MARIJINE"), + (240, "SVETNIŠKE"), + (263, "ZAKRAMENTI"), + (383, "MAŠNE"), + (406, "DAROVANJSKE"), + (419, "OBHAJILNE"), + (470, "BLAGOSLOVNE"), + (497, "ZAKRAMENT SPRAVE"), + (505, "MAŠNIŠKO POSVEČENJE"), + (539, "POGREB"), + (696, "LITANIJE"), + (773, "KRIŽEV POT"), + (777, "ROŽNI VENEC"), + (786, "MISIJONSKE"), + (788, "ZAHVALNE"), + (790, "JUBILATE DEO in ON ŽIVI"), + (1273,"DRUGE PESMI"), + ] + + cursor.execute("SELECT id, title FROM songs ORDER BY id") + zaporedno = cursor.fetchall() + + cursor.execute("SELECT id, title FROM songs") + abecedno = cursor.fetchall() + abecedno.sort(key=lambda x: slovenski_kljuc(x[1])) + + with open("kazalo.txt", "w", encoding="utf-8") as f: + f.write("ZAPOREDNO KAZALO\n") + trenutno_poglavje, indeks = None, 0 + + for pesem in zaporedno: + while indeks < len(poglavja) and pesem[0] >= poglavja[indeks][0]: + trenutno_poglavje = poglavja[indeks][1] + f.write(f"\n--- {trenutno_poglavje} ---\n") + indeks += 1 + f.write(f"{pesem[0]}\t{pesem[1]}\n") + + f.write("\n\nABECEDNO KAZALO\n") + for pesem in abecedno: + f.write(f"{pesem[0]}\t{pesem[1]}\n") + + print("Kazalo izpisano v 'kazalo.txt'.") + +def zazeni_projekcijo(): + print("Zaganjam projekcijo …") + conn.close() + python_exe = os.sys.executable + os.execl(python_exe, python_exe, "projector.py") + +# --------------------------------------------------------------------------- +# G L A V N I Z A G O N +# --------------------------------------------------------------------------- + +def main(): + while True: + prikazi_meni() + izbira = input("Vnesi številko možnosti: ").strip() + os.system('cls' if os.name == 'nt' else 'clear') + + if izbira == "1": dodaj_pesem() + elif izbira == "2": uredi_pesem() + elif izbira == "3": prestavi_pesem() + elif izbira == "4": izbrisi_pesem() + elif izbira == "5": izvozi_bazo() + elif izbira == "6": uvozi_bazo() + elif izbira == "7": izpisi_kazalo() + elif izbira == "8": zazeni_projekcijo() + elif izbira == "9": + print("Program se zapira …") + break + else: + print("Napačna izbira, poskusi znova.") + + conn.close() + +if __name__ == "__main__": + main() diff --git a/nastavitve.py b/nastavitve.py new file mode 100644 index 0000000..d2ad558 --- /dev/null +++ b/nastavitve.py @@ -0,0 +1,377 @@ +import tkinter as tk +import json +import os +import subprocess +import sys + +DEFAULT_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, + "split_by_stanza": True +} + +SETTINGS_FILE = "settings.json" + +if not os.path.exists(SETTINGS_FILE): + with open(SETTINGS_FILE, "w") as f: + json.dump(DEFAULT_SETTINGS, f, indent=4) + +def load_settings(): + with open(SETTINGS_FILE, "r") as f: + return json.load(f) + +def save_settings(settings): + with open(SETTINGS_FILE, "w") as f: + json.dump(settings, f, indent=4) + +def run_menu(): + root = tk.Tk() + root.attributes("-fullscreen", True) + root.update_idletasks() + + settings = load_settings() + + def calculate_max_font_size(): + screen_height = root.winfo_screenheight() + screen_width = root.winfo_screenwidth() + max_lines = 12 + max_height_per_line = screen_height / max_lines + max_font_size = int(max_height_per_line * 0.8) + return min(max_font_size, settings["font_size"]) + + FONT_SIZE = calculate_max_font_size() + FONT = (settings["font_name"], FONT_SIZE, "bold" if settings.get("font_bold") else "normal") + + root.lift() + root.attributes("-topmost", True) + root.after(100, lambda: root.attributes("-topmost", False)) + root.configure(bg=settings["bg_color"]) + + screen_width = root.winfo_screenwidth() + left_right_width = int((100 - settings["screen_width_percent"]) / 2 * screen_width / 100) + + left_block = tk.Frame(root, bg="black", width=left_right_width) + left_block.pack(side="left", fill="y") + + right_block = tk.Frame(root, bg="black", width=left_right_width) + right_block.pack(side="right", fill="y") + + frame = tk.Frame(root, bg=settings["bg_color"]) + frame.pack(expand=True) + + def restart_program(): + python = sys.executable + subprocess.Popen([python] + sys.argv) + sys.exit() + + def update_menu(options, title, callback): + for widget in frame.winfo_children(): + widget.destroy() + + title_label = tk.Label(frame, text=title, font=FONT, fg=settings["fg_color"], bg=settings["bg_color"]) + title_label.pack(pady=(0, 20)) + + button_frame = tk.Frame(frame, bg=settings["bg_color"]) + button_frame.pack() + + wrap_length = screen_width * settings["screen_width_percent"] // 100 + + for key, text in options.items(): + label = tk.Label( + button_frame, + text=f"{key}. {text}", + anchor="w", + font=FONT, + fg=settings["fg_color"], + bg=settings["bg_color"], + wraplength=wrap_length + ) + label.pack(fill='x', anchor="w") + + entry = tk.Entry(frame, font=FONT, justify='center', fg=settings["fg_color"], bg=settings["bg_color"], + insertbackground=settings["fg_color"]) + entry.pack(pady=20) + + def on_enter(event=None): + choice = entry.get() + callback(choice) + + entry.bind("", on_enter) + root.after(100, entry.focus_set) + + def keep_focus(entry_widget): + def refocus(): + entry_widget.focus_set() + entry_widget.after(1000, refocus) + refocus() + + keep_focus(entry) + + def manual_input(prompt, setting_key): + for widget in frame.winfo_children(): + widget.destroy() + + label = tk.Label(frame, text=prompt, font=FONT, fg=settings["fg_color"], bg=settings["bg_color"]) + label.pack(pady=10) + + entry = tk.Entry(frame, font=FONT, justify='center', fg=settings["fg_color"], bg=settings["bg_color"], + insertbackground=settings["fg_color"]) + entry.pack(pady=20) + + def on_enter(event=None): + value = entry.get() + if setting_key in ["font_size", "screen_width_percent"]: + if value.isdigit(): + settings[setting_key] = int(value) + else: + settings[setting_key] = value + save_settings(settings) + restart_program() + + entry.bind("", on_enter) + root.after(100, entry.focus_set) + + def keep_focus(entry_widget): + def refocus(): + entry_widget.focus_set() + entry_widget.after(1000, refocus) + refocus() + + keep_focus(entry) + + def show_main_menu(): + options = { + "1": "Spremeni pisavo", + "2": "Spremeni barvo ozadja", + "3": "Spremeni barvo črk", + "4": "Spremeni barvo ozadja in črk", + "5": "Spremeni velikost črk", + "6": "Spremeni odebeljenost pisave", + "7": "Prikaz številke in kitice", + "8": "Prikaz samostojne kitice", + "9": "Spremeni širino platna", + "10": "Zaženi projekcijo", + "11": "Zapri" + } + + def handle_main_choice(choice): + if choice == "1": + show_font_menu() + elif choice == "2": + show_bg_menu() + elif choice == "3": + show_fg_menu() + elif choice == "4": + show_bg_fg_combo_menu() + elif choice == "5": + manual_input("Vnesi velikost pisave (številka):", "font_size") + elif choice == "6": + toggle_bold_choice() + elif choice == "7": + toggle_show_song_info() + elif choice == "8": + toggle_split_by_stanza() + elif choice == "9": + manual_input("Vnesi širino platna (v %):", "screen_width_percent") + elif choice == "10": + root.destroy() + subprocess.run(["python", "projector.py"]) + elif choice == "11": + root.destroy() + + update_menu(options, "Izberi možnost:", handle_main_choice) + + def toggle_bold_choice(): + options = { + "1": "Odebeljena pisava (Da)", + "2": "Odebeljena pisava (Ne)" + } + + def handle_choice(choice): + if choice == "1": + settings["font_bold"] = True + elif choice == "2": + settings["font_bold"] = False + save_settings(settings) + restart_program() + + update_menu(options, "Izberi možnost:", handle_choice) + + def toggle_show_song_info(): + options = { + "1": "Prikaz številke in kitice (Da)", + "2": "Prikaz številke in kitice (Ne)" + } + + def handle_choice(choice): + if choice == "1": + settings["show_song_info"] = True + elif choice == "2": + settings["show_song_info"] = False + save_settings(settings) + restart_program() + + update_menu(options, "Izberi možnost:", handle_choice) + + def toggle_split_by_stanza(): + options = { + "1": "Prelom po kiticah", + "2": "Prelom ob polnem platnu" + } + + def handle_choice(choice): + if choice == "1": + settings["split_by_stanza"] = True + elif choice == "2": + settings["split_by_stanza"] = False + save_settings(settings) + restart_program() + + update_menu(options, "Izberi način prikaza kitic:", handle_choice) + + def show_font_menu(): + fonts = { + "1": "Arial", + "2": "Calibri", + "3": "Times New Roman", + "4": "Verdana", + "5": "Georgia", + "6": "Helvetica", + "7": "Tahoma", + "8": "Trebuchet MS", + "9": "Courier New", + "10": "Comic Sans MS" + } + + def set_font(choice): + if choice in fonts: + settings["font_name"] = fonts[choice] + save_settings(settings) + restart_program() + + update_menu(fonts, "Izberi pisavo:", set_font) + + def show_bg_menu(): + current_fg = settings["fg_color"] + colors = { + "1": "črna", + "2": "bela", + "3": "rdeča", + "4": "zelena", + "5": "modra", + "6": "rumena", + "7": "cian", + "8": "magenta", + "9": "siva", + "10": "temno rdeča" + } + + color_values = { + "črna": "#000000", + "bela": "#FFFFFF", + "rdeča": "#FF0000", + "zelena": "#00FF00", + "modra": "#0000FF", + "rumena": "#FFFF00", + "cian": "#00FFFF", + "magenta": "#FF00FF", + "siva": "#808080", + "temno rdeča": "#800000" + } + + filtered_colors = {k: v for k, v in colors.items() if color_values.get(v, "") != current_fg} + + def set_bg(choice): + if choice in filtered_colors: + settings["bg_color"] = color_values[filtered_colors[choice]] + save_settings(settings) + restart_program() + + update_menu(filtered_colors, "Izberi barvo ozadja:", set_bg) + + def show_fg_menu(): + current_bg = settings["bg_color"] + colors = { + "1": "bela", + "2": "črna", + "3": "rdeča", + "4": "zelena", + "5": "modra", + "6": "rumena", + "7": "cian", + "8": "magenta", + "9": "siva", + "10": "temno rdeča" + } + + color_values = { + "črna": "#000000", + "bela": "#FFFFFF", + "rdeča": "#FF0000", + "zelena": "#00FF00", + "modra": "#0000FF", + "rumena": "#FFFF00", + "cian": "#00FFFF", + "magenta": "#FF00FF", + "siva": "#808080", + "temno rdeča": "#800000" + } + + filtered_colors = {k: v for k, v in colors.items() if color_values.get(v, "") != current_bg} + + def set_fg(choice): + if choice in filtered_colors: + settings["fg_color"] = color_values[filtered_colors[choice]] + save_settings(settings) + restart_program() + + update_menu(filtered_colors, "Izberi barvo črk:", set_fg) + + def show_bg_fg_combo_menu(): + combinations = { + "1": ("črna", "bela"), + "2": ("bela", "črna"), + "3": ("modra", "rumena"), + "4": ("temno rdeča", "bela"), + "5": ("zelena", "črna"), + "6": ("rdeča", "črna"), + "7": ("rumena", "črna"), + "8": ("črna", "rumena"), + "9": ("modra", "bela"), + "10": ("siva", "črna"), + } + + color_values = { + "črna": "#000000", + "bela": "#FFFFFF", + "rdeča": "#FF0000", + "zelena": "#00FF00", + "modra": "#0000FF", + "rumena": "#FFFF00", + "cian": "#00FFFF", + "magenta": "#FF00FF", + "siva": "#808080", + "temno rdeča": "#800000" + } + + def handle_combo_choice(choice): + if choice in combinations: + bg_name, fg_name = combinations[choice] + settings["bg_color"] = color_values[bg_name] + settings["fg_color"] = color_values[fg_name] + save_settings(settings) + restart_program() + + options = {key: f"{bg} ozadje / {fg} črke" for key, (bg, fg) in combinations.items()} + update_menu(options, "Izberi kombinacijo barv:", handle_combo_choice) + + show_main_menu() + root.mainloop() + +if __name__ == "__main__": + run_menu() diff --git a/projector.py b/projector.py new file mode 100644 index 0000000..03b7762 --- /dev/null +++ b/projector.py @@ -0,0 +1,427 @@ +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("", self.enter_pressed) + root.bind("", self.enter_pressed) + root.bind("", self.clear_screen) + root.bind("", self.prev_page) + root.bind("", self.search_song) + root.bind("", 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('', reset_cursor_timer) + self.root.bind_all('', 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 pod‑vrstic, 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 pod‑vrstic + 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 1‑krat 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("", 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() diff --git a/settings.json b/settings.json new file mode 100644 index 0000000..3d4467a --- /dev/null +++ b/settings.json @@ -0,0 +1,10 @@ +{ + "font_name": "Times New Roman", + "bg_color": "#000000", + "fg_color": "#FFFFFF", + "font_size": 38, + "screen_width_percent": 60, + "font_bold": true, + "show_song_info": true, + "split_by_stanza": true +} \ No newline at end of file