Files
sci-gui-agent-benchmark/annotation/ducktrack/app.py

251 lines
9.7 KiB
Python

import os
import sys
from platform import system
from PyQt6.QtCore import QTimer, pyqtSlot
from PyQt6.QtGui import QAction, QIcon
from PyQt6.QtWidgets import (QApplication, QCheckBox, QDialog, QFileDialog,
QFormLayout, QLabel, QLineEdit, QMenu,
QMessageBox, QPushButton, QSystemTrayIcon,
QTextEdit, QVBoxLayout, QWidget)
from .obs_client import close_obs, is_obs_running, open_obs
from .playback import Player, get_latest_recording
from .recorder import Recorder
from .util import get_recordings_dir, open_file
class TitleDescriptionDialog(QDialog):
def __init__(self, parent=None):
super().__init__(parent)
self.setWindowTitle("Recording Details")
layout = QVBoxLayout(self)
self.form_layout = QFormLayout()
self.title_label = QLabel("Title:")
self.title_input = QLineEdit(self)
self.form_layout.addRow(self.title_label, self.title_input)
self.description_label = QLabel("Description:")
self.description_input = QTextEdit(self)
self.form_layout.addRow(self.description_label, self.description_input)
layout.addLayout(self.form_layout)
self.submit_button = QPushButton("Save", self)
self.submit_button.clicked.connect(self.accept)
layout.addWidget(self.submit_button)
def get_values(self):
return self.title_input.text(), self.description_input.toPlainText()
class MainInterface(QWidget):
def __init__(self, app: QApplication):
super().__init__()
self.tray = QSystemTrayIcon(QIcon(resource_path("assets/duck.png")))
self.tray.show()
self.app = app
self.init_tray()
self.init_window()
if not is_obs_running():
self.obs_process = open_obs()
def init_window(self):
self.setWindowTitle("DuckTrack")
layout = QVBoxLayout(self)
self.toggle_record_button = QPushButton("Start Recording", self)
self.toggle_record_button.clicked.connect(self.toggle_record)
layout.addWidget(self.toggle_record_button)
self.toggle_pause_button = QPushButton("Pause Recording", self)
self.toggle_pause_button.clicked.connect(self.toggle_pause)
self.toggle_pause_button.setEnabled(False)
layout.addWidget(self.toggle_pause_button)
self.show_recordings_button = QPushButton("Show Recordings", self)
self.show_recordings_button.clicked.connect(lambda: open_file(get_recordings_dir()))
layout.addWidget(self.show_recordings_button)
self.play_latest_button = QPushButton("Play Latest Recording", self)
self.play_latest_button.clicked.connect(self.play_latest_recording)
layout.addWidget(self.play_latest_button)
self.play_custom_button = QPushButton("Play Custom Recording", self)
self.play_custom_button.clicked.connect(self.play_custom_recording)
layout.addWidget(self.play_custom_button)
self.replay_recording_button = QPushButton("Replay Recording", self)
self.replay_recording_button.clicked.connect(self.replay_recording)
self.replay_recording_button.setEnabled(False)
layout.addWidget(self.replay_recording_button)
self.quit_button = QPushButton("Quit", self)
self.quit_button.clicked.connect(self.quit)
layout.addWidget(self.quit_button)
self.natural_scrolling_checkbox = QCheckBox("Natural Scrolling", self, checked=system() == "Darwin")
layout.addWidget(self.natural_scrolling_checkbox)
self.natural_scrolling_checkbox.stateChanged.connect(self.toggle_natural_scrolling)
self.setLayout(layout)
def init_tray(self):
self.menu = QMenu()
self.tray.setContextMenu(self.menu)
self.toggle_record_action = QAction("Start Recording")
self.toggle_record_action.triggered.connect(self.toggle_record)
self.menu.addAction(self.toggle_record_action)
self.toggle_pause_action = QAction("Pause Recording")
self.toggle_pause_action.triggered.connect(self.toggle_pause)
self.toggle_pause_action.setVisible(False)
self.menu.addAction(self.toggle_pause_action)
self.show_recordings_action = QAction("Show Recordings")
self.show_recordings_action.triggered.connect(lambda: open_file(get_recordings_dir()))
self.menu.addAction(self.show_recordings_action)
self.play_latest_action = QAction("Play Latest Recording")
self.play_latest_action.triggered.connect(self.play_latest_recording)
self.menu.addAction(self.play_latest_action)
self.play_custom_action = QAction("Play Custom Recording")
self.play_custom_action.triggered.connect(self.play_custom_recording)
self.menu.addAction(self.play_custom_action)
self.replay_recording_action = QAction("Replay Recording")
self.replay_recording_action.triggered.connect(self.replay_recording)
self.menu.addAction(self.replay_recording_action)
self.replay_recording_action.setVisible(False)
self.quit_action = QAction("Quit")
self.quit_action.triggered.connect(self.quit)
self.menu.addAction(self.quit_action)
self.menu.addSeparator()
self.natural_scrolling_option = QAction("Natural Scrolling", checkable=True, checked=system() == "Darwin")
self.natural_scrolling_option.triggered.connect(self.toggle_natural_scrolling)
self.menu.addAction(self.natural_scrolling_option)
@pyqtSlot()
def replay_recording(self):
player = Player()
if hasattr(self, "last_played_recording_path"):
player.play(self.last_played_recording_path)
else:
self.display_error_message("No recording has been played yet!")
@pyqtSlot()
def play_latest_recording(self):
player = Player()
recording_path = get_latest_recording()
self.last_played_recording_path = recording_path
self.replay_recording_action.setVisible(True)
self.replay_recording_button.setEnabled(True)
player.play(recording_path)
@pyqtSlot()
def play_custom_recording(self):
player = Player()
directory = QFileDialog.getExistingDirectory(None, "Select Recording", get_recordings_dir())
if directory:
self.last_played_recording_path = directory
self.replay_recording_button.setEnabled(True)
self.replay_recording_action.setVisible(True)
player.play(directory)
@pyqtSlot()
def quit(self):
if hasattr(self, "recorder_thread"):
self.toggle_record()
if hasattr(self, "obs_process"):
close_obs(self.obs_process)
self.app.quit()
def closeEvent(self, event):
self.quit()
@pyqtSlot()
def toggle_natural_scrolling(self):
sender = self.sender()
if sender == self.natural_scrolling_checkbox:
state = self.natural_scrolling_checkbox.isChecked()
self.natural_scrolling_option.setChecked(state)
else:
state = self.natural_scrolling_option.isChecked()
self.natural_scrolling_checkbox.setChecked(state)
@pyqtSlot()
def toggle_pause(self):
if self.recorder_thread._is_paused:
self.recorder_thread.resume_recording()
self.toggle_pause_action.setText("Pause Recording")
self.toggle_pause_button.setText("Pause Recording")
else:
self.recorder_thread.pause_recording()
self.toggle_pause_action.setText("Resume Recording")
self.toggle_pause_button.setText("Resume Recording")
@pyqtSlot()
def toggle_record(self):
if not hasattr(self, "recorder_thread"):
self.recorder_thread = Recorder(natural_scrolling=self.natural_scrolling_checkbox.isChecked())
self.recorder_thread.recording_stopped.connect(self.on_recording_stopped)
self.recorder_thread.start()
self.update_menu(True)
else:
self.recorder_thread.stop_recording()
self.recorder_thread.terminate()
recording_dir = self.recorder_thread.recording_path
del self.recorder_thread
dialog = TitleDescriptionDialog()
QTimer.singleShot(0, dialog.raise_)
result = dialog.exec()
if result == QDialog.DialogCode.Accepted:
title, description = dialog.get_values()
if title:
renamed_dir = os.path.join(os.path.dirname(recording_dir), title)
os.rename(recording_dir, renamed_dir)
with open(os.path.join(renamed_dir, 'README.md'), 'w') as f:
f.write(description)
self.on_recording_stopped()
@pyqtSlot()
def on_recording_stopped(self):
self.update_menu(False)
def update_menu(self, is_recording: bool):
self.toggle_record_button.setText("Stop Recording" if is_recording else "Start Recording")
self.toggle_record_action.setText("Stop Recording" if is_recording else "Start Recording")
self.toggle_pause_button.setEnabled(is_recording)
self.toggle_pause_action.setVisible(is_recording)
def display_error_message(self, message):
QMessageBox.critical(None, "Error", message)
def resource_path(relative_path: str) -> str:
if hasattr(sys, '_MEIPASS'):
base_path = getattr(sys, "_MEIPASS")
else:
base_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), '..')
return os.path.join(base_path, relative_path)