318 lines
13 KiB
Python
318 lines
13 KiB
Python
from PyQt5.QtCore import Qt, QTimer, QDir, QSize, QDateTime, QRect, QThread, pyqtSignal, QMutex, QObject, QEvent
|
|
from PyQt5.QtGui import (QImage, QPixmap, QIcon, QColor, QKeySequence, QPainter,
|
|
QPen, QBrush)
|
|
from PyQt5.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout,
|
|
QWidget, QLabel, QPushButton, QComboBox, QSpinBox,
|
|
QFileDialog, QMessageBox, QMenu, QAction, QActionGroup, QGridLayout, QGroupBox,
|
|
QDockWidget, QScrollArea, QToolButton, QDialog,
|
|
QShortcut, QListWidget, QFormLayout, QLineEdit,
|
|
QCheckBox, QTabWidget, QListWidgetItem, QSplitter,
|
|
QProgressBar, QSizePolicy)
|
|
import NetworkCameraDialog
|
|
from todopackage.todo import todo
|
|
import os
|
|
|
|
class CameraSelectorDialog(QDialog):
|
|
def __init__(self, parent=None):
|
|
super().__init__(parent)
|
|
self.setWindowTitle("Camera Selector")
|
|
self.setModal(True)
|
|
self.resize(900, 650) # Increased size for better visibility
|
|
self.setSizeGripEnabled(True)
|
|
|
|
self.detector = parent.detector if parent else None
|
|
self.selected_cameras = []
|
|
|
|
# Main layout
|
|
layout = QVBoxLayout(self)
|
|
|
|
# Instructions with better formatting
|
|
instructions = QLabel(todo.get_instructions_CaSeDi_QLabel())
|
|
print(todo.get_instructions_CaSeDi_QLabel())
|
|
|
|
instructions.setStyleSheet("QLabel { background-color: #2A2A2A; padding: 10px; border-radius: 4px; }")
|
|
instructions.setWordWrap(True)
|
|
layout.addWidget(instructions)
|
|
|
|
# Split view for cameras
|
|
splitter = QSplitter(Qt.Horizontal)
|
|
splitter.setChildrenCollapsible(False)
|
|
splitter.setHandleWidth(6)
|
|
|
|
# Left side - Available Cameras
|
|
left_widget = QWidget()
|
|
left_layout = QVBoxLayout(left_widget)
|
|
left_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
|
|
# Local Cameras Group
|
|
local_group = QGroupBox("Local Cameras")
|
|
local_group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
local_layout = QVBoxLayout()
|
|
self.local_list = QListWidget()
|
|
self.local_list.setSelectionMode(QListWidget.ExtendedSelection)
|
|
self.local_list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
local_layout.addWidget(self.local_list)
|
|
local_group.setLayout(local_layout)
|
|
left_layout.addWidget(local_group)
|
|
|
|
# Network Cameras Group
|
|
network_group = QGroupBox("Network Cameras")
|
|
network_group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
network_layout = QVBoxLayout()
|
|
self.network_list = QListWidget()
|
|
self.network_list.setSelectionMode(QListWidget.ExtendedSelection)
|
|
self.network_list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
network_layout.addWidget(self.network_list)
|
|
network_group.setLayout(network_layout)
|
|
left_layout.addWidget(network_group)
|
|
|
|
# Camera management buttons
|
|
btn_layout = QHBoxLayout()
|
|
self.refresh_btn = QPushButton("Refresh")
|
|
self.refresh_btn.clicked.connect(self.refresh_cameras)
|
|
add_net_btn = QPushButton("Add Network Camera")
|
|
add_net_btn.clicked.connect(self.show_network_dialog)
|
|
|
|
btn_layout.addWidget(self.refresh_btn)
|
|
btn_layout.addWidget(add_net_btn)
|
|
left_layout.addLayout(btn_layout)
|
|
|
|
# Make lists expand and buttons stay minimal in left pane
|
|
left_layout.setStretch(0, 1)
|
|
left_layout.setStretch(1, 1)
|
|
left_layout.setStretch(2, 0)
|
|
|
|
splitter.addWidget(left_widget)
|
|
splitter.setStretchFactor(0, 1)
|
|
|
|
# Right side - Selected Cameras Preview
|
|
right_widget = QWidget()
|
|
right_layout = QVBoxLayout(right_widget)
|
|
right_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
|
|
preview_label = QLabel("Selected Cameras Preview")
|
|
preview_label.setStyleSheet("font-weight: bold;")
|
|
right_layout.addWidget(preview_label)
|
|
|
|
self.preview_list = QListWidget()
|
|
self.preview_list.setDragDropMode(QListWidget.InternalMove)
|
|
self.preview_list.setSelectionMode(QListWidget.ExtendedSelection)
|
|
self.preview_list.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
|
right_layout.addWidget(self.preview_list)
|
|
|
|
# Preview controls
|
|
preview_btn_layout = QHBoxLayout()
|
|
remove_btn = QPushButton("Remove Selected")
|
|
remove_btn.clicked.connect(self.remove_selected)
|
|
clear_btn = QPushButton("Clear All")
|
|
clear_btn.clicked.connect(self.clear_selection)
|
|
|
|
preview_btn_layout.addWidget(remove_btn)
|
|
preview_btn_layout.addWidget(clear_btn)
|
|
right_layout.addLayout(preview_btn_layout)
|
|
|
|
# Make preview list expand and buttons stay minimal in right pane
|
|
right_layout.setStretch(0, 0)
|
|
right_layout.setStretch(1, 1)
|
|
right_layout.setStretch(2, 0)
|
|
|
|
splitter.addWidget(right_widget)
|
|
splitter.setStretchFactor(1, 1)
|
|
layout.addWidget(splitter)
|
|
|
|
# Bottom buttons
|
|
bottom_layout = QHBoxLayout()
|
|
select_all_btn = QPushButton("Select All")
|
|
select_all_btn.clicked.connect(self.select_all)
|
|
ok_btn = QPushButton("OK")
|
|
ok_btn.clicked.connect(self.accept)
|
|
cancel_btn = QPushButton("Cancel")
|
|
cancel_btn.clicked.connect(self.reject)
|
|
|
|
bottom_layout.addWidget(select_all_btn)
|
|
bottom_layout.addStretch()
|
|
bottom_layout.addWidget(ok_btn)
|
|
bottom_layout.addWidget(cancel_btn)
|
|
layout.addLayout(bottom_layout)
|
|
|
|
# Connect signals
|
|
self.local_list.itemChanged.connect(self.update_preview)
|
|
self.network_list.itemChanged.connect(self.update_preview)
|
|
self.preview_list.model().rowsMoved.connect(self.update_camera_order)
|
|
|
|
# Set splitter sizes
|
|
splitter.setSizes([450, 450])
|
|
|
|
# Initial camera refresh
|
|
self.refresh_cameras()
|
|
|
|
# Restore last selection if available
|
|
if self.detector:
|
|
last_selected = self.detector.config.load_setting('last_selected_cameras', [])
|
|
if last_selected:
|
|
self.restore_selection(last_selected)
|
|
|
|
def refresh_cameras(self):
|
|
"""Refresh both local and network camera lists asynchronously"""
|
|
self.local_list.clear()
|
|
self.network_list.clear()
|
|
|
|
if not self.detector:
|
|
return
|
|
|
|
# Show placeholders and disable refresh while scanning
|
|
self.refresh_btn.setEnabled(False)
|
|
scanning_item_local = QListWidgetItem("Scanning for cameras…")
|
|
scanning_item_local.setFlags(Qt.NoItemFlags)
|
|
self.local_list.addItem(scanning_item_local)
|
|
scanning_item_net = QListWidgetItem("Loading network cameras…")
|
|
scanning_item_net.setFlags(Qt.NoItemFlags)
|
|
self.network_list.addItem(scanning_item_net)
|
|
|
|
# Start background scan
|
|
started = self.detector.start_camera_scan(10)
|
|
if not started:
|
|
# If a scan is already running, we'll just wait for its signal
|
|
pass
|
|
|
|
# Connect once to update lists when scan completes
|
|
try:
|
|
self.detector.cameras_scanned.disconnect(self._on_scan_finished_dialog)
|
|
except Exception:
|
|
pass
|
|
self.detector.cameras_scanned.connect(self._on_scan_finished_dialog)
|
|
|
|
def _on_scan_finished_dialog(self, cams, names):
|
|
# Re-enable refresh
|
|
self.refresh_btn.setEnabled(True)
|
|
# Rebuild lists
|
|
self.local_list.clear()
|
|
self.network_list.clear()
|
|
|
|
# Local cameras
|
|
for cam_path in cams:
|
|
if cam_path.startswith('net:'):
|
|
continue
|
|
if cam_path.startswith('/dev/'):
|
|
display = os.path.basename(cam_path)
|
|
else:
|
|
# Numeric index
|
|
pretty = names.get(cam_path)
|
|
display = f"{pretty} (#{cam_path})" if pretty else f"Camera {cam_path}"
|
|
item = QListWidgetItem(display)
|
|
item.setData(Qt.UserRole, cam_path)
|
|
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
|
item.setCheckState(Qt.Unchecked)
|
|
self.local_list.addItem(item)
|
|
|
|
# Network cameras
|
|
for name, camera_info in self.detector.network_cameras.items():
|
|
if isinstance(camera_info, dict):
|
|
url = camera_info.get('url', '')
|
|
has_auth = camera_info.get('username') is not None
|
|
display_text = f"{name} ({url})"
|
|
if has_auth:
|
|
display_text += " 🔒"
|
|
else:
|
|
display_text = f"{name} ({camera_info})"
|
|
item = QListWidgetItem(display_text)
|
|
item.setData(Qt.UserRole, f"net:{name}")
|
|
item.setFlags(item.flags() | Qt.ItemIsUserCheckable)
|
|
item.setCheckState(Qt.Unchecked)
|
|
self.network_list.addItem(item)
|
|
|
|
def restore_selection(self, last_selected):
|
|
"""Restore previous camera selection"""
|
|
for cam_id in last_selected:
|
|
# Check local cameras
|
|
for i in range(self.local_list.count()):
|
|
item = self.local_list.item(i)
|
|
if item.data(Qt.UserRole) == cam_id:
|
|
item.setCheckState(Qt.Checked)
|
|
|
|
# Check network cameras
|
|
for i in range(self.network_list.count()):
|
|
item = self.network_list.item(i)
|
|
if item.data(Qt.UserRole) == cam_id:
|
|
item.setCheckState(Qt.Checked)
|
|
|
|
def update_preview(self):
|
|
"""Update the preview list with currently selected cameras"""
|
|
self.preview_list.clear()
|
|
self.selected_cameras = []
|
|
|
|
# Get selected local cameras
|
|
for i in range(self.local_list.count()):
|
|
item = self.local_list.item(i)
|
|
if item.checkState() == Qt.Checked:
|
|
cam_id = item.data(Qt.UserRole)
|
|
preview_item = QListWidgetItem(f"Local: {item.text()}")
|
|
preview_item.setData(Qt.UserRole, cam_id)
|
|
self.preview_list.addItem(preview_item)
|
|
self.selected_cameras.append(cam_id)
|
|
|
|
# Get selected network cameras
|
|
for i in range(self.network_list.count()):
|
|
item = self.network_list.item(i)
|
|
if item.checkState() == Qt.Checked:
|
|
cam_id = item.data(Qt.UserRole)
|
|
preview_item = QListWidgetItem(f"Network: {item.text()}")
|
|
preview_item.setData(Qt.UserRole, cam_id)
|
|
self.preview_list.addItem(preview_item)
|
|
self.selected_cameras.append(cam_id)
|
|
|
|
# Save the current selection to config
|
|
if self.detector:
|
|
self.detector.config.save_setting('last_selected_cameras', self.selected_cameras)
|
|
|
|
def update_camera_order(self):
|
|
"""Update the camera order based on preview list order"""
|
|
self.selected_cameras = []
|
|
for i in range(self.preview_list.count()):
|
|
item = self.preview_list.item(i)
|
|
self.selected_cameras.append(item.data(Qt.UserRole))
|
|
|
|
# Save the new order
|
|
if self.detector:
|
|
self.detector.config.save_setting('last_selected_cameras', self.selected_cameras)
|
|
|
|
def select_all(self):
|
|
"""Select all cameras in both lists"""
|
|
for i in range(self.local_list.count()):
|
|
self.local_list.item(i).setCheckState(Qt.Checked)
|
|
for i in range(self.network_list.count()):
|
|
self.network_list.item(i).setCheckState(Qt.Checked)
|
|
|
|
def clear_selection(self):
|
|
"""Clear all selections"""
|
|
for i in range(self.local_list.count()):
|
|
self.local_list.item(i).setCheckState(Qt.Unchecked)
|
|
for i in range(self.network_list.count()):
|
|
self.network_list.item(i).setCheckState(Qt.Unchecked)
|
|
|
|
def remove_selected(self):
|
|
"""Remove selected items from the preview list"""
|
|
selected_items = self.preview_list.selectedItems()
|
|
for item in selected_items:
|
|
cam_id = item.data(Qt.UserRole)
|
|
# Uncheck corresponding items in source lists
|
|
for i in range(self.local_list.count()):
|
|
if self.local_list.item(i).data(Qt.UserRole) == cam_id:
|
|
self.local_list.item(i).setCheckState(Qt.Unchecked)
|
|
for i in range(self.network_list.count()):
|
|
if self.network_list.item(i).data(Qt.UserRole) == cam_id:
|
|
self.network_list.item(i).setCheckState(Qt.Unchecked)
|
|
|
|
# Camera connection tests removed for performance reasons per user request.
|
|
def test_selected_cameras(self):
|
|
"""Deprecated: Camera tests are disabled to improve performance."""
|
|
QMessageBox.information(self, "Camera Tests Disabled",
|
|
"Camera connectivity tests have been removed to speed up the application.")
|
|
return
|
|
|
|
def show_network_dialog(self):
|
|
"""Show the network camera configuration dialog"""
|
|
dialog = NetworkCameraDialog(self)
|
|
if dialog.exec_() == QDialog.Accepted:
|
|
self.refresh_cameras()
|