Enhance database schema and UI functionality for folder management and indexing
This commit is contained in:
311
uff_app.py
311
uff_app.py
@@ -3,14 +3,14 @@ import os
|
|||||||
import sqlite3
|
import sqlite3
|
||||||
from pypdf import PdfReader
|
from pypdf import PdfReader
|
||||||
|
|
||||||
# PyQt6 Module
|
|
||||||
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
from PyQt6.QtWidgets import (QApplication, QMainWindow, QWidget, QVBoxLayout,
|
||||||
QHBoxLayout, QLineEdit, QPushButton, QLabel,
|
QHBoxLayout, QLineEdit, QPushButton, QLabel,
|
||||||
QFileDialog, QTextBrowser, QProgressBar, QMessageBox)
|
QFileDialog, QTextBrowser, QProgressBar, QMessageBox,
|
||||||
|
QListWidget, QListWidgetItem, QSplitter, QFrame)
|
||||||
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QUrl
|
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QUrl
|
||||||
from PyQt6.QtGui import QDesktopServices, QIcon
|
from PyQt6.QtGui import QDesktopServices
|
||||||
|
|
||||||
# --- 1. BACKEND (Unverändert, nur ausgelagert) ---
|
# --- 1. DATENBANK MANAGER ---
|
||||||
|
|
||||||
class DatabaseHandler:
|
class DatabaseHandler:
|
||||||
def __init__(self, db_name="uff_index.db"):
|
def __init__(self, db_name="uff_index.db"):
|
||||||
@@ -24,36 +24,70 @@ class DatabaseHandler:
|
|||||||
CREATE VIRTUAL TABLE IF NOT EXISTS documents
|
CREATE VIRTUAL TABLE IF NOT EXISTS documents
|
||||||
USING fts5(filename, path, content);
|
USING fts5(filename, path, content);
|
||||||
""")
|
""")
|
||||||
|
cursor.execute("""
|
||||||
|
CREATE TABLE IF NOT EXISTS folders (
|
||||||
|
path TEXT PRIMARY KEY,
|
||||||
|
alias TEXT
|
||||||
|
);
|
||||||
|
""")
|
||||||
conn.commit()
|
conn.commit()
|
||||||
conn.close()
|
conn.close()
|
||||||
|
|
||||||
|
def add_folder(self, path):
|
||||||
|
conn = sqlite3.connect(self.db_name)
|
||||||
|
try:
|
||||||
|
conn.execute("INSERT OR IGNORE INTO folders (path, alias) VALUES (?, ?)", (path, os.path.basename(path)))
|
||||||
|
conn.commit()
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def remove_folder(self, path):
|
||||||
|
conn = sqlite3.connect(self.db_name)
|
||||||
|
conn.execute("DELETE FROM folders WHERE path = ?", (path,))
|
||||||
|
conn.execute("DELETE FROM documents WHERE path LIKE ?", (f"{path}%",))
|
||||||
|
conn.commit()
|
||||||
|
conn.close()
|
||||||
|
|
||||||
|
def get_folders(self):
|
||||||
|
conn = sqlite3.connect(self.db_name)
|
||||||
|
rows = conn.execute("SELECT path FROM folders").fetchall()
|
||||||
|
conn.close()
|
||||||
|
return [r[0] for r in rows]
|
||||||
|
|
||||||
def search(self, query):
|
def search(self, query):
|
||||||
conn = sqlite3.connect(self.db_name)
|
conn = sqlite3.connect(self.db_name)
|
||||||
cursor = conn.cursor()
|
|
||||||
safe_query = query.replace('"', '""')
|
safe_query = query.replace('"', '""')
|
||||||
sql = """
|
sql = """
|
||||||
SELECT filename, path, snippet(documents, 2, '<b>', '</b>', '...', 15)
|
SELECT filename, path, snippet(documents, 2, '<b>', '</b>', '...', 15)
|
||||||
FROM documents
|
FROM documents
|
||||||
WHERE documents MATCH ?
|
WHERE documents MATCH ?
|
||||||
ORDER BY rank LIMIT 50
|
ORDER BY rank LIMIT 100
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
results = cursor.execute(sql, (f"{safe_query}*",)).fetchall()
|
results = conn.execute(sql, (f"{safe_query}*",)).fetchall()
|
||||||
except sqlite3.OperationalError:
|
except:
|
||||||
results = []
|
results = []
|
||||||
conn.close()
|
conn.close()
|
||||||
return results
|
return results
|
||||||
|
|
||||||
# --- 2. WORKER THREAD (Damit die UI beim Scannen nicht einfriert) ---
|
# --- 2. INDEXER (Mit Stop-Funktion) ---
|
||||||
|
|
||||||
class IndexerThread(QThread):
|
class IndexerThread(QThread):
|
||||||
progress_signal = pyqtSignal(str) # Sendet Text an UI
|
progress_signal = pyqtSignal(str)
|
||||||
finished_signal = pyqtSignal(int, int) # Sendet Statistiken (indexed, skipped)
|
finished_signal = pyqtSignal(int, int, bool) # bool = Wurde abgebrochen?
|
||||||
|
|
||||||
def __init__(self, folder_path, db_name="uff_index.db"):
|
def __init__(self, folder_path, db_name="uff_index.db"):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.folder_path = folder_path
|
self.folder_path = folder_path
|
||||||
self.db_name = db_name
|
self.db_name = db_name
|
||||||
|
self.is_running = True # Flag zum Steuern
|
||||||
|
|
||||||
|
def stop(self):
|
||||||
|
"""Setzt das Flag, damit der Loop stoppt."""
|
||||||
|
self.is_running = False
|
||||||
|
|
||||||
def _extract_text(self, filepath):
|
def _extract_text(self, filepath):
|
||||||
ext = os.path.splitext(filepath)[1].lower()
|
ext = os.path.splitext(filepath)[1].lower()
|
||||||
@@ -62,175 +96,252 @@ class IndexerThread(QThread):
|
|||||||
reader = PdfReader(filepath)
|
reader = PdfReader(filepath)
|
||||||
text = ""
|
text = ""
|
||||||
for page in reader.pages:
|
for page in reader.pages:
|
||||||
text_page = page.extract_text()
|
if page_text := page.extract_text(): text += page_text + "\n"
|
||||||
if text_page: text += text_page + "\n"
|
|
||||||
return text
|
return text
|
||||||
elif ext in [".txt", ".md", ".py", ".json", ".csv", ".html", ".log", ".ini"]:
|
elif ext in [".txt", ".md", ".py", ".json", ".csv", ".html", ".log", ".ini", ".xml"]:
|
||||||
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
|
with open(filepath, "r", encoding="utf-8", errors="ignore") as f:
|
||||||
return f.read()
|
return f.read()
|
||||||
return None
|
return None
|
||||||
except Exception:
|
except:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
conn = sqlite3.connect(self.db_name)
|
conn = sqlite3.connect(self.db_name)
|
||||||
cursor = conn.cursor()
|
|
||||||
|
|
||||||
# Alten Index leeren
|
# Alten Inhalt des Ordners löschen
|
||||||
cursor.execute("DELETE FROM documents")
|
conn.execute("DELETE FROM documents WHERE path LIKE ?", (f"{self.folder_path}%",))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
|
|
||||||
indexed = 0
|
indexed = 0
|
||||||
skipped = 0
|
skipped = 0
|
||||||
|
was_cancelled = False
|
||||||
|
|
||||||
for root, dirs, files in os.walk(self.folder_path):
|
for root, dirs, files in os.walk(self.folder_path):
|
||||||
for file in files:
|
# Check 1: Wurde Stop gedrückt?
|
||||||
self.progress_signal.emit(f"Scanne: {file}...")
|
if not self.is_running:
|
||||||
path = os.path.join(root, file)
|
was_cancelled = True
|
||||||
|
break
|
||||||
|
|
||||||
|
for file in files:
|
||||||
|
# Check 2: Auch innerhalb der Dateien prüfen für schnellere Reaktion
|
||||||
|
if not self.is_running:
|
||||||
|
was_cancelled = True
|
||||||
|
break
|
||||||
|
|
||||||
|
self.progress_signal.emit(f"Lese: {file}...")
|
||||||
|
path = os.path.join(root, file)
|
||||||
content = self._extract_text(path)
|
content = self._extract_text(path)
|
||||||
|
|
||||||
if content and len(content.strip()) > 0:
|
if content and len(content.strip()) > 0:
|
||||||
cursor.execute(
|
conn.execute(
|
||||||
"INSERT INTO documents (filename, path, content) VALUES (?, ?, ?)",
|
"INSERT INTO documents (filename, path, content) VALUES (?, ?, ?)",
|
||||||
(file, path, content)
|
(file, path, content)
|
||||||
)
|
)
|
||||||
indexed += 1
|
indexed += 1
|
||||||
else:
|
else:
|
||||||
skipped += 1
|
skipped += 1
|
||||||
|
|
||||||
|
if was_cancelled:
|
||||||
|
break
|
||||||
|
|
||||||
conn.commit()
|
conn.commit() # Wir speichern, was wir bis zum Abbruch geschafft haben
|
||||||
conn.close()
|
conn.close()
|
||||||
self.finished_signal.emit(indexed, skipped)
|
self.finished_signal.emit(indexed, skipped, was_cancelled)
|
||||||
|
|
||||||
# --- 3. FRONTEND (Das PyQt Fenster) ---
|
# --- 3. UI ---
|
||||||
|
|
||||||
class UffWindow(QMainWindow):
|
class UffWindow(QMainWindow):
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
super().__init__()
|
super().__init__()
|
||||||
self.db = DatabaseHandler()
|
self.db = DatabaseHandler()
|
||||||
|
self.indexer_thread = None
|
||||||
self.initUI()
|
self.initUI()
|
||||||
|
self.load_saved_folders()
|
||||||
|
|
||||||
def initUI(self):
|
def initUI(self):
|
||||||
self.setWindowTitle("UFF Text Search - PyQt Edition")
|
self.setWindowTitle("UFF Text Search v2.1")
|
||||||
self.resize(800, 600)
|
self.resize(1000, 700)
|
||||||
|
|
||||||
# Haupt-Container
|
central = QWidget()
|
||||||
central_widget = QWidget()
|
self.setCentralWidget(central)
|
||||||
self.setCentralWidget(central_widget)
|
main_layout = QHBoxLayout(central)
|
||||||
layout = QVBoxLayout(central_widget)
|
|
||||||
|
|
||||||
# --- Header ---
|
# --- LINKS ---
|
||||||
title = QLabel("UFF Text Search")
|
left_panel = QFrame()
|
||||||
title.setStyleSheet("font-size: 24px; font-weight: bold; color: #333;")
|
left_panel.setFixedWidth(250)
|
||||||
layout.addWidget(title)
|
left_layout = QVBoxLayout(left_panel)
|
||||||
|
left_layout.setContentsMargins(0, 0, 0, 0)
|
||||||
|
|
||||||
# --- Ordner Auswahl ---
|
lbl_folders = QLabel("📂 Meine Ordner")
|
||||||
folder_layout = QHBoxLayout()
|
lbl_folders.setStyleSheet("font-weight: bold; font-size: 14px;")
|
||||||
self.btn_folder = QPushButton("Ordner wählen")
|
|
||||||
self.btn_folder.setCursor(Qt.CursorShape.PointingHandCursor)
|
|
||||||
self.btn_folder.clicked.connect(self.select_folder)
|
|
||||||
|
|
||||||
self.lbl_folder = QLabel("Kein Ordner gewählt")
|
self.folder_list = QListWidget()
|
||||||
self.lbl_folder.setStyleSheet("color: gray; font-style: italic;")
|
self.folder_list.setSelectionMode(QListWidget.SelectionMode.SingleSelection)
|
||||||
|
|
||||||
folder_layout.addWidget(self.btn_folder)
|
|
||||||
folder_layout.addWidget(self.lbl_folder)
|
|
||||||
folder_layout.addStretch()
|
|
||||||
layout.addLayout(folder_layout)
|
|
||||||
|
|
||||||
# --- Suche ---
|
btn_add = QPushButton(" + Hinzufügen")
|
||||||
search_layout = QHBoxLayout()
|
btn_add.clicked.connect(self.add_new_folder)
|
||||||
|
|
||||||
|
btn_remove = QPushButton(" - Entfernen")
|
||||||
|
btn_remove.clicked.connect(self.delete_selected_folder)
|
||||||
|
|
||||||
|
self.btn_rescan = QPushButton(" ↻ Neu scannen")
|
||||||
|
self.btn_rescan.clicked.connect(self.rescan_selected_folder)
|
||||||
|
|
||||||
|
# Der neue Abbrechen-Button (Standardmäßig unsichtbar)
|
||||||
|
self.btn_cancel = QPushButton("🛑 Abbrechen")
|
||||||
|
self.btn_cancel.setStyleSheet("background-color: #ffcccc; color: #cc0000; font-weight: bold;")
|
||||||
|
self.btn_cancel.clicked.connect(self.cancel_indexing)
|
||||||
|
self.btn_cancel.hide()
|
||||||
|
|
||||||
|
left_layout.addWidget(lbl_folders)
|
||||||
|
left_layout.addWidget(self.folder_list)
|
||||||
|
left_layout.addWidget(btn_add)
|
||||||
|
left_layout.addWidget(btn_remove)
|
||||||
|
left_layout.addStretch() # Spacer
|
||||||
|
left_layout.addWidget(self.btn_rescan)
|
||||||
|
left_layout.addWidget(self.btn_cancel) # Wird eingeblendet beim Scan
|
||||||
|
|
||||||
|
# --- RECHTS ---
|
||||||
|
right_panel = QWidget()
|
||||||
|
right_layout = QVBoxLayout(right_panel)
|
||||||
|
|
||||||
|
search_container = QHBoxLayout()
|
||||||
self.input_search = QLineEdit()
|
self.input_search = QLineEdit()
|
||||||
self.input_search.setPlaceholderText("Suchbegriff eingeben und Enter drücken...")
|
self.input_search.setPlaceholderText("Suchbegriff eingeben...")
|
||||||
self.input_search.returnPressed.connect(self.perform_search)
|
self.input_search.returnPressed.connect(self.perform_search)
|
||||||
|
self.input_search.setStyleSheet("padding: 8px; font-size: 14px;")
|
||||||
|
|
||||||
self.btn_search = QPushButton("Suchen")
|
btn_go = QPushButton("Suchen")
|
||||||
self.btn_search.clicked.connect(self.perform_search)
|
btn_go.setFixedWidth(100)
|
||||||
|
btn_go.clicked.connect(self.perform_search)
|
||||||
|
|
||||||
|
search_container.addWidget(self.input_search)
|
||||||
|
search_container.addWidget(btn_go)
|
||||||
|
|
||||||
search_layout.addWidget(self.input_search)
|
|
||||||
search_layout.addWidget(self.btn_search)
|
|
||||||
layout.addLayout(search_layout)
|
|
||||||
|
|
||||||
# --- Status & Progress ---
|
|
||||||
self.lbl_status = QLabel("Bereit.")
|
self.lbl_status = QLabel("Bereit.")
|
||||||
|
self.lbl_status.setStyleSheet("color: #666;")
|
||||||
self.progress_bar = QProgressBar()
|
self.progress_bar = QProgressBar()
|
||||||
self.progress_bar.setRange(0, 0) # Infinite Loading Animation
|
|
||||||
self.progress_bar.hide()
|
self.progress_bar.hide()
|
||||||
layout.addWidget(self.lbl_status)
|
|
||||||
layout.addWidget(self.progress_bar)
|
|
||||||
|
|
||||||
# --- Ergebnisse (Browser Engine für HTML Support) ---
|
|
||||||
self.result_browser = QTextBrowser()
|
self.result_browser = QTextBrowser()
|
||||||
self.result_browser.setOpenExternalLinks(False) # Wir handeln Links selbst
|
self.result_browser.setOpenExternalLinks(False)
|
||||||
self.result_browser.anchorClicked.connect(self.link_clicked)
|
self.result_browser.anchorClicked.connect(self.link_clicked)
|
||||||
self.result_browser.setStyleSheet("background-color: white; padding: 10px; font-size: 14px;")
|
self.result_browser.setStyleSheet("background-color: white; border: 1px solid #ccc;")
|
||||||
layout.addWidget(self.result_browser)
|
|
||||||
|
|
||||||
def select_folder(self):
|
right_layout.addLayout(search_container)
|
||||||
|
right_layout.addWidget(self.lbl_status)
|
||||||
|
right_layout.addWidget(self.progress_bar)
|
||||||
|
right_layout.addWidget(self.result_browser)
|
||||||
|
|
||||||
|
splitter = QSplitter(Qt.Orientation.Horizontal)
|
||||||
|
splitter.addWidget(left_panel)
|
||||||
|
splitter.addWidget(right_panel)
|
||||||
|
splitter.setSizes([250, 750])
|
||||||
|
|
||||||
|
main_layout.addWidget(splitter)
|
||||||
|
|
||||||
|
# --- LOGIK ---
|
||||||
|
|
||||||
|
def load_saved_folders(self):
|
||||||
|
self.folder_list.clear()
|
||||||
|
folders = self.db.get_folders()
|
||||||
|
for f in folders:
|
||||||
|
item = QListWidgetItem(f)
|
||||||
|
item.setToolTip(f)
|
||||||
|
self.folder_list.addItem(item)
|
||||||
|
|
||||||
|
def add_new_folder(self):
|
||||||
folder = QFileDialog.getExistingDirectory(self, "Ordner wählen")
|
folder = QFileDialog.getExistingDirectory(self, "Ordner wählen")
|
||||||
if folder:
|
if folder:
|
||||||
self.lbl_folder.setText(folder)
|
if self.db.add_folder(folder):
|
||||||
self.start_indexing(folder)
|
self.load_saved_folders()
|
||||||
|
self.start_indexing(folder)
|
||||||
|
else:
|
||||||
|
QMessageBox.warning(self, "Info", "Ordner ist bereits vorhanden.")
|
||||||
|
|
||||||
|
def delete_selected_folder(self):
|
||||||
|
item = self.folder_list.currentItem()
|
||||||
|
if not item: return
|
||||||
|
path = item.text()
|
||||||
|
|
||||||
|
if QMessageBox.question(self, "Löschen", f"Ordner entfernen?\n{path}",
|
||||||
|
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No) == QMessageBox.StandardButton.Yes:
|
||||||
|
self.db.remove_folder(path)
|
||||||
|
self.load_saved_folders()
|
||||||
|
self.result_browser.clear()
|
||||||
|
self.lbl_status.setText("Ordner entfernt.")
|
||||||
|
|
||||||
|
def rescan_selected_folder(self):
|
||||||
|
item = self.folder_list.currentItem()
|
||||||
|
if not item:
|
||||||
|
QMessageBox.information(self, "Info", "Bitte Ordner links auswählen.")
|
||||||
|
return
|
||||||
|
self.start_indexing(item.text())
|
||||||
|
|
||||||
def start_indexing(self, folder):
|
def start_indexing(self, folder):
|
||||||
self.btn_folder.setEnabled(False)
|
self.set_ui_busy(True)
|
||||||
self.input_search.setEnabled(False)
|
self.lbl_status.setText(f"Starte... {os.path.basename(folder)}")
|
||||||
self.progress_bar.show()
|
|
||||||
|
|
||||||
# Thread starten
|
|
||||||
self.indexer_thread = IndexerThread(folder)
|
self.indexer_thread = IndexerThread(folder)
|
||||||
self.indexer_thread.progress_signal.connect(self.update_status)
|
self.indexer_thread.progress_signal.connect(lambda msg: self.lbl_status.setText(msg))
|
||||||
self.indexer_thread.finished_signal.connect(self.indexing_finished)
|
self.indexer_thread.finished_signal.connect(self.indexing_finished)
|
||||||
self.indexer_thread.start()
|
self.indexer_thread.start()
|
||||||
|
|
||||||
def update_status(self, msg):
|
def cancel_indexing(self):
|
||||||
self.lbl_status.setText(msg)
|
if self.indexer_thread and self.indexer_thread.isRunning():
|
||||||
|
self.lbl_status.setText("Breche ab... Bitte warten...")
|
||||||
|
self.indexer_thread.stop()
|
||||||
|
# Wir warten nicht auf den Thread hier (non-blocking),
|
||||||
|
# das finished_signal kümmert sich um den Rest.
|
||||||
|
|
||||||
def indexing_finished(self, indexed, skipped):
|
def indexing_finished(self, indexed, skipped, was_cancelled):
|
||||||
self.progress_bar.hide()
|
self.set_ui_busy(False)
|
||||||
self.btn_folder.setEnabled(True)
|
if was_cancelled:
|
||||||
self.input_search.setEnabled(True)
|
self.lbl_status.setText(f"Abgebrochen. ({indexed} indiziert).")
|
||||||
self.lbl_status.setText(f"Fertig! {indexed} Dateien indiziert ({skipped} übersprungen).")
|
QMessageBox.information(self, "Abbruch", f"Vorgang vom Benutzer abgebrochen.\nBis dahin indiziert: {indexed}")
|
||||||
QMessageBox.information(self, "Scan beendet", f"{indexed} Dateien wurden erfolgreich indiziert.")
|
else:
|
||||||
|
self.lbl_status.setText(f"Fertig. {indexed} neu, {skipped} übersprungen.")
|
||||||
|
QMessageBox.information(self, "Fertig", f"Scan abgeschlossen!\n{indexed} Dateien im Index.")
|
||||||
|
|
||||||
|
def set_ui_busy(self, busy):
|
||||||
|
# Steuert die Buttons während des Scans
|
||||||
|
self.input_search.setEnabled(not busy)
|
||||||
|
self.folder_list.setEnabled(not busy)
|
||||||
|
self.btn_rescan.setVisible(not busy) # Rescan verstecken
|
||||||
|
self.btn_cancel.setVisible(busy) # Abbrechen zeigen
|
||||||
|
|
||||||
|
if busy:
|
||||||
|
self.progress_bar.setRange(0, 0)
|
||||||
|
self.progress_bar.show()
|
||||||
|
else:
|
||||||
|
self.progress_bar.hide()
|
||||||
|
|
||||||
def perform_search(self):
|
def perform_search(self):
|
||||||
query = self.input_search.text()
|
query = self.input_search.text()
|
||||||
if not query: return
|
if not query: return
|
||||||
|
|
||||||
results = self.db.search(query)
|
results = self.db.search(query)
|
||||||
self.lbl_status.setText(f"{len(results)} Treffer gefunden.")
|
self.lbl_status.setText(f"{len(results)} Treffer.")
|
||||||
|
|
||||||
# HTML bauen für die Anzeige
|
html = ""
|
||||||
html_content = ""
|
|
||||||
if not results:
|
if not results:
|
||||||
html_content = "<p style='color: gray;'>Keine Ergebnisse gefunden.</p>"
|
html = "<h3 style='color: gray; text-align: center; margin-top: 20px;'>Nichts gefunden.</h3>"
|
||||||
|
|
||||||
for filename, filepath, snippet in results:
|
for filename, filepath, snippet in results:
|
||||||
# Wir nutzen den Dateipfad als Link-URL
|
|
||||||
file_url = QUrl.fromLocalFile(filepath).toString()
|
file_url = QUrl.fromLocalFile(filepath).toString()
|
||||||
|
html += f"""
|
||||||
html_content += f"""
|
<div style='margin-bottom: 10px; padding: 10px; background-color: #f9f9f9; border-left: 4px solid #2980b9;'>
|
||||||
<div style='margin-bottom: 15px; border-bottom: 1px solid #ddd; padding-bottom: 5px;'>
|
|
||||||
<a href="{file_url}" style='font-size: 16px; font-weight: bold; color: #2980b9; text-decoration: none;'>
|
<a href="{file_url}" style='font-size: 16px; font-weight: bold; color: #2980b9; text-decoration: none;'>
|
||||||
📄 {filename}
|
{filename}
|
||||||
</a>
|
</a>
|
||||||
<div style='color: #444; margin-top: 5px; font-family: sans-serif;'>
|
<div style='color: #333; margin-top: 5px; font-family: sans-serif; font-size: 13px;'>{snippet}</div>
|
||||||
...{snippet}...
|
<div style='color: #999; font-size: 11px; margin-top: 4px;'>{filepath}</div>
|
||||||
</div>
|
|
||||||
<div style='color: #888; font-size: 10px; margin-top: 2px;'>
|
|
||||||
{filepath}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
"""
|
"""
|
||||||
|
self.result_browser.setHtml(html)
|
||||||
self.result_browser.setHtml(html_content)
|
|
||||||
|
|
||||||
def link_clicked(self, url):
|
def link_clicked(self, url):
|
||||||
# Öffnet die Datei mit dem Standard-Programm des Betriebssystems
|
|
||||||
QDesktopServices.openUrl(url)
|
QDesktopServices.openUrl(url)
|
||||||
|
|
||||||
# --- APP START ---
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = QApplication(sys.argv)
|
app = QApplication(sys.argv)
|
||||||
window = UffWindow()
|
window = UffWindow()
|
||||||
|
|||||||
BIN
uff_index.db
BIN
uff_index.db
Binary file not shown.
Reference in New Issue
Block a user