from PyQt6.QtCore import Qt, QTimer, pyqtSignal from PyQt6.QtGui import QFont, QGuiApplication from PyQt6.QtWidgets import ( QDialog, QFrame, QHBoxLayout, QLabel, QPushButton, QScrollArea, QVBoxLayout, QWidget, ) from gui.v2.ui.styles.styles import ( POPUP_BG_QSS, POPUP_CANCEL_BUTTON_QSS, ) class OperationResultPopup(QDialog): action_selected = pyqtSignal(str) def __init__(self, parent=None, title="", message="", actions=None, severity="error"): super().__init__(parent) self.parent_window = parent self.title_text = title or "Operation Failed" self.message = message or "The operation could not be completed." self.actions = actions or [{'key': 'dismiss', 'label': 'OK', 'role': 'primary'}] self.severity = severity self._completed = False self._use_parent_local_geometry = QGuiApplication.platformName().lower().startswith("wayland") self.initUI() def initUI(self): self.setFixedSize(560, 300) if self._use_parent_local_geometry: if self.parent_window is not None: self.setParent(self.parent_window) self.setWindowFlags(Qt.WindowType.Widget) else: self.setWindowFlags(Qt.WindowType.Dialog | Qt.WindowType.FramelessWindowHint) self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground) outer = QVBoxLayout(self) outer.setContentsMargins(0, 0, 0, 0) bg_widget = QWidget(self) bg_widget.setStyleSheet(POPUP_BG_QSS) outer.addWidget(bg_widget) content_layout = QVBoxLayout(bg_widget) content_layout.setContentsMargins(22, 14, 22, 18) content_layout.setSpacing(12) top_row = QHBoxLayout() top_row.setContentsMargins(0, 0, 0, 0) top_row.setSpacing(10) title_label = QLabel(self.title_text) title_label.setFont(QFont("Arial", 18, QFont.Weight.Bold)) title_label.setStyleSheet(f"color: {self._title_color()}; background: transparent; border: none;") title_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignVCenter) title_label.setMinimumWidth(0) top_row.addWidget(title_label, 1) close_button = QPushButton("X") close_button.setFixedSize(36, 36) close_button.setFont(QFont("Arial", 14, QFont.Weight.Bold)) close_button.setStyleSheet(""" QPushButton { background-color: transparent; color: #444444; font-size: 18px; font-weight: 900; border: none; } QPushButton:hover { color: #d62828; } """) close_button.clicked.connect(lambda: self._choose('cancel')) top_row.addWidget(close_button, 0, Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignTop) content_layout.addLayout(top_row) scroll_area = QScrollArea() scroll_area.setWidgetResizable(True) scroll_area.setFrameShape(QFrame.Shape.NoFrame) scroll_area.setStyleSheet("background: transparent; border: none;") content_layout.addWidget(scroll_area, 1) scroll_content = QWidget() scroll_content.setStyleSheet("background: transparent; border: none;") scroll_layout = QVBoxLayout(scroll_content) scroll_layout.setContentsMargins(0, 0, 0, 0) message_label = QLabel(self.message) message_label.setFont(QFont("Arial", 12)) message_label.setStyleSheet("color: #333333; background: transparent; border: none;") message_label.setWordWrap(True) message_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse) message_label.setAlignment(Qt.AlignmentFlag.AlignLeft | Qt.AlignmentFlag.AlignTop) scroll_layout.addWidget(message_label) scroll_layout.addStretch() scroll_area.setWidget(scroll_content) button_row = QHBoxLayout() button_row.addStretch() for action in self.actions: button = QPushButton(action.get('label', 'OK')) button.setFixedSize(max(110, min(180, 22 + len(action.get('label', 'OK')) * 8)), 42) button.setFont(QFont("Arial", 11, QFont.Weight.Bold)) button.setCursor(Qt.CursorShape.PointingHandCursor) button.setStyleSheet(self._button_style(action.get('role', 'primary'))) button.clicked.connect(lambda checked=False, key=action.get('key', 'dismiss'): self._choose(key)) button_row.addWidget(button) content_layout.addLayout(button_row) if not self._use_parent_local_geometry: self.setWindowModality(Qt.WindowModality.ApplicationModal) def _title_color(self): if self.severity == 'warning': return '#d97706' return '#d62828' def _button_style(self, role): if role == 'secondary': return POPUP_CANCEL_BUTTON_QSS if self.severity == 'warning': return """ QPushButton { background-color: #f59e0b; border: none; color: white; border-radius: 5px; font-weight: bold; } QPushButton:hover { background-color: #d97706; } """ return """ QPushButton { background-color: #d62828; border: none; color: white; border-radius: 5px; font-weight: bold; } QPushButton:hover { background-color: #b91c1c; } """ def _choose(self, action): if self._completed: return self._completed = True self.action_selected.emit(action) self.accept() def closeEvent(self, event): if not self._completed: self._completed = True self.action_selected.emit('cancel') event.accept() def show(self): self._center_on_parent() super().show() def showEvent(self, event): super().showEvent(event) self._center_on_parent() QTimer.singleShot(0, self._center_on_parent) def _center_on_parent(self): parent = self.parent_window if parent is None: return try: if not hasattr(parent, 'mapToGlobal') or not hasattr(parent, 'rect'): return parent_local_rect = parent.rect() if parent_local_rect.isEmpty(): return my_size = self.size() if my_size.isEmpty() or my_size.width() <= 0 or my_size.height() <= 0: my_size = self.sizeHint() if self._use_parent_local_geometry: target_x = parent_local_rect.x() + (parent_local_rect.width() - my_size.width()) // 2 target_y = parent_local_rect.y() + (parent_local_rect.height() - my_size.height()) // 2 target_x = max(parent_local_rect.left(), min(target_x, parent_local_rect.right() - my_size.width() + 1)) target_y = max(parent_local_rect.top(), min(target_y, parent_local_rect.bottom() - my_size.height() + 1)) else: parent_center = parent.mapToGlobal(parent_local_rect.center()) target_x = parent_center.x() - my_size.width() // 2 target_y = parent_center.y() - my_size.height() // 2 screen = None if hasattr(parent, 'screen'): try: screen = parent.screen() except Exception: screen = None if screen is not None: avail = screen.availableGeometry() target_x = max(avail.left(), min(target_x, avail.right() - my_size.width())) target_y = max(avail.top(), min(target_y, avail.bottom() - my_size.height())) self.setGeometry(target_x, target_y, my_size.width(), my_size.height()) if self._use_parent_local_geometry: self.raise_() self.setFocus(Qt.FocusReason.PopupFocusReason) except Exception: pass def mousePressEvent(self, event): self.oldPos = event.globalPosition().toPoint() def mouseMoveEvent(self, event): delta = event.globalPosition().toPoint() - self.oldPos self.move(self.x() + delta.x(), self.y() + delta.y()) self.oldPos = event.globalPosition().toPoint()