hydraveil-gui/gui/v2/ui/popups/operation_result_popup.py
2026-08-28 11:12:57 -04:00

223 lines
8.5 KiB
Python
Executable file

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()