2025-09-03 13:20:23 +02:00

667 lines
29 KiB
Python

from PyQt5.QtWidgets import QMessageBox, QCheckBox, QGroupBox, QDateTimeEdit,QProgressBar, QMainWindow, QTableWidget, QTableWidgetItem, QLineEdit, QStyledItemDelegate, QTextEdit, QWidget, QVBoxLayout, QHBoxLayout, QPushButton, QComboBox, QLabel
from PyQt5.QtCore import pyqtSignal, Qt, pyqtSignal, QDateTime, QThread
from PyQt5.QtGui import QTextDocument, QTextOption
import re
import logging
import html
import datetime
from logline_leviathan.database.query import DatabaseGUIQuery, QueryThread
from logline_leviathan.database.database_manager import EntitiesTable, session_scope
COLUMN_WIDTHS = [200, 100, 250, 100, 120, 600, 80, 100, 40] # Adjust these values as needed
COLUMN_NAMES = ['Distinct Entity', 'Entity Type', 'File Name', 'Line Number', 'Timestamp', 'Context', 'Match Score', 'Flag', 'Identifier']
DEFAULT_ROW_HEIGHT = 120
FILTER_EDIT_WIDTH = 150
class DataProcessor(QThread):
dataProcessed = pyqtSignal(list)
def __init__(self, total_data, search_terms, chunk_size=50):
super().__init__()
self.total_data = total_data
self.search_terms = search_terms
# Ensure chunk_size is an integer
if not isinstance(chunk_size, int):
raise ValueError(f"chunk_size must be an integer, got {type(chunk_size)}")
self.chunk_size = chunk_size
def run(self):
# Process initial chunk
for i in range(0, len(self.total_data), self.chunk_size):
chunk = sorted(self.total_data[i:i+self.chunk_size], key=lambda x: x[1], reverse=True)
self.dataProcessed.emit(chunk)
# Continue processing remaining data
remaining_data = self.total_data[self.chunk_size:]
for i in range(0, len(remaining_data), self.chunk_size):
chunk = sorted(remaining_data[i:i+self.chunk_size], key=lambda x: x[1], reverse=True)
self.dataProcessed.emit(chunk)
class ResultsWindow(QMainWindow):
def __init__(self, db_query_instance, parent=None):
super(ResultsWindow, self).__init__(parent)
self.db_query_instance = db_query_instance
self.total_data = []
self.current_filters = {}
self.query_text = None
self.database_query = DatabaseGUIQuery()
self.query_thread = QueryThread(self.db_query_instance, self.query_text)
self.sorted_results = []
self.setWindowTitle("Suchergebnis")
self.setGeometry(800, 600, 1600, 700) # Adjust size as needed
# Create central widget and set layout
centralWidget = QWidget(self)
self.setCentralWidget(centralWidget)
mainLayout = QVBoxLayout(centralWidget)
queryFieldLayout = QHBoxLayout()
self.databaseQueryLineEdit = QueryLineEdit(self)
self.databaseQueryLineEdit.setPlaceholderText(" Suchbegriff eingeben...")
self.databaseQueryLineEdit.returnPressed.connect(self.execute_query_from_results_window)
self.databaseQueryLineEdit.setStyleSheet("""
QLineEdit {
background-color: #3C4043;
color: white;
min-height: 20px;
}
""")
queryFieldLayout.addWidget(self.databaseQueryLineEdit)
# Create a progress bar for query in progress
self.query_progress_bar = QProgressBar(self)
self.query_progress_bar.setRange(0, 1) # Indeterminate mode
self.query_progress_bar.setFixedWidth(100) # Initially hidden
queryFieldLayout.addWidget(self.query_progress_bar)
executeQueryButton = QPushButton("Suche ausführen", self)
executeQueryButton.clicked.connect(self.execute_query_from_results_window)
queryFieldLayout.addWidget(executeQueryButton)
mainLayout.addLayout(queryFieldLayout)
mainLayout.addWidget(QLabel(' Die Suche nach mehreren bestimmten Begriffen, Entitätentypen (Kurzform z.B. <ipv4>), Dateinamen und Timestamps ist möglich.\n Nach erfolgreicher Abfrage der Datenbank werden die Ergebnisse tabellarisch dargestellt. Sollte die Anzahl der Suchergebnisse sehr hoch sein, dauert der Prozess einige Sekunden. Die Anzahl der Ergebnisse ist aus Performancegründen auf die besten 512 beschränkt.\n Groß- und Kleinschreibung wird bei Zitatsuchen berücksichtigt; Werden mehrere Suchbegriffe eingegeben, fließt deren Abstand und Reihenfolge ins Ergebnis mit ein.', self))
# Create a horizontal layout for filter options
filterLayout = QHBoxLayout()
mainLayout.addLayout(filterLayout)
# Updated stylesheet for the entire ResultsWindow
stylesheet = """
/* Styles for QTableWidget and headers */
QTableWidget, QHeaderView::section {
background-color: #2A2F35;
color: white;
border: 1px solid #4A4A4A;
}
/* Style for QLineEdit */
QLineEdit {
background-color: #3A3F44;
color: white;
border: 1px solid #4A4A4A;
}
/* Style for QPushButton */
QPushButton {
background-color: #4B5563;
color: white;
border-radius: 4px;
padding: 5px;
margin: 5px;
}
QPushButton:hover {
background-color: #5C677D;
}
QPushButton:pressed {
background-color: #2A2F35;
}
/* Style for empty rows and other areas */
QWidget {
background-color: #2A2F35;
color: white;
}
"""
self.setStyleSheet(stylesheet)
self.resultsTable = QTableWidget(self)
self.clearAllButton = QPushButton("Alle Filteroptionen loeschen", self)
self.clearAllButton.clicked.connect(self.clearAllFilters)
filterLayout.addWidget(self.clearAllButton)
# GroupBox for Entitätenfilter
entitaten_filter_groupbox = QGroupBox("Entitätenfilter", self)
entitaten_filter_layout = QVBoxLayout()
entitaten_filter_groupbox.setLayout(entitaten_filter_layout)
self.distinct_entity_edit = QLineEdit(self)
self.distinct_entity_edit.setPlaceholderText(" Enter distinct entity...")
self.distinct_entity_edit.textChanged.connect(self.applyDistinctEntityTextFilter)
entitaten_filter_layout.addWidget(self.distinct_entity_edit)
self.entityTypeComboBox = QComboBox()
entitaten_filter_layout.addWidget(self.entityTypeComboBox)
filterLayout.addWidget(entitaten_filter_groupbox)
# GroupBox for Fundstelle
fundstelle_groupbox = QGroupBox("Fundstelle", self)
fundstelle_layout = QVBoxLayout()
fundstelle_groupbox.setLayout(fundstelle_layout)
self.file_name_edit = QLineEdit(self)
self.file_name_edit.setPlaceholderText(" Enter file name...")
self.file_name_edit.textChanged.connect(self.applyFileNameTextFilter)
fundstelle_layout.addWidget(self.file_name_edit)
self.line_number_edit = QLineEdit(self)
self.line_number_edit.setPlaceholderText(" Enter line number...")
self.line_number_edit.textChanged.connect(self.applyLineNumberTextFilter)
fundstelle_layout.addWidget(self.line_number_edit)
filterLayout.addWidget(fundstelle_groupbox)
# GroupBox for timestamp filtering
self.timestampFilterGroupbox = QGroupBox("Zeitrahmen", self)
timestampFilterLayout = QVBoxLayout()
self.timestampFilterGroupbox.setLayout(timestampFilterLayout)
filterLayout.addWidget(self.timestampFilterGroupbox)
dateedit_layout = QHBoxLayout()
self.startDateEdit = QDateTimeEdit(self)
self.startDateEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
self.startDateEdit.setCalendarPopup(True)
self.startDateEdit.setDateTime(QDateTime.currentDateTime().addDays(-10000))
dateedit_layout.addWidget(self.startDateEdit)
self.startDateEdit.dateTimeChanged.connect(self.applyTimestampFilter)
self.endDateEdit = QDateTimeEdit(self)
self.endDateEdit.setDisplayFormat("yyyy-MM-dd HH:mm:ss")
self.endDateEdit.setCalendarPopup(True)
self.endDateEdit.setDateTime(QDateTime.currentDateTime().addDays(1))
dateedit_layout.addWidget(self.endDateEdit)
self.endDateEdit.dateTimeChanged.connect(self.applyTimestampFilter)
self.timestamp_edit = QLineEdit(self)
self.timestamp_edit.setPlaceholderText(" Enter timestamp...")
self.timestamp_edit.textChanged.connect(self.applyTimestampTextFilter)
timestampFilterLayout.addWidget(self.timestamp_edit)
timestampFilterLayout.addLayout(dateedit_layout)
# GroupBox for Match Score, Flags, and Identifier
more_filters_groupbox = QGroupBox("Weitere Filter", self)
more_filters_layout = QHBoxLayout()
more_filters_groupbox.setLayout(more_filters_layout)
filterLayout.addWidget(more_filters_groupbox)
flag_layout = QVBoxLayout()
flag_true_checkbox = QCheckBox("Flagged", self)
flag_layout.addWidget(flag_true_checkbox)
flag_false_checkbox = QCheckBox("Not Flagged", self)
flag_layout.addWidget(flag_false_checkbox)
more_filters_layout.addLayout(flag_layout)
meta_layout = QVBoxLayout()
self.match_score_edit = QLineEdit(self)
self.match_score_edit.setPlaceholderText(" Enter match score...")
self.match_score_edit.textChanged.connect(self.applyMatchScoreTextFilter)
meta_layout.addWidget(self.match_score_edit)
self.identifier_edit = QLineEdit(self)
self.identifier_edit.setPlaceholderText(" Enter identifier...")
self.identifier_edit.textChanged.connect(self.applyIdentifierTextFilter)
meta_layout.addWidget(self.identifier_edit)
more_filters_layout.addLayout(meta_layout)
self.resultsTable.setColumnCount(len(COLUMN_NAMES))
self.resultsTable.setHorizontalHeaderLabels(COLUMN_NAMES)
self.resultsTable.setSortingEnabled(True)
mainLayout.addWidget(self.resultsTable)
bottomLayout = QHBoxLayout()
self.query_status_label = QLabel(" Hintergrundsuchprozess läuft...")
bottomLayout.addWidget(self.query_status_label)
self.bottomButtonLayout = QHBoxLayout()
flag_visible_items_button = QPushButton("Sichtbare Objekte markieren", self)
flag_visible_items_button.clicked.connect(self.flagVisibleItems)
self.bottomButtonLayout.addWidget(flag_visible_items_button)
unflag_visible_items_button = QPushButton("Sichtbare Objekte demarkieren", self)
unflag_visible_items_button.clicked.connect(self.unflagVisibleItems)
self.bottomButtonLayout.addWidget(unflag_visible_items_button)
clear_all_flags_button = QPushButton("Sämtliche Markierungen entfernen", self)
clear_all_flags_button.clicked.connect(self.clearAllFlags)
self.bottomButtonLayout.addWidget(clear_all_flags_button)
bottomLayout.addLayout(self.bottomButtonLayout)
mainLayout.addLayout(bottomLayout)
# Create and add the Dismiss button
self.dismissButton = QPushButton("Schließen", self)
self.dismissButton.clicked.connect(self.close)
mainLayout.addWidget(self.dismissButton)
self.populate_entity_type_combobox()
def populate_entity_type_combobox(self):
entity_types = self.database_query.get_entity_types()
self.entityTypeComboBox.addItem("Alle verfügbaren Typen", None) # Default option
for entity_type in entity_types:
self.entityTypeComboBox.addItem(entity_type, entity_type)
self.entityTypeComboBox.currentIndexChanged.connect(self.applyEntityTypeFilter)
def execute_query_from_results_window(self):
self.resultsTable.clear()
self.resultsTable.setRowCount(0)
self.query_text = self.databaseQueryLineEdit.text().strip()
if not self.query_text:
# Handle empty query case
return
self.query_status_label.setText(" Suche wird mit Suchbegriffen " + self.query_text + " durchgeführt...")
self.query_thread = QueryThread(self.database_query, self.query_text)
self.query_thread.queryCompleted.connect(self.on_query_completed)
self.query_thread.start()
self.query_progress_bar.setRange(0, 0)
def set_query_and_execute(self, query_text):
self.databaseQueryLineEdit.setText(query_text)
self.execute_query_from_results_window()
def on_query_completed(self, results_dict):
self.resultsTable.setUpdatesEnabled(False)
self.resultsTable.setSortingEnabled(False) # Disable sorting
self.resultsTable.setRowCount(0)
self.resultsTable.setColumnCount(len(COLUMN_NAMES))
self.sorted_results = sorted(results_dict.items(), key=lambda x: x[1], reverse=True)
self.top_results = self.sorted_results[:512]
self.query_status_label.setText(" Suche abgeschlossen // Anzahl Suchergebnisse: " + str(len(results_dict)) + " // Tabelle wird befüllt (das kann einige Zeit dauern)")
for entities_id, score in self.top_results:
self.insert_row(entities_id, score)
self.query_progress_bar.setRange(0, 1)
self.query_status_label.setText(" Suche abgeschlossen // Anzahl Suchergebnisse: " + str(len(results_dict)) + " (begrenzt auf die besten 512)")
self.resultsTable.setHorizontalHeaderLabels(COLUMN_NAMES)
self.adjust_column_widths()
self.resultsTable.setSortingEnabled(True)
self.resultsTable.sortByColumn(6, Qt.DescendingOrder) # Enable sorting
self.resultsTable.setUpdatesEnabled(True)
self.resultsTable.update()
self.applyAllFilters()
def insert_row(self, entities_id, score):
try:
with session_scope() as session:
# Fetch the entity from the database
entity = session.query(EntitiesTable).filter(EntitiesTable.entities_id == entities_id).first()
if not entity:
return # Skip if the entity is not found
# Fetch related data
distinct_entity = entity.entity.distinct_entity if entity.entity else ""
entity_type = entity.regex_library.gui_name if entity.regex_library else ""
file_name = entity.file.file_name if entity.file else ""
line_number = str(entity.line_number) if entity.line_number is not None else ""
entry_timestamp = entity.entry_timestamp.strftime("%Y-%m-%d %H:%M:%S") if entity.entry_timestamp else ""
context_large = entity.context.context_large if entity.context else ""
# Insert a new row in the table
row_position = self.resultsTable.rowCount()
self.resultsTable.insertRow(row_position)
search_terms = self.query_text.split()
self.highlight_delegate = HighlightDelegate(self.resultsTable, search_terms)
# Set the values for each column
self.resultsTable.setItem(row_position, 0, QTableWidgetItem(distinct_entity))
self.resultsTable.setItem(row_position, 1, QTableWidgetItem(entity_type))
self.resultsTable.setItem(row_position, 2, QTableWidgetItem(file_name))
self.resultsTable.setItem(row_position, 3, QTableWidgetItem(line_number))
self.resultsTable.setItem(row_position, 4, QTableWidgetItem(entry_timestamp))
context_widget = ScrollableTextWidget(context_large, search_terms, distinct_entity)
self.resultsTable.setCellWidget(row_position, 5, context_widget)
match_score_item = NumericTableWidgetItem(str(score))
self.resultsTable.setItem(row_position, 6, match_score_item)
flag_button_widget = FlagButton(entities_id, entity.flag)
self.resultsTable.setCellWidget(row_position, COLUMN_NAMES.index('Flag'), flag_button_widget)
self.resultsTable.setItem(row_position, 8, QTableWidgetItem(str(entities_id)))
self.resultsTable.setRowHeight(row_position, DEFAULT_ROW_HEIGHT)
for column_index in range(self.resultsTable.columnCount()):
if column_index in [0, 1, 2, 3, 4]:
self.resultsTable.setItemDelegateForColumn(column_index, self.highlight_delegate)
self.resultsTable.update()
except Exception as e:
logging.error(f"Error inserting row: {e}")
def adjust_column_widths(self):
for i, width in enumerate(COLUMN_WIDTHS):
self.resultsTable.setColumnWidth(i, width)
def flagVisibleItems(self):
"""Flag all visible items in the table."""
for row in range(self.resultsTable.rowCount()):
if not self.resultsTable.isRowHidden(row):
flag_button_widget = self.resultsTable.cellWidget(row, COLUMN_NAMES.index('Flag'))
if flag_button_widget and not flag_button_widget.flag:
flag_button_widget.toggle_flag()
def unflagVisibleItems(self):
"""Unflag all visible items in the table."""
for row in range(self.resultsTable.rowCount()):
if not self.resultsTable.isRowHidden(row):
flag_button_widget = self.resultsTable.cellWidget(row, COLUMN_NAMES.index('Flag'))
if flag_button_widget and flag_button_widget.flag:
flag_button_widget.toggle_flag()
def clearAllFlags(self):
"""Ask for confirmation and clear all flags in the table if confirmed."""
reply = QMessageBox.question(self, 'Confirm Action',
"Sollen tatsächlich alle Markierungen entfernt werden?\nDas wirkt sich auf alle Einträge in der Datenbank aus!",
QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
if reply == QMessageBox.Yes:
# Clear all flags in the database
FlagButton.clearAllFlagsInDatabase()
# Update all flag buttons in the table
for row in range(self.resultsTable.rowCount()):
flag_button_widget = self.resultsTable.cellWidget(row, COLUMN_NAMES.index('Flag'))
if flag_button_widget and flag_button_widget.flag:
flag_button_widget.flag = False
flag_button_widget.button.setText("_")
flag_button_widget.update_button_style()
# Update all flag buttons in the table
for row in range(self.resultsTable.rowCount()):
flag_button_widget = self.resultsTable.cellWidget(row, COLUMN_NAMES.index('Flag'))
if flag_button_widget and flag_button_widget.flag:
flag_button_widget.flag = False
flag_button_widget.button.setText("_")
flag_button_widget.update_button_style()
def applyTextFilter(self, column_index, filter_text):
if filter_text:
for row in range(self.resultsTable.rowCount()):
item = self.resultsTable.item(row, column_index)
show_row = not filter_text or (item and filter_text.lower() in item.text().lower())
self.resultsTable.setRowHidden(row, not show_row)
def applyDistinctEntityTextFilter(self):
filter_text = self.distinct_entity_edit.text()
self.applyTextFilter(COLUMN_NAMES.index('Distinct Entity'), filter_text)
def applyFileNameTextFilter(self):
filter_text = self.file_name_edit.text()
self.applyTextFilter(COLUMN_NAMES.index('File Name'), filter_text)
def applyLineNumberTextFilter(self):
filter_text = self.line_number_edit.text()
self.applyTextFilter(COLUMN_NAMES.index('Line Number'), filter_text)
def applyMatchScoreTextFilter(self):
filter_text = self.match_score_edit.text()
self.applyTextFilter(COLUMN_NAMES.index('Match Score'), filter_text)
def applyTimestampTextFilter(self):
filter_text = self.timestamp_edit.text()
self.applyTextFilter(COLUMN_NAMES.index('Timestamp'), filter_text)
def applyIdentifierTextFilter(self):
filter_text = self.identifier_edit.text()
self.applyTextFilter(COLUMN_NAMES.index('Identifier'), filter_text)
def applyEntityTypeFilter(self):
selected_type = self.entityTypeComboBox.currentData()
entity_type_column = COLUMN_NAMES.index('Entity Type')
for row in range(self.resultsTable.rowCount()):
item = self.resultsTable.item(row, entity_type_column)
show_row = (selected_type is None or (item and item.text() == selected_type))
self.resultsTable.setRowHidden(row, not show_row)
def applyTimestampFilter(self):
start_date = self.startDateEdit.dateTime().toPyDateTime()
end_date = self.endDateEdit.dateTime().toPyDateTime()
timestamp_column = COLUMN_NAMES.index('Timestamp')
for row in range(self.resultsTable.rowCount()):
item = self.resultsTable.item(row, timestamp_column)
if item and item.text() != "":
row_timestamp = datetime.datetime.strptime(item.text(), "%Y-%m-%d %H:%M:%S")
show_row = start_date <= row_timestamp <= end_date
self.resultsTable.setRowHidden(row, not show_row)
else:
self.resultsTable.setRowHidden(row, False)
def applyAllFilters(self):
self.applyDistinctEntityTextFilter()
self.applyFileNameTextFilter()
self.applyLineNumberTextFilter()
self.applyMatchScoreTextFilter()
self.applyTimestampTextFilter()
self.applyEntityTypeFilter()
self.applyTimestampFilter()
def clearAllFilters(self):
self.entityTypeComboBox.setCurrentIndex(0)
self.startDateEdit.setDateTime(QDateTime(QDateTime(2009, 1, 1, 0, 0, 0)))
self.endDateEdit.setDateTime(QDateTime.currentDateTime())
self.distinct_entity_edit.clear()
self.file_name_edit.clear()
self.line_number_edit.clear()
self.match_score_edit.clear()
self.timestamp_edit.clear()
self.identifier_edit.clear()
self.applyAllFilters()
class QueryLineEdit(QLineEdit):
returnPressed = pyqtSignal()
def keyPressEvent(self, event):
if event.key() == Qt.Key_Return:
self.returnPressed.emit()
else:
super().keyPressEvent(event)
class HighlightDelegate(QStyledItemDelegate):
def __init__(self, parent=None, search_terms=None):
super().__init__(parent)
self.search_terms = search_terms or []
def paint(self, painter, option, index):
painter.save()
# Set text color and other options
options = QTextOption()
options.setWrapMode(QTextOption.WrapAtWordBoundaryOrAnywhere)
document = QTextDocument()
document.setDefaultTextOption(options)
document.setDefaultFont(option.font)
# Prepare highlighted text
text = index.model().data(index)
highlighted_text = self.get_highlighted_text(text)
document.setHtml(highlighted_text)
# Set the width of the document to the cell width
document.setTextWidth(option.rect.width())
# Draw the contents
painter.translate(option.rect.topLeft())
document.drawContents(painter)
painter.restore()
def get_highlighted_text(self, text):
if text is None:
text = ""
text_with_color = f"<span style='color: white;'>{text}</span>"
for term in self.search_terms:
# Retain the '+' at the beginning and strip other special characters
is_positive = term.startswith('+')
clean_term = re.sub(r'[^\w\s]', '', term.lstrip('+-')).lower()
if is_positive and clean_term.lower() in text.lower():
# Use regex for case-insensitive search and replace
regex = re.compile(re.escape(clean_term), re.IGNORECASE)
highlighted_term = f"<span style='background-color: yellow; color: black;'>{clean_term}</span>"
text_with_color = regex.sub(highlighted_term, text_with_color)
return text_with_color.replace("\n", "<br>")
class ScrollableTextWidget(QWidget):
def __init__(self, text, search_terms, distinct_entity, parent=None):
super().__init__(parent)
layout = QVBoxLayout(self)
layout.setContentsMargins(0, 0, 0, 0)
self.text_edit = CustomTextEdit(self)
self.text_edit.setReadOnly(True)
# Apply styles including scrollbar styles
self.text_edit.setStyleSheet("""
QTextEdit {
background-color: #2A2F35; /* Dark blue-ish background */
color: white; /* White text */
}
QTextEdit QScrollBar:vertical {
border: none;
background-color: #3A3F44; /* Dark scrollbar background */
width: 8px; /* Width of the scrollbar */
}
QTextEdit QScrollBar::handle:vertical {
background-color: #6E6E6E; /* Scroll handle color */
border-radius: 4px; /* Rounded corners for the handle */
}
QTextEdit QScrollBar::add-line:vertical, QTextEdit QScrollBar::sub-line:vertical {
background: none;
}
""")
# Set the text with highlighting
self.setHighlightedText(text, search_terms, distinct_entity)
layout.addWidget(self.text_edit)
# Scroll to the distinct entity
self.scroll_to_text(distinct_entity)
def setHighlightedText(self, text, search_terms, distinct_entity):
if text is None:
text = ""
# Wrap the original text in a span to maintain color
text_with_color = f"<span style='color: white;'>{text}</span>"
# Highlight distinct entity in a different color
if distinct_entity:
distinct_entity_escaped = html.escape(distinct_entity)
text_with_color = re.sub(
re.escape(distinct_entity_escaped),
lambda match: f"<span style='background-color: blue; color: white;'>{match.group()}</span>",
text_with_color,
flags=re.IGNORECASE
)
for term in search_terms:
# Check if the term starts with '+'
is_positive = term.startswith('+')
clean_term = re.sub(r'[^\w\s]', '', term.lstrip('+-'))
# If the term starts with '+', highlight all matches regardless of case
if is_positive or clean_term.lower() in text.lower():
regex = re.compile(re.escape(clean_term), re.IGNORECASE)
highlighted_term = f"<span style='background-color: yellow; color: black;'>{clean_term}</span>"
text_with_color = regex.sub(highlighted_term, text_with_color)
self.text_edit.setHtml(text_with_color.replace("\n", "<br>"))
def scroll_to_text(self, text):
if text:
cursor = self.text_edit.document().find(text)
self.text_edit.setTextCursor(cursor)
class CustomTextEdit(QTextEdit):
def __init__(self, parent=None):
super().__init__(parent)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded) # Enable vertical scrollbar as needed
def wheelEvent(self, event):
# Always handle the wheel event within QTextEdit
super().wheelEvent(event)
# Stop propagation of the event to parent
if self.verticalScrollBar().isVisible():
event.accept()
else:
event.ignore()
class FlagButton(QWidget):
def __init__(self, entities_id, flag, parent=None):
super().__init__(parent)
self.entities_id = entities_id
self.flag = flag
self.layout = QHBoxLayout(self)
self.button = QPushButton("FLAG" if flag else "_", self)
self.update_button_style()
self.button.clicked.connect(self.toggle_flag)
self.layout.addWidget(self.button)
self.layout.setContentsMargins(0, 0, 0, 0)
self.setLayout(self.layout)
def toggle_flag(self):
# Toggle the flag
self.flag = not self.flag
self.button.setText("FLAG" if self.flag else "_")
self.update_button_style()
# Update the flag in the database
with session_scope() as session:
entity = session.query(EntitiesTable).filter(EntitiesTable.entities_id == self.entities_id).first()
if entity:
entity.flag = self.flag
session.commit()
@staticmethod
def clearAllFlagsInDatabase():
with session_scope() as session:
entities = session.query(EntitiesTable).all()
for entity in entities:
entity.flag = False
session.commit()
def update_button_style(self):
if self.flag:
self.button.setStyleSheet("QPushButton { background-color: yellow; color: black; }")
else:
self.button.setStyleSheet("")
class NumericTableWidgetItem(QTableWidgetItem):
def __lt__(self, other):
return float(self.text()) < float(other.text())