667 lines
29 KiB
Python
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())
|
|
|