feat: color exported scan result rows green
Scan panel rows whose range contains an exported clip's start time are colored green. Priority: disabled > negative > exported > default. Exported state refreshes automatically after an auto-export batch completes on the current file. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
+11
@@ -418,6 +418,17 @@ class ProcessedDB:
|
|||||||
pass
|
pass
|
||||||
return max_n
|
return max_n
|
||||||
|
|
||||||
|
def get_scan_export_times(self, filename: str, profile: str) -> list[float]:
|
||||||
|
"""Return start_times of scan_export=1 rows for this file/profile."""
|
||||||
|
if not self._enabled:
|
||||||
|
return []
|
||||||
|
rows = self._con.execute(
|
||||||
|
"SELECT start_time FROM processed"
|
||||||
|
" WHERE filename = ? AND profile = ? AND scan_export = 1",
|
||||||
|
(filename, profile),
|
||||||
|
).fetchall()
|
||||||
|
return [r[0] for r in rows]
|
||||||
|
|
||||||
def delete_scan_exports(self, filename: str, profile: str) -> int:
|
def delete_scan_exports(self, filename: str, profile: str) -> int:
|
||||||
"""Delete all scan_export entries for *filename* in *profile*.
|
"""Delete all scan_export entries for *filename* in *profile*.
|
||||||
|
|
||||||
|
|||||||
@@ -728,6 +728,7 @@ class ScanResultsPanel(QWidget):
|
|||||||
self._filename = ""
|
self._filename = ""
|
||||||
self._profile = ""
|
self._profile = ""
|
||||||
self._neg_times: set[float] = set()
|
self._neg_times: set[float] = set()
|
||||||
|
self._exported_times: list[float] = []
|
||||||
self._editing = False # guard against cellChanged during programmatic updates
|
self._editing = False # guard against cellChanged during programmatic updates
|
||||||
self._undo_stack: list[tuple] = [] # list of (action, *data)
|
self._undo_stack: list[tuple] = [] # list of (action, *data)
|
||||||
|
|
||||||
@@ -792,6 +793,7 @@ class ScanResultsPanel(QWidget):
|
|||||||
self._filename = filename
|
self._filename = filename
|
||||||
self._profile = profile
|
self._profile = profile
|
||||||
self._neg_times = self._db.get_hard_negative_times(filename, profile)
|
self._neg_times = self._db.get_hard_negative_times(filename, profile)
|
||||||
|
self._exported_times = self._db.get_scan_export_times(filename, profile)
|
||||||
self._tabs.blockSignals(True)
|
self._tabs.blockSignals(True)
|
||||||
self._tabs.clear()
|
self._tabs.clear()
|
||||||
results = self._db.get_scan_results(filename, profile)
|
results = self._db.get_scan_results(filename, profile)
|
||||||
@@ -800,6 +802,51 @@ class ScanResultsPanel(QWidget):
|
|||||||
self._populate_version_combos()
|
self._populate_version_combos()
|
||||||
self._tabs.blockSignals(False)
|
self._tabs.blockSignals(False)
|
||||||
|
|
||||||
|
def _is_row_exported(self, start: float, end: float) -> bool:
|
||||||
|
for t in self._exported_times:
|
||||||
|
if start <= t <= end:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _row_fg(self, table: QTableWidget, row: int, disabled: bool,
|
||||||
|
default_fg: QColor) -> QColor:
|
||||||
|
if disabled:
|
||||||
|
return QColor(100, 100, 100)
|
||||||
|
item0 = table.item(row, 0)
|
||||||
|
item1 = table.item(row, 1)
|
||||||
|
start = item0.data(Qt.ItemDataRole.UserRole + 1) if item0 else None
|
||||||
|
end = item1.data(Qt.ItemDataRole.UserRole) if item1 else None
|
||||||
|
if start is not None and float(start) in self._neg_times:
|
||||||
|
return QColor(220, 60, 60)
|
||||||
|
if (start is not None and end is not None
|
||||||
|
and self._is_row_exported(float(start), float(end))):
|
||||||
|
return QColor(90, 200, 120)
|
||||||
|
return default_fg
|
||||||
|
|
||||||
|
def refresh_exported_state(self) -> None:
|
||||||
|
"""Reload exported times from DB and recolor all visible rows."""
|
||||||
|
if not self._filename:
|
||||||
|
return
|
||||||
|
self._exported_times = self._db.get_scan_export_times(
|
||||||
|
self._filename, self._profile)
|
||||||
|
self._editing = True
|
||||||
|
for i in range(self._tabs.count()):
|
||||||
|
table = self._tab_table(i)
|
||||||
|
if table is None:
|
||||||
|
continue
|
||||||
|
default_fg = table.palette().color(table.foregroundRole())
|
||||||
|
for r in range(table.rowCount()):
|
||||||
|
item0 = table.item(r, 0)
|
||||||
|
if item0 is None:
|
||||||
|
continue
|
||||||
|
disabled = item0.data(Qt.ItemDataRole.UserRole + 2) or False
|
||||||
|
fg = self._row_fg(table, r, disabled, default_fg)
|
||||||
|
for col in range(3):
|
||||||
|
it = table.item(r, col)
|
||||||
|
if it is not None:
|
||||||
|
it.setForeground(fg)
|
||||||
|
self._editing = False
|
||||||
|
|
||||||
def add_scan_results(self, model: str,
|
def add_scan_results(self, model: str,
|
||||||
regions: list[tuple[float, float, float]]) -> None:
|
regions: list[tuple[float, float, float]]) -> None:
|
||||||
"""Add/replace a tab with new scan results and save to DB."""
|
"""Add/replace a tab with new scan results and save to DB."""
|
||||||
@@ -851,8 +898,7 @@ class ScanResultsPanel(QWidget):
|
|||||||
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
|
header.setSectionResizeMode(1, QHeaderView.ResizeMode.Stretch)
|
||||||
header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
|
header.setSectionResizeMode(2, QHeaderView.ResizeMode.ResizeToContents)
|
||||||
|
|
||||||
red = QColor(220, 60, 60)
|
default_fg = table.palette().color(table.foregroundRole())
|
||||||
gray = QColor(100, 100, 100)
|
|
||||||
self._editing = True
|
self._editing = True
|
||||||
for i, (row_id, start, end, score, disabled, os_, oe) in enumerate(rows):
|
for i, (row_id, start, end, score, disabled, os_, oe) in enumerate(rows):
|
||||||
t_item = QTableWidgetItem(format_time(start))
|
t_item = QTableWidgetItem(format_time(start))
|
||||||
@@ -871,13 +917,11 @@ class ScanResultsPanel(QWidget):
|
|||||||
sc_item.setFlags(sc_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
|
sc_item.setFlags(sc_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
|
||||||
table.setItem(i, 2, sc_item)
|
table.setItem(i, 2, sc_item)
|
||||||
|
|
||||||
# Color: disabled (gray) > negative (red) > default
|
# Color: disabled (gray) > negative (red) > exported (green) > default
|
||||||
if disabled:
|
fg = self._row_fg(table, i, disabled, default_fg)
|
||||||
|
if fg != default_fg:
|
||||||
for col in range(3):
|
for col in range(3):
|
||||||
table.item(i, col).setForeground(gray)
|
table.item(i, col).setForeground(fg)
|
||||||
elif start in self._neg_times:
|
|
||||||
for col in range(3):
|
|
||||||
table.item(i, col).setForeground(red)
|
|
||||||
self._editing = False
|
self._editing = False
|
||||||
|
|
||||||
table.itemSelectionChanged.connect(
|
table.itemSelectionChanged.connect(
|
||||||
@@ -941,8 +985,7 @@ class ScanResultsPanel(QWidget):
|
|||||||
return
|
return
|
||||||
self._editing = True
|
self._editing = True
|
||||||
table.setRowCount(len(rows))
|
table.setRowCount(len(rows))
|
||||||
red = QColor(220, 60, 60)
|
default_fg = table.palette().color(table.foregroundRole())
|
||||||
gray = QColor(100, 100, 100)
|
|
||||||
for r, (row_id, start, end, score, disabled, os_, oe) in enumerate(rows):
|
for r, (row_id, start, end, score, disabled, os_, oe) in enumerate(rows):
|
||||||
t_item = QTableWidgetItem(format_time(start))
|
t_item = QTableWidgetItem(format_time(start))
|
||||||
t_item.setData(Qt.ItemDataRole.UserRole, row_id)
|
t_item.setData(Qt.ItemDataRole.UserRole, row_id)
|
||||||
@@ -957,12 +1000,10 @@ class ScanResultsPanel(QWidget):
|
|||||||
sc_item = QTableWidgetItem(f"{score:.2f}")
|
sc_item = QTableWidgetItem(f"{score:.2f}")
|
||||||
sc_item.setFlags(sc_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
|
sc_item.setFlags(sc_item.flags() & ~Qt.ItemFlag.ItemIsEditable)
|
||||||
table.setItem(r, 2, sc_item)
|
table.setItem(r, 2, sc_item)
|
||||||
if disabled:
|
fg = self._row_fg(table, r, disabled, default_fg)
|
||||||
|
if fg != default_fg:
|
||||||
for col in range(3):
|
for col in range(3):
|
||||||
table.item(r, col).setForeground(gray)
|
table.item(r, col).setForeground(fg)
|
||||||
elif start in self._neg_times:
|
|
||||||
for col in range(3):
|
|
||||||
table.item(r, col).setForeground(red)
|
|
||||||
self._editing = False
|
self._editing = False
|
||||||
self._tabs.setTabText(i, f"{model} ({len(rows)})")
|
self._tabs.setTabText(i, f"{model} ({len(rows)})")
|
||||||
self.regions_edited.emit()
|
self.regions_edited.emit()
|
||||||
@@ -1056,25 +1097,16 @@ class ScanResultsPanel(QWidget):
|
|||||||
for r in selected_rows]
|
for r in selected_rows]
|
||||||
self._undo_stack.append(("disable", self._tabs.currentIndex(), prev))
|
self._undo_stack.append(("disable", self._tabs.currentIndex(), prev))
|
||||||
|
|
||||||
gray = QColor(100, 100, 100)
|
|
||||||
red = QColor(220, 60, 60)
|
|
||||||
default_fg = table.palette().color(table.foregroundRole())
|
default_fg = table.palette().color(table.foregroundRole())
|
||||||
for row in selected_rows:
|
for row in selected_rows:
|
||||||
item0 = table.item(row, 0)
|
item0 = table.item(row, 0)
|
||||||
row_id = item0.data(Qt.ItemDataRole.UserRole)
|
row_id = item0.data(Qt.ItemDataRole.UserRole)
|
||||||
start = item0.data(Qt.ItemDataRole.UserRole + 1)
|
|
||||||
currently_disabled = item0.data(Qt.ItemDataRole.UserRole + 2) or False
|
currently_disabled = item0.data(Qt.ItemDataRole.UserRole + 2) or False
|
||||||
new_disabled = not currently_disabled
|
new_disabled = not currently_disabled
|
||||||
item0.setData(Qt.ItemDataRole.UserRole + 2, new_disabled)
|
item0.setData(Qt.ItemDataRole.UserRole + 2, new_disabled)
|
||||||
if row_id is not None:
|
if row_id is not None:
|
||||||
self._db.toggle_scan_result_disabled(row_id, new_disabled)
|
self._db.toggle_scan_result_disabled(row_id, new_disabled)
|
||||||
# Update visual
|
fg = self._row_fg(table, row, new_disabled, default_fg)
|
||||||
if new_disabled:
|
|
||||||
fg = gray
|
|
||||||
elif start is not None and float(start) in self._neg_times:
|
|
||||||
fg = red
|
|
||||||
else:
|
|
||||||
fg = default_fg
|
|
||||||
for col in range(3):
|
for col in range(3):
|
||||||
table.item(row, col).setForeground(fg)
|
table.item(row, col).setForeground(fg)
|
||||||
self.regions_edited.emit()
|
self.regions_edited.emit()
|
||||||
@@ -1202,8 +1234,6 @@ class ScanResultsPanel(QWidget):
|
|||||||
|
|
||||||
add_times: list[float] = []
|
add_times: list[float] = []
|
||||||
remove_times: list[float] = []
|
remove_times: list[float] = []
|
||||||
red = QColor(220, 60, 60)
|
|
||||||
gray = QColor(100, 100, 100)
|
|
||||||
default_fg = table.palette().color(table.foregroundRole())
|
default_fg = table.palette().color(table.foregroundRole())
|
||||||
for row in selected_rows:
|
for row in selected_rows:
|
||||||
item0 = table.item(row, 0)
|
item0 = table.item(row, 0)
|
||||||
@@ -1215,11 +1245,10 @@ class ScanResultsPanel(QWidget):
|
|||||||
if t in self._neg_times:
|
if t in self._neg_times:
|
||||||
remove_times.append(t)
|
remove_times.append(t)
|
||||||
self._neg_times.discard(t)
|
self._neg_times.discard(t)
|
||||||
fg = gray if disabled else default_fg
|
|
||||||
else:
|
else:
|
||||||
add_times.append(t)
|
add_times.append(t)
|
||||||
self._neg_times.add(t)
|
self._neg_times.add(t)
|
||||||
fg = gray if disabled else red
|
fg = self._row_fg(table, row, disabled, default_fg)
|
||||||
for col in range(3):
|
for col in range(3):
|
||||||
table.item(row, col).setForeground(fg)
|
table.item(row, col).setForeground(fg)
|
||||||
if add_times:
|
if add_times:
|
||||||
@@ -1287,8 +1316,6 @@ class ScanResultsPanel(QWidget):
|
|||||||
table = self._tab_table(tab_idx)
|
table = self._tab_table(tab_idx)
|
||||||
if table is None:
|
if table is None:
|
||||||
return
|
return
|
||||||
gray = QColor(100, 100, 100)
|
|
||||||
red = QColor(220, 60, 60)
|
|
||||||
default_fg = table.palette().color(table.foregroundRole())
|
default_fg = table.palette().color(table.foregroundRole())
|
||||||
for row, was_disabled in prev:
|
for row, was_disabled in prev:
|
||||||
if row >= table.rowCount():
|
if row >= table.rowCount():
|
||||||
@@ -1298,13 +1325,7 @@ class ScanResultsPanel(QWidget):
|
|||||||
row_id = item0.data(Qt.ItemDataRole.UserRole)
|
row_id = item0.data(Qt.ItemDataRole.UserRole)
|
||||||
if row_id is not None:
|
if row_id is not None:
|
||||||
self._db.toggle_scan_result_disabled(row_id, was_disabled)
|
self._db.toggle_scan_result_disabled(row_id, was_disabled)
|
||||||
start = item0.data(Qt.ItemDataRole.UserRole + 1)
|
fg = self._row_fg(table, row, was_disabled, default_fg)
|
||||||
if was_disabled:
|
|
||||||
fg = gray
|
|
||||||
elif start is not None and float(start) in self._neg_times:
|
|
||||||
fg = red
|
|
||||||
else:
|
|
||||||
fg = default_fg
|
|
||||||
for col in range(3):
|
for col in range(3):
|
||||||
table.item(row, col).setForeground(fg)
|
table.item(row, col).setForeground(fg)
|
||||||
self.regions_edited.emit()
|
self.regions_edited.emit()
|
||||||
@@ -4813,6 +4834,7 @@ class MainWindow(QMainWindow):
|
|||||||
if self._file_path == batch_file:
|
if self._file_path == batch_file:
|
||||||
self._refresh_markers()
|
self._refresh_markers()
|
||||||
self._update_next_label()
|
self._update_next_label()
|
||||||
|
self._scan_panel.refresh_exported_state()
|
||||||
|
|
||||||
_log(f"Auto export complete: {n} clips ({os.path.basename(batch_file)})")
|
_log(f"Auto export complete: {n} clips ({os.path.basename(batch_file)})")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user