make it beauty and shiny
This commit is contained in:
@@ -6,4 +6,6 @@ sentence-transformers==2.2.2
|
||||
transformers==4.28.1
|
||||
torch==1.13.1
|
||||
numpy==1.24.2
|
||||
python-docx
|
||||
python-docx
|
||||
openpyxl
|
||||
python-pptx
|
||||
375
uff_app.py
375
uff_app.py
@@ -7,11 +7,21 @@ import zipfile
|
||||
import io
|
||||
import traceback
|
||||
|
||||
# NEU: Word Support
|
||||
# --- OPTIONALE IMPORTE ---
|
||||
try:
|
||||
import docx
|
||||
except ImportError:
|
||||
docx = None # Fallback, falls nicht installiert
|
||||
docx = None
|
||||
|
||||
try:
|
||||
import openpyxl
|
||||
except ImportError:
|
||||
openpyxl = None
|
||||
|
||||
try:
|
||||
from pptx import Presentation
|
||||
except ImportError:
|
||||
Presentation = None
|
||||
|
||||
from sentence_transformers import SentenceTransformer, util
|
||||
from rapidfuzz import process, fuzz
|
||||
@@ -21,8 +31,8 @@ from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
||||
QHBoxLayout, QLineEdit, QPushButton, QLabel,
|
||||
QFileDialog, QProgressBar, QMessageBox,
|
||||
QListWidget, QListWidgetItem, QSplitter, QFrame,
|
||||
QSplashScreen, QScrollArea, QStyle)
|
||||
from PyQt6.QtGui import QDesktopServices, QPixmap, QCursor, QAction
|
||||
QSplashScreen, QScrollArea, QStyle, QGraphicsDropShadowEffect)
|
||||
from PyQt6.QtGui import QDesktopServices, QPixmap, QCursor, QAction, QColor, QPalette, QFont
|
||||
|
||||
# --- 0. LOGGING & SETUP ---
|
||||
|
||||
@@ -51,12 +61,117 @@ class Logger(object):
|
||||
sys.stdout = Logger()
|
||||
sys.stderr = sys.stdout
|
||||
|
||||
print(f"--- START LOGGING ---")
|
||||
print(f"Logfile: {log_file_path}")
|
||||
# --- STYLESHEET ---
|
||||
STYLESHEET = """
|
||||
QMainWindow {
|
||||
background-color: #f4f7f6;
|
||||
}
|
||||
|
||||
# Check ob docx installiert ist und warnen im Log
|
||||
if docx is None:
|
||||
print("WARNUNG: 'python-docx' ist nicht installiert. .docx Dateien werden ignoriert.")
|
||||
/* Sidebar Styles */
|
||||
QFrame#Sidebar {
|
||||
background-color: #2c3e50;
|
||||
border: none;
|
||||
}
|
||||
QLabel#SidebarTitle {
|
||||
color: #ecf0f1;
|
||||
font-weight: bold;
|
||||
font-size: 16px;
|
||||
padding: 10px;
|
||||
}
|
||||
QListWidget {
|
||||
background-color: #34495e;
|
||||
color: #ecf0f1;
|
||||
border: none;
|
||||
outline: none;
|
||||
font-size: 13px;
|
||||
}
|
||||
QListWidget::item {
|
||||
padding: 8px;
|
||||
border-bottom: 1px solid #2c3e50;
|
||||
}
|
||||
QListWidget::item:selected {
|
||||
background-color: #1abc9c;
|
||||
color: white;
|
||||
}
|
||||
QListWidget::item:hover {
|
||||
background-color: #16a085;
|
||||
}
|
||||
|
||||
/* Sidebar Buttons */
|
||||
QPushButton#SidebarBtn {
|
||||
background-color: #34495e;
|
||||
color: #bdc3c7;
|
||||
border: 1px solid #2c3e50;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
border-radius: 4px;
|
||||
margin: 2px 10px;
|
||||
}
|
||||
QPushButton#SidebarBtn:hover {
|
||||
background-color: #1abc9c;
|
||||
color: white;
|
||||
border: 1px solid #16a085;
|
||||
}
|
||||
QPushButton#CancelBtn {
|
||||
background-color: #e74c3c;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
border-radius: 4px;
|
||||
margin: 10px;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
/* Main Area */
|
||||
QLineEdit {
|
||||
padding: 10px;
|
||||
border: 1px solid #bdc3c7;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
background-color: white;
|
||||
selection-background-color: #3498db;
|
||||
}
|
||||
QLineEdit:focus {
|
||||
border: 2px solid #3498db;
|
||||
}
|
||||
|
||||
QPushButton#SearchBtn {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
font-weight: bold;
|
||||
border-radius: 20px;
|
||||
padding: 10px 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
QPushButton#SearchBtn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
QPushButton#SearchBtn:pressed {
|
||||
background-color: #1f618d;
|
||||
}
|
||||
|
||||
/* Scroll Area & Results */
|
||||
QScrollArea {
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
QWidget#ResultsContainer {
|
||||
background-color: transparent;
|
||||
}
|
||||
QLabel#StatusLabel {
|
||||
color: #7f8c8d;
|
||||
font-size: 12px;
|
||||
margin-left: 10px;
|
||||
}
|
||||
QProgressBar {
|
||||
border: none;
|
||||
background-color: #ecf0f1;
|
||||
height: 4px;
|
||||
text-align: center;
|
||||
}
|
||||
QProgressBar::chunk {
|
||||
background-color: #1abc9c;
|
||||
}
|
||||
"""
|
||||
|
||||
def qt_message_handler(mode, context, message):
|
||||
msg_lower = message.lower()
|
||||
@@ -65,7 +180,6 @@ def qt_message_handler(mode, context, message):
|
||||
"unable to create font", "fontbbox", "script"
|
||||
]
|
||||
if any(k in msg_lower for k in ignore_keywords): return
|
||||
|
||||
try:
|
||||
sys.stdout.write(f"[Qt] {message}\n")
|
||||
except: pass
|
||||
@@ -74,45 +188,58 @@ qInstallMessageHandler(qt_message_handler)
|
||||
os.environ["QT_LOGGING_RULES"] = "qt.text.font.db=false;qt.qpa.fonts=false"
|
||||
|
||||
|
||||
# --- NEUE KOMPONENTE: Ein einzelnes Suchergebnis als Widget ---
|
||||
# --- WIDGET: Modernes Suchergebnis (Fixed Tooltips) ---
|
||||
class SearchResultItem(QFrame):
|
||||
"""
|
||||
Stellt ein einzelnes Suchergebnis als 'Karte' dar.
|
||||
"""
|
||||
def __init__(self, filename, filepath, snippet, parent=None):
|
||||
super().__init__(parent)
|
||||
self.filepath = filepath
|
||||
|
||||
# WICHTIG: Tooltip auf das gesamte Frame setzen, nicht nur auf Kinder
|
||||
self.setToolTip(filepath)
|
||||
|
||||
# Design der Karte
|
||||
self.setFrameShape(QFrame.Shape.StyledPanel)
|
||||
self.setStyleSheet("""
|
||||
SearchResultItem {
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 5px;
|
||||
margin-bottom: 5px;
|
||||
background-color: white;
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
}
|
||||
SearchResultItem:hover {
|
||||
background-color: #f0f8ff;
|
||||
border: 1px solid #2980b9;
|
||||
border: 1px solid #3498db;
|
||||
background-color: #fbfbfb;
|
||||
}
|
||||
""")
|
||||
|
||||
# Schatten-Effekt
|
||||
shadow = QGraphicsDropShadowEffect(self)
|
||||
shadow.setBlurRadius(10)
|
||||
shadow.setXOffset(0)
|
||||
shadow.setYOffset(2)
|
||||
shadow.setColor(QColor(0, 0, 0, 30))
|
||||
self.setGraphicsEffect(shadow)
|
||||
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(10, 10, 10, 10)
|
||||
layout.setContentsMargins(15, 15, 15, 15)
|
||||
layout.setSpacing(5)
|
||||
|
||||
# 1. Dateiname
|
||||
# 1. Titel (Dateiname)
|
||||
self.btn_title = QPushButton(filename)
|
||||
self.btn_title.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
# MouseTracking aktivieren hilft manchmal bei schnellen Bewegungen
|
||||
self.btn_title.setMouseTracking(True)
|
||||
self.btn_title.setStyleSheet("""
|
||||
QPushButton {
|
||||
text-align: left;
|
||||
font-weight: bold;
|
||||
font-size: 14pt;
|
||||
color: #2980b9;
|
||||
font-size: 16px;
|
||||
color: #2c3e50;
|
||||
border: none;
|
||||
background: transparent;
|
||||
padding: 0px;
|
||||
}
|
||||
QPushButton:hover {
|
||||
color: #3498db;
|
||||
text-decoration: underline;
|
||||
}
|
||||
""")
|
||||
@@ -121,26 +248,30 @@ class SearchResultItem(QFrame):
|
||||
# 2. Snippet
|
||||
self.lbl_snippet = QLabel(snippet)
|
||||
self.lbl_snippet.setWordWrap(True)
|
||||
self.lbl_snippet.setStyleSheet("color: #444; font-size: 10pt; margin-top: 5px;")
|
||||
self.lbl_snippet.setStyleSheet("color: #555; font-size: 13px; line-height: 1.4;")
|
||||
|
||||
# 3. Pfad (unten, klein)
|
||||
path_layout = QHBoxLayout()
|
||||
lbl_icon = QLabel("📄")
|
||||
lbl_icon.setStyleSheet("font-size: 10px; color: #95a5a6;")
|
||||
|
||||
# 3. Pfad
|
||||
self.lbl_path = QLabel(filepath)
|
||||
self.lbl_path.setStyleSheet("color: #888; font-size: 8pt; margin-top: 5px;")
|
||||
self.lbl_path.setStyleSheet("color: #95a5a6; font-size: 11px;")
|
||||
|
||||
path_layout.addWidget(lbl_icon)
|
||||
path_layout.addWidget(self.lbl_path)
|
||||
path_layout.addStretch()
|
||||
|
||||
layout.addWidget(self.btn_title)
|
||||
layout.addWidget(self.lbl_snippet)
|
||||
layout.addWidget(self.lbl_path)
|
||||
layout.addLayout(path_layout)
|
||||
|
||||
def open_file(self):
|
||||
print(f"Öffne Datei: {self.filepath}")
|
||||
target_path = self.filepath
|
||||
if " :: " in target_path:
|
||||
target_path = target_path.split(" :: ")[0]
|
||||
|
||||
url = QUrl.fromLocalFile(target_path)
|
||||
success = QDesktopServices.openUrl(url)
|
||||
if not success:
|
||||
print("Fehler: Konnte Datei nicht öffnen.")
|
||||
QDesktopServices.openUrl(url)
|
||||
|
||||
|
||||
# --- 1. DATENBANK MANAGER ---
|
||||
@@ -191,7 +322,6 @@ class DatabaseHandler:
|
||||
def search(self, query):
|
||||
if not query.strip() or not self.model: return []
|
||||
|
||||
# 1. Semantik
|
||||
q_vec = self.model.encode(query, convert_to_tensor=False)
|
||||
conn = sqlite3.connect(self.db_name)
|
||||
cursor = conn.cursor()
|
||||
@@ -207,7 +337,6 @@ class DatabaseHandler:
|
||||
scores = np.clip(scores, 0, 1)
|
||||
sem_map = {did: float(s) for did, s in zip(doc_ids, scores)}
|
||||
|
||||
# 2. Lexikalisch
|
||||
words = query.replace('"', '').split()
|
||||
if not words: words = [query]
|
||||
fts_query = " OR ".join([f'"{w}"*' for w in words])
|
||||
@@ -222,7 +351,6 @@ class DatabaseHandler:
|
||||
r2 = fuzz.partial_token_set_ratio(query.lower(), content[:5000].lower())
|
||||
lex_map[did] = max(r1, r2) / 100.0
|
||||
|
||||
# 3. Hybrid
|
||||
final = {}
|
||||
ALPHA = 0.65
|
||||
BETA = 0.35
|
||||
@@ -233,7 +361,6 @@ class DatabaseHandler:
|
||||
if s_score > 0.4 and l_score > 0.6: h_score += 0.1
|
||||
final[did] = h_score
|
||||
|
||||
# 4. Fetch
|
||||
sorted_ids = sorted(final.keys(), key=lambda x: final[x], reverse=True)[:50]
|
||||
results = []
|
||||
for did in sorted_ids:
|
||||
@@ -269,7 +396,6 @@ class IndexerThread(QThread):
|
||||
ext = os.path.splitext(filename)[1].lower()
|
||||
text = ""
|
||||
try:
|
||||
# --- PDF ---
|
||||
if ext == ".pdf":
|
||||
try:
|
||||
with pdfplumber.open(stream) as pdf:
|
||||
@@ -277,17 +403,34 @@ class IndexerThread(QThread):
|
||||
if t := p.extract_text(): text += t + "\n"
|
||||
except: pass
|
||||
|
||||
# --- WORD / DOCX ---
|
||||
elif ext == ".docx" and docx is not None:
|
||||
try:
|
||||
# python-docx kann file-like objects (BytesIO oder file) lesen
|
||||
doc = docx.Document(stream)
|
||||
for para in doc.paragraphs:
|
||||
text += para.text + "\n"
|
||||
except Exception as e:
|
||||
print(f"Docx Error {filename}: {e}")
|
||||
for para in doc.paragraphs: text += para.text + "\n"
|
||||
except: pass
|
||||
|
||||
elif ext == ".xlsx" and openpyxl is not None:
|
||||
try:
|
||||
wb = openpyxl.load_workbook(stream, data_only=True, read_only=True)
|
||||
for sheet in wb.worksheets:
|
||||
text += f"\n--- {sheet.title} ---\n"
|
||||
for row in sheet.iter_rows(values_only=True):
|
||||
row_text = " ".join([str(c) for c in row if c is not None])
|
||||
if row_text.strip(): text += row_text + "\n"
|
||||
except: pass
|
||||
|
||||
elif ext == ".pptx" and Presentation is not None:
|
||||
try:
|
||||
prs = Presentation(stream)
|
||||
for i, slide in enumerate(prs.slides):
|
||||
text += f"\n--- Folie {i+1} ---\n"
|
||||
for shape in slide.shapes:
|
||||
if shape.has_text_frame:
|
||||
for paragraph in shape.text_frame.paragraphs:
|
||||
for run in paragraph.runs: text += run.text + " "
|
||||
text += "\n"
|
||||
except: pass
|
||||
|
||||
# --- PLAIN TEXT ---
|
||||
elif ext in [".txt", ".md", ".py", ".json", ".csv", ".html", ".log", ".ini", ".xml"]:
|
||||
try:
|
||||
content = stream.read()
|
||||
@@ -301,7 +444,6 @@ class IndexerThread(QThread):
|
||||
conn = sqlite3.connect(self.db_name)
|
||||
cursor = conn.cursor()
|
||||
|
||||
# Cleanup
|
||||
cursor.execute("SELECT rowid FROM documents WHERE path LIKE ?", (f"{self.folder_path}%",))
|
||||
ids = [r[0] for r in cursor.fetchall()]
|
||||
if ids:
|
||||
@@ -333,12 +475,16 @@ class IndexerThread(QThread):
|
||||
indexed += 1
|
||||
except: skipped += 1
|
||||
else:
|
||||
with open(path, "rb") as f:
|
||||
content = self._extract_text(f, file)
|
||||
if content and len(content.strip()) > 20:
|
||||
self._save(cursor, file, path, content)
|
||||
indexed += 1
|
||||
else: skipped += 1
|
||||
try:
|
||||
with open(path, "rb") as f:
|
||||
file_content = io.BytesIO(f.read())
|
||||
content = self._extract_text(file_content, file)
|
||||
if content and len(content.strip()) > 20:
|
||||
self._save(cursor, file, path, content)
|
||||
indexed += 1
|
||||
else: skipped += 1
|
||||
except: skipped += 1
|
||||
|
||||
if cancelled: break
|
||||
|
||||
conn.commit()
|
||||
@@ -351,7 +497,7 @@ class IndexerThread(QThread):
|
||||
vec = self.model.encode(content[:8000], convert_to_tensor=False).tobytes()
|
||||
cursor.execute("INSERT INTO embeddings (doc_id, vec) VALUES (?, ?)", (did, vec))
|
||||
|
||||
# --- 3. UI ---
|
||||
# --- 3. UI MAIN WINDOW ---
|
||||
|
||||
class UffWindow(QMainWindow):
|
||||
def __init__(self, splash=None):
|
||||
@@ -362,90 +508,111 @@ class UffWindow(QMainWindow):
|
||||
self.load_saved_folders()
|
||||
|
||||
def initUI(self):
|
||||
self.setWindowTitle("UFF Search")
|
||||
self.resize(1000, 700)
|
||||
self.setWindowTitle("UFF Search v7.2 (Stable Tooltips)")
|
||||
self.resize(1100, 750)
|
||||
|
||||
self.setStyleSheet(STYLESHEET)
|
||||
|
||||
central = QWidget()
|
||||
self.setCentralWidget(central)
|
||||
main_layout = QHBoxLayout(central)
|
||||
main_layout.setContentsMargins(0, 0, 0, 0)
|
||||
main_layout.setSpacing(0)
|
||||
|
||||
# -- LINKS (Sidebar) --
|
||||
# -- SIDEBAR --
|
||||
left_panel = QFrame()
|
||||
left_panel.setFixedWidth(250)
|
||||
left_panel.setStyleSheet("background-color: #f0f0f0; border-right: 1px solid #ccc;")
|
||||
left_panel.setObjectName("Sidebar")
|
||||
left_panel.setFixedWidth(260)
|
||||
left_layout = QVBoxLayout(left_panel)
|
||||
left_layout.setContentsMargins(0, 20, 0, 20)
|
||||
|
||||
lbl_title = QLabel(" UFF SEARCH")
|
||||
lbl_title.setObjectName("SidebarTitle")
|
||||
|
||||
self.folder_list = QListWidget()
|
||||
self.folder_list.setStyleSheet("border: 1px solid #ddd; background: white;")
|
||||
|
||||
btn_add = QPushButton(" + Ordner")
|
||||
icon_add = self.style().standardIcon(QStyle.StandardPixmap.SP_FileDialogNewFolder)
|
||||
icon_del = self.style().standardIcon(QStyle.StandardPixmap.SP_TrashIcon)
|
||||
icon_refresh = self.style().standardIcon(QStyle.StandardPixmap.SP_BrowserReload)
|
||||
icon_stop = self.style().standardIcon(QStyle.StandardPixmap.SP_DialogCancelButton)
|
||||
|
||||
btn_add = QPushButton(" Ordner hinzufügen")
|
||||
btn_add.setObjectName("SidebarBtn")
|
||||
btn_add.setIcon(icon_add)
|
||||
btn_add.clicked.connect(self.add_new_folder)
|
||||
btn_del = QPushButton(" - Löschen")
|
||||
|
||||
btn_del = QPushButton(" Ordner entfernen")
|
||||
btn_del.setObjectName("SidebarBtn")
|
||||
btn_del.setIcon(icon_del)
|
||||
btn_del.clicked.connect(self.delete_selected_folder)
|
||||
self.btn_rescan = QPushButton(" ↻ Scan")
|
||||
|
||||
self.btn_rescan = QPushButton(" Neu scannen")
|
||||
self.btn_rescan.setObjectName("SidebarBtn")
|
||||
self.btn_rescan.setIcon(icon_refresh)
|
||||
self.btn_rescan.clicked.connect(self.rescan_selected_folder)
|
||||
self.btn_cancel = QPushButton("🛑 Stop")
|
||||
|
||||
self.btn_cancel = QPushButton("STOPPEN")
|
||||
self.btn_cancel.setObjectName("CancelBtn")
|
||||
self.btn_cancel.setIcon(icon_stop)
|
||||
self.btn_cancel.clicked.connect(self.cancel_indexing)
|
||||
self.btn_cancel.hide()
|
||||
self.btn_cancel.setStyleSheet("background-color: #ffcccc; color: red;")
|
||||
|
||||
left_layout.addWidget(QLabel("<b>📂 Indizierte Ordner</b>"))
|
||||
left_layout.addWidget(lbl_title)
|
||||
left_layout.addSpacing(10)
|
||||
left_layout.addWidget(self.folder_list)
|
||||
left_layout.addSpacing(10)
|
||||
left_layout.addWidget(btn_add)
|
||||
left_layout.addWidget(btn_del)
|
||||
left_layout.addStretch()
|
||||
left_layout.addWidget(self.btn_rescan)
|
||||
left_layout.addWidget(self.btn_cancel)
|
||||
|
||||
# -- RECHTS (Suche & Ergebnisse) --
|
||||
# -- RECHTS (Hauptbereich) --
|
||||
right_panel = QWidget()
|
||||
right_panel.setObjectName("MainArea")
|
||||
right_layout = QVBoxLayout(right_panel)
|
||||
right_layout.setContentsMargins(30, 30, 30, 30)
|
||||
right_layout.setSpacing(15)
|
||||
|
||||
# Suchleiste
|
||||
# Header
|
||||
search_box = QHBoxLayout()
|
||||
self.input_search = QLineEdit()
|
||||
self.input_search.setPlaceholderText("Suchbegriff eingeben...")
|
||||
self.input_search.setStyleSheet("padding: 8px; font-size: 14px;")
|
||||
self.input_search.setPlaceholderText("Wonach suchst du heute?")
|
||||
self.input_search.returnPressed.connect(self.perform_search)
|
||||
|
||||
self.btn_go = QPushButton("Suchen")
|
||||
self.btn_go.setFixedWidth(100)
|
||||
self.btn_go.setStyleSheet("background-color: #2980b9; color: white; padding: 8px; font-weight: bold;")
|
||||
self.btn_go.setObjectName("SearchBtn")
|
||||
self.btn_go.setCursor(Qt.CursorShape.PointingHandCursor)
|
||||
self.btn_go.clicked.connect(self.perform_search)
|
||||
|
||||
search_box.addWidget(self.input_search)
|
||||
search_box.addWidget(self.btn_go)
|
||||
|
||||
# Status & Progress
|
||||
self.lbl_status = QLabel("Warte auf Modell...")
|
||||
# Status
|
||||
status_box = QHBoxLayout()
|
||||
self.lbl_status = QLabel("Modell wird geladen...")
|
||||
self.lbl_status.setObjectName("StatusLabel")
|
||||
self.progress_bar = QProgressBar()
|
||||
self.progress_bar.hide()
|
||||
status_box.addWidget(self.lbl_status)
|
||||
status_box.addWidget(self.progress_bar)
|
||||
|
||||
# ERGEBNIS-BEREICH (QScrollArea statt QTextBrowser)
|
||||
# Ergebnisse
|
||||
self.scroll_area = QScrollArea()
|
||||
self.scroll_area.setWidgetResizable(True)
|
||||
self.scroll_area.setStyleSheet("background-color: #fafafa; border: none;")
|
||||
|
||||
# Container Widget für die Ergebnisse
|
||||
self.results_container = QWidget()
|
||||
self.results_container.setStyleSheet("background-color: transparent;")
|
||||
self.results_container.setObjectName("ResultsContainer")
|
||||
self.results_layout = QVBoxLayout(self.results_container)
|
||||
self.results_layout.setAlignment(Qt.AlignmentFlag.AlignTop)
|
||||
self.results_layout.setSpacing(10)
|
||||
|
||||
self.results_layout.setSpacing(15)
|
||||
self.scroll_area.setWidget(self.results_container)
|
||||
|
||||
right_layout.addLayout(search_box)
|
||||
right_layout.addWidget(self.lbl_status)
|
||||
right_layout.addWidget(self.progress_bar)
|
||||
right_layout.addLayout(status_box)
|
||||
right_layout.addWidget(self.scroll_area)
|
||||
|
||||
# Splitter
|
||||
splitter = QSplitter()
|
||||
splitter.addWidget(left_panel)
|
||||
splitter.addWidget(right_panel)
|
||||
splitter.setSizes([250, 750])
|
||||
|
||||
main_layout.addWidget(splitter)
|
||||
main_layout.addWidget(left_panel)
|
||||
main_layout.addWidget(right_panel)
|
||||
self.set_ui_enabled(False)
|
||||
|
||||
def set_ui_enabled(self, enabled):
|
||||
@@ -465,44 +632,38 @@ class UffWindow(QMainWindow):
|
||||
QMessageBox.critical(self, "Fehler", "Modell konnte nicht geladen werden.")
|
||||
return
|
||||
self.db.model = model
|
||||
self.lbl_status.setText("Bereit.")
|
||||
self.lbl_status.setText("Bereit für deine Suche.")
|
||||
self.set_ui_enabled(True)
|
||||
|
||||
def perform_search(self):
|
||||
query = self.input_search.text()
|
||||
if not query: return
|
||||
|
||||
self.lbl_status.setText("Suche läuft...")
|
||||
QApplication.processEvents()
|
||||
|
||||
# 1. Alte Ergebnisse löschen
|
||||
while self.results_layout.count():
|
||||
child = self.results_layout.takeAt(0)
|
||||
if child.widget():
|
||||
child.widget().deleteLater()
|
||||
if child.widget(): child.widget().deleteLater()
|
||||
|
||||
# 2. Suchen
|
||||
results = self.db.search(query)
|
||||
self.lbl_status.setText(f"{len(results)} Treffer.")
|
||||
self.lbl_status.setText(f"{len(results)} Treffer gefunden.")
|
||||
|
||||
# 3. Neue Ergebnisse als Widgets hinzufügen
|
||||
if not results:
|
||||
lbl = QLabel("Keine Ergebnisse gefunden.")
|
||||
lbl.setStyleSheet("color: #777; font-size: 14pt; margin-top: 20px;")
|
||||
lbl = QLabel("Leider keine Ergebnisse.")
|
||||
lbl.setStyleSheet("color: #95a5a6; font-size: 18px; margin-top: 40px;")
|
||||
lbl.setAlignment(Qt.AlignmentFlag.AlignHCenter)
|
||||
self.results_layout.addWidget(lbl)
|
||||
else:
|
||||
for fname, fpath, snippet in results:
|
||||
item = SearchResultItem(fname, fpath, snippet)
|
||||
self.results_layout.addWidget(item)
|
||||
|
||||
self.results_layout.addWidget(SearchResultItem(fname, fpath, snippet))
|
||||
self.results_layout.addStretch()
|
||||
|
||||
# --- Folder Management ---
|
||||
def load_saved_folders(self):
|
||||
self.folder_list.clear()
|
||||
for f in self.db.get_folders():
|
||||
self.folder_list.addItem(QListWidgetItem(f))
|
||||
item = QListWidgetItem(self.style().standardIcon(QStyle.StandardPixmap.SP_DirIcon), f)
|
||||
item.setToolTip(f)
|
||||
self.folder_list.addItem(item)
|
||||
|
||||
def add_new_folder(self):
|
||||
f = QFileDialog.getExistingDirectory(self, "Ordner wählen")
|
||||
@@ -534,19 +695,21 @@ class UffWindow(QMainWindow):
|
||||
def idx_done(self, n, s, c):
|
||||
self.set_ui_enabled(True)
|
||||
self.btn_cancel.hide(); self.btn_rescan.show(); self.progress_bar.hide()
|
||||
msg = "Abgebrochen" if c else "Fertig"
|
||||
msg = "Abgebrochen" if c else "Indexierung fertig"
|
||||
self.lbl_status.setText(f"{msg}: {n} neu, {s} übersprungen.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
|
||||
font = QFont("Segoe UI", 10)
|
||||
app.setFont(font)
|
||||
|
||||
splash = None
|
||||
try:
|
||||
if os.path.exists("assets/uff_banner.jpeg"):
|
||||
splash = QSplashScreen(QPixmap("assets/uff_banner.jpeg"))
|
||||
splash.show()
|
||||
except: pass
|
||||
|
||||
w = UffWindow(splash)
|
||||
w.show()
|
||||
w.start_model_loading()
|
||||
|
||||
Reference in New Issue
Block a user