feat: move status messages to inline label on settings row
Replace the bottom status bar with a right-aligned label on the settings row, saving vertical space. Add "Export complete" message when a batch finishes. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
from PyQt6.QtWidgets import (
|
from PyQt6.QtWidgets import (
|
||||||
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
||||||
QLabel, QPushButton, QLineEdit, QFileDialog, QFrame, QStatusBar,
|
QLabel, QPushButton, QLineEdit, QFileDialog, QFrame,
|
||||||
QListWidget, QListWidgetItem, QAbstractItemView, QSplitter, QToolTip,
|
QListWidget, QListWidgetItem, QAbstractItemView, QSplitter, QToolTip,
|
||||||
QComboBox, QCheckBox, QSpinBox, QDoubleSpinBox,
|
QComboBox, QCheckBox, QSpinBox, QDoubleSpinBox,
|
||||||
QMessageBox, QInputDialog,
|
QMessageBox, QInputDialog,
|
||||||
@@ -1808,7 +1808,6 @@ def main():
|
|||||||
QComboBox QAbstractItemView { background: #2a2a2a; border: 1px solid #555; selection-background-color: #3a6ea8; }
|
QComboBox QAbstractItemView { background: #2a2a2a; border: 1px solid #555; selection-background-color: #3a6ea8; }
|
||||||
QSpinBox, QDoubleSpinBox { background: #2a2a2a; border: 1px solid #555; padding: 3px; border-radius: 3px; }
|
QSpinBox, QDoubleSpinBox { background: #2a2a2a; border: 1px solid #555; padding: 3px; border-radius: 3px; }
|
||||||
QCheckBox::indicator { width: 14px; height: 14px; }
|
QCheckBox::indicator { width: 14px; height: 14px; }
|
||||||
QStatusBar { color: #aaa; }
|
|
||||||
QListWidget { background: #252525; alternate-background-color: #2a2a2a; }
|
QListWidget { background: #252525; alternate-background-color: #2a2a2a; }
|
||||||
QListWidget::item { padding: 4px; color: #ccc; }
|
QListWidget::item { padding: 4px; color: #ccc; }
|
||||||
QListWidget::item:alternate { color: #ddd; }
|
QListWidget::item:alternate { color: #ddd; }
|
||||||
@@ -2160,6 +2159,13 @@ class MainWindow(QMainWindow):
|
|||||||
settings_row.addWidget(self._chk_rand_square)
|
settings_row.addWidget(self._chk_rand_square)
|
||||||
settings_row.addWidget(self._chk_track)
|
settings_row.addWidget(self._chk_track)
|
||||||
settings_row.addStretch()
|
settings_row.addStretch()
|
||||||
|
self._lbl_status = QLabel()
|
||||||
|
self._lbl_status.setStyleSheet("color: #888; font-size: 11px;")
|
||||||
|
self._lbl_status.setAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
|
||||||
|
self._status_timer = QTimer(self)
|
||||||
|
self._status_timer.setSingleShot(True)
|
||||||
|
self._status_timer.timeout.connect(lambda: self._lbl_status.clear())
|
||||||
|
settings_row.addWidget(self._lbl_status)
|
||||||
|
|
||||||
right = QWidget()
|
right = QWidget()
|
||||||
right_layout = QVBoxLayout(right)
|
right_layout = QVBoxLayout(right)
|
||||||
@@ -2209,7 +2215,7 @@ class MainWindow(QMainWindow):
|
|||||||
splitter.setCollapsible(1, False)
|
splitter.setCollapsible(1, False)
|
||||||
|
|
||||||
self.setCentralWidget(splitter)
|
self.setCentralWidget(splitter)
|
||||||
self.setStatusBar(QStatusBar())
|
self.setStatusBar(None)
|
||||||
if saved_ratio != "Off":
|
if saved_ratio != "Off":
|
||||||
self._crop_bar.setVisible(True)
|
self._crop_bar.setVisible(True)
|
||||||
self._mpv.set_crop_overlay(_RATIOS[saved_ratio], self._crop_center)
|
self._mpv.set_crop_overlay(_RATIOS[saved_ratio], self._crop_center)
|
||||||
@@ -2337,7 +2343,15 @@ class MainWindow(QMainWindow):
|
|||||||
if self._file_path:
|
if self._file_path:
|
||||||
self._refresh_markers()
|
self._refresh_markers()
|
||||||
_log(f"Profile switched: {text}")
|
_log(f"Profile switched: {text}")
|
||||||
self.statusBar().showMessage(f"Profile: {text}", 3000)
|
self._show_status(f"Profile: {text}", 3000)
|
||||||
|
|
||||||
|
def _show_status(self, msg: str, timeout: int = 0) -> None:
|
||||||
|
"""Show a message in the inline status label. Timeout in ms (0 = sticky)."""
|
||||||
|
self._lbl_status.setText(msg)
|
||||||
|
if timeout > 0:
|
||||||
|
self._status_timer.start(timeout)
|
||||||
|
else:
|
||||||
|
self._status_timer.stop()
|
||||||
|
|
||||||
def _on_hide_exported_toggled(self, hide: bool) -> None:
|
def _on_hide_exported_toggled(self, hide: bool) -> None:
|
||||||
self._settings.setValue("hide_exported", "true" if hide else "false")
|
self._settings.setValue("hide_exported", "true" if hide else "false")
|
||||||
@@ -2432,9 +2446,9 @@ class MainWindow(QMainWindow):
|
|||||||
if os.path.basename(self._file_path) != queried:
|
if os.path.basename(self._file_path) != queried:
|
||||||
return
|
return
|
||||||
if match:
|
if match:
|
||||||
self.statusBar().showMessage(f"⚠ Similar to already processed: {match}")
|
self._show_status(f"⚠ Similar to already processed: {match}")
|
||||||
else:
|
else:
|
||||||
self.statusBar().clearMessage()
|
self._lbl_status.clear()
|
||||||
self._timeline.set_markers(markers)
|
self._timeline.set_markers(markers)
|
||||||
|
|
||||||
def _refresh_markers(self) -> None:
|
def _refresh_markers(self) -> None:
|
||||||
@@ -2461,7 +2475,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._update_next_label()
|
self._update_next_label()
|
||||||
n = len(deleted) if deleted else 1
|
n = len(deleted) if deleted else 1
|
||||||
_log(f"Deleted marker: {n} clip(s) from DB")
|
_log(f"Deleted marker: {n} clip(s) from DB")
|
||||||
self.statusBar().showMessage(
|
self._show_status(
|
||||||
f"Deleted marker ({n} clip{'s' if n != 1 else ''})", 4000
|
f"Deleted marker ({n} clip{'s' if n != 1 else ''})", 4000
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2472,7 +2486,7 @@ class MainWindow(QMainWindow):
|
|||||||
]
|
]
|
||||||
self._timeline.set_crop_keyframes(self._crop_keyframes)
|
self._timeline.set_crop_keyframes(self._crop_keyframes)
|
||||||
_log(f"Deleted crop keyframe @ {format_time(time)} ({len(self._crop_keyframes)} remaining)")
|
_log(f"Deleted crop keyframe @ {format_time(time)} ({len(self._crop_keyframes)} remaining)")
|
||||||
self.statusBar().showMessage(f"Deleted keyframe @ {format_time(time)}", 3000)
|
self._show_status(f"Deleted keyframe @ {format_time(time)}", 3000)
|
||||||
|
|
||||||
def _on_marker_clicked(self, start_time: float, output_path: str) -> None:
|
def _on_marker_clicked(self, start_time: float, output_path: str) -> None:
|
||||||
self._overwrite_path = output_path
|
self._overwrite_path = output_path
|
||||||
@@ -2517,7 +2531,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._crop_bar.set_crop_center(self._crop_center)
|
self._crop_bar.set_crop_center(self._crop_center)
|
||||||
if ratio != "Off":
|
if ratio != "Off":
|
||||||
self._mpv.set_crop_overlay(_RATIOS[ratio], self._crop_center)
|
self._mpv.set_crop_overlay(_RATIOS[ratio], self._crop_center)
|
||||||
self.statusBar().showMessage(
|
self._show_status(
|
||||||
f"Overwrite mode: {group_dir} ({n} clip{'s' if n != 1 else ''}) — export to replace", 5000
|
f"Overwrite mode: {group_dir} ({n} clip{'s' if n != 1 else ''}) — export to replace", 5000
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -2583,7 +2597,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._update_next_label()
|
self._update_next_label()
|
||||||
self._refresh_markers()
|
self._refresh_markers()
|
||||||
self._refresh_playlist_checks()
|
self._refresh_playlist_checks()
|
||||||
self.statusBar().showMessage(f"Deleted {n} clip{'s' if n != 1 else ''}: {group_dir}")
|
self._show_status(f"Deleted {n} clip{'s' if n != 1 else ''}: {group_dir}")
|
||||||
|
|
||||||
def _on_portrait_ratio_changed(self, text: str) -> None:
|
def _on_portrait_ratio_changed(self, text: str) -> None:
|
||||||
ratio = None if text == "Off" else text
|
ratio = None if text == "Off" else text
|
||||||
@@ -2861,7 +2875,7 @@ class MainWindow(QMainWindow):
|
|||||||
if not self._file_path:
|
if not self._file_path:
|
||||||
return
|
return
|
||||||
if self._export_worker and self._export_worker.isRunning():
|
if self._export_worker and self._export_worker.isRunning():
|
||||||
self.statusBar().showMessage("Export already running…")
|
self._show_status("Export already running…")
|
||||||
return
|
return
|
||||||
|
|
||||||
fmt = self._cmb_format.currentText()
|
fmt = self._cmb_format.currentText()
|
||||||
@@ -2944,7 +2958,7 @@ class MainWindow(QMainWindow):
|
|||||||
# Subject tracking: re-detect crop center per sub-clip.
|
# Subject tracking: re-detect crop center per sub-clip.
|
||||||
if self._chk_track.isChecked() and any(j[2] for j in jobs):
|
if self._chk_track.isChecked() and any(j[2] for j in jobs):
|
||||||
starts = [j[0] for j in jobs]
|
starts = [j[0] for j in jobs]
|
||||||
self.statusBar().showMessage(f"Tracking subject across {len(jobs)} clip(s)…")
|
self._show_status(f"Tracking subject across {len(jobs)} clip(s)…")
|
||||||
QApplication.processEvents()
|
QApplication.processEvents()
|
||||||
centers = track_centers_for_jobs(
|
centers = track_centers_for_jobs(
|
||||||
self._file_path, self._cursor, base_center, starts,
|
self._file_path, self._cursor, base_center, starts,
|
||||||
@@ -2966,7 +2980,7 @@ class MainWindow(QMainWindow):
|
|||||||
self._export_spread = self._spn_spread.value()
|
self._export_spread = self._spn_spread.value()
|
||||||
|
|
||||||
self._btn_export.setEnabled(False)
|
self._btn_export.setEnabled(False)
|
||||||
self.statusBar().showMessage(f"Exporting {len(jobs)} clip(s)…")
|
self._show_status(f"Exporting {len(jobs)} clip(s)…")
|
||||||
|
|
||||||
# Show one pending marker at the cursor position for the whole batch.
|
# Show one pending marker at the cursor position for the whole batch.
|
||||||
first_out = jobs[0][1]
|
first_out = jobs[0][1]
|
||||||
@@ -3018,7 +3032,7 @@ class MainWindow(QMainWindow):
|
|||||||
upsert_clip_annotation(folder, path, label)
|
upsert_clip_annotation(folder, path, label)
|
||||||
self._last_export_path = path
|
self._last_export_path = path
|
||||||
_log(f" clip done: {os.path.basename(path)}")
|
_log(f" clip done: {os.path.basename(path)}")
|
||||||
self.statusBar().showMessage(f"Exported: {os.path.basename(path)}")
|
self._show_status(f"Exported: {os.path.basename(path)}")
|
||||||
|
|
||||||
def _on_batch_done(self):
|
def _on_batch_done(self):
|
||||||
"""Called once after all clips in the batch are done."""
|
"""Called once after all clips in the batch are done."""
|
||||||
@@ -3029,6 +3043,11 @@ class MainWindow(QMainWindow):
|
|||||||
self._btn_export.setEnabled(True)
|
self._btn_export.setEnabled(True)
|
||||||
self._btn_export.setText("Export")
|
self._btn_export.setText("Export")
|
||||||
self._btn_export.setStyleSheet("")
|
self._btn_export.setStyleSheet("")
|
||||||
|
if self._last_export_path:
|
||||||
|
group = os.path.basename(os.path.dirname(self._last_export_path))
|
||||||
|
self._show_status(f"Export complete: {group}", 5000)
|
||||||
|
else:
|
||||||
|
self._show_status("Export complete", 5000)
|
||||||
self._btn_delete.setEnabled(True)
|
self._btn_delete.setEnabled(True)
|
||||||
self._btn_delete.setText("Delete")
|
self._btn_delete.setText("Delete")
|
||||||
self._refresh_markers()
|
self._refresh_markers()
|
||||||
@@ -3051,13 +3070,13 @@ class MainWindow(QMainWindow):
|
|||||||
self._btn_export.setText("Export")
|
self._btn_export.setText("Export")
|
||||||
self._btn_export.setStyleSheet("")
|
self._btn_export.setStyleSheet("")
|
||||||
self._refresh_markers() # remove stale pending marker
|
self._refresh_markers() # remove stale pending marker
|
||||||
self.statusBar().showMessage(f"Export error: {msg}")
|
self._show_status(f"Export error: {msg}")
|
||||||
|
|
||||||
def _on_cancel_export(self):
|
def _on_cancel_export(self):
|
||||||
if self._export_worker and self._export_worker.isRunning():
|
if self._export_worker and self._export_worker.isRunning():
|
||||||
self._btn_cancel.setEnabled(False)
|
self._btn_cancel.setEnabled(False)
|
||||||
self._export_worker.cancel()
|
self._export_worker.cancel()
|
||||||
self.statusBar().showMessage("Cancelling export…")
|
self._show_status("Cancelling export…")
|
||||||
|
|
||||||
def _on_export_cancelled(self):
|
def _on_export_cancelled(self):
|
||||||
_log("Export cancelled")
|
_log("Export cancelled")
|
||||||
@@ -3069,7 +3088,7 @@ class MainWindow(QMainWindow):
|
|||||||
markers = self._db.get_markers(os.path.basename(self._file_path), self._profile)
|
markers = self._db.get_markers(os.path.basename(self._file_path), self._profile)
|
||||||
if markers:
|
if markers:
|
||||||
self._playlist.mark_done(self._file_path, len(markers))
|
self._playlist.mark_done(self._file_path, len(markers))
|
||||||
self.statusBar().showMessage("Export cancelled", 4000)
|
self._show_status("Export cancelled", 4000)
|
||||||
|
|
||||||
def changeEvent(self, event):
|
def changeEvent(self, event):
|
||||||
super().changeEvent(event)
|
super().changeEvent(event)
|
||||||
|
|||||||
Reference in New Issue
Block a user