@@ -3,7 +3,7 @@ |
| 3 | 3 | from PyQt6.QtCore import Qt, pyqtSignal, QTimer, QPropertyAnimation, QRect |
| 4 | 4 | from PyQt6.QtWidgets import ( |
| 5 | 5 | QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, |
| 6 | | - QWidget, QFrame, QGraphicsOpacityEffect, QSizePolicy |
| 6 | + QWidget, QFrame, QGraphicsOpacityEffect, QSizePolicy, QLineEdit |
| 7 | 7 | ) |
| 8 | 8 | from PyQt6.QtGui import QKeyEvent |
| 9 | 9 | import qtawesome as qta |
@@ -18,8 +18,7 @@ class FastConfirmDialog(QDialog): |
| 18 | 18 | self.setModal(True) |
| 19 | 19 | self.setWindowFlags( |
| 20 | 20 | Qt.WindowType.Dialog | |
| 21 | | - Qt.WindowType.FramelessWindowHint | |
| 22 | | - Qt.WindowType.WindowStaysOnTopHint |
| 21 | + Qt.WindowType.FramelessWindowHint |
| 23 | 22 | ) |
| 24 | 23 | |
| 25 | 24 | # Make it slightly transparent for modern look |
@@ -264,8 +263,7 @@ class FastMessageDialog(QDialog): |
| 264 | 263 | self.setModal(True) |
| 265 | 264 | self.setWindowFlags( |
| 266 | 265 | Qt.WindowType.Dialog | |
| 267 | | - Qt.WindowType.FramelessWindowHint | |
| 268 | | - Qt.WindowType.WindowStaysOnTopHint |
| 266 | + Qt.WindowType.FramelessWindowHint |
| 269 | 267 | ) |
| 270 | 268 | |
| 271 | 269 | self.setWindowOpacity(0.95) |
@@ -356,10 +354,158 @@ class FastMessageDialog(QDialog): |
| 356 | 354 | self.accept() |
| 357 | 355 | |
| 358 | 356 | |
| 357 | +class FastInputDialog(QDialog): |
| 358 | + """Fast input dialog for text entry.""" |
| 359 | + |
| 360 | + def __init__(self, title: str, label: str, placeholder: str = "", parent=None): |
| 361 | + super().__init__(parent) |
| 362 | + self.setWindowTitle(title) |
| 363 | + self.setModal(True) |
| 364 | + self.setWindowFlags( |
| 365 | + Qt.WindowType.Dialog | |
| 366 | + Qt.WindowType.FramelessWindowHint |
| 367 | + ) |
| 368 | + |
| 369 | + self.setWindowOpacity(0.95) |
| 370 | + self.input_text = "" |
| 371 | + |
| 372 | + self._setup_ui(title, label, placeholder) |
| 373 | + |
| 374 | + # Size and position |
| 375 | + self.setFixedSize(400, 180) |
| 376 | + if parent: |
| 377 | + parent_rect = parent.geometry() |
| 378 | + x = parent_rect.x() + (parent_rect.width() - self.width()) // 2 |
| 379 | + y = parent_rect.y() + (parent_rect.height() - self.height()) // 2 |
| 380 | + self.move(x, y) |
| 381 | + |
| 382 | + def _setup_ui(self, title: str, label: str, placeholder: str): |
| 383 | + """Set up the dialog UI.""" |
| 384 | + layout = QVBoxLayout(self) |
| 385 | + layout.setContentsMargins(0, 0, 0, 0) |
| 386 | + |
| 387 | + # Frame |
| 388 | + frame = QFrame() |
| 389 | + frame.setObjectName("inputFrame") |
| 390 | + frame.setStyleSheet(""" |
| 391 | + #inputFrame { |
| 392 | + background-color: #2b2b2b; |
| 393 | + border: 2px solid #555; |
| 394 | + border-radius: 8px; |
| 395 | + } |
| 396 | + """) |
| 397 | + |
| 398 | + frame_layout = QVBoxLayout(frame) |
| 399 | + frame_layout.setContentsMargins(20, 20, 20, 20) |
| 400 | + frame_layout.setSpacing(15) |
| 401 | + |
| 402 | + # Title |
| 403 | + title_label = QLabel(title) |
| 404 | + title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #fff;") |
| 405 | + frame_layout.addWidget(title_label) |
| 406 | + |
| 407 | + # Label |
| 408 | + label_widget = QLabel(label) |
| 409 | + label_widget.setStyleSheet("color: #ddd;") |
| 410 | + frame_layout.addWidget(label_widget) |
| 411 | + |
| 412 | + # Input field |
| 413 | + self.input_field = QLineEdit() |
| 414 | + self.input_field.setPlaceholderText(placeholder) |
| 415 | + self.input_field.setStyleSheet(""" |
| 416 | + QLineEdit { |
| 417 | + background-color: #3a3a3a; |
| 418 | + color: #fff; |
| 419 | + border: 1px solid #555; |
| 420 | + border-radius: 4px; |
| 421 | + padding: 8px; |
| 422 | + font-size: 14px; |
| 423 | + } |
| 424 | + QLineEdit:focus { |
| 425 | + border-color: #0d7377; |
| 426 | + } |
| 427 | + """) |
| 428 | + self.input_field.returnPressed.connect(self.accept) |
| 429 | + frame_layout.addWidget(self.input_field) |
| 430 | + |
| 431 | + frame_layout.addStretch() |
| 432 | + |
| 433 | + # Buttons |
| 434 | + button_layout = QHBoxLayout() |
| 435 | + button_layout.addStretch() |
| 436 | + |
| 437 | + # OK button |
| 438 | + self.ok_btn = QPushButton("OK") |
| 439 | + self.ok_btn.setDefault(True) |
| 440 | + self.ok_btn.setStyleSheet(""" |
| 441 | + QPushButton { |
| 442 | + background-color: #0d7377; |
| 443 | + color: white; |
| 444 | + border: none; |
| 445 | + padding: 8px 20px; |
| 446 | + border-radius: 4px; |
| 447 | + font-weight: bold; |
| 448 | + min-width: 80px; |
| 449 | + } |
| 450 | + QPushButton:hover { |
| 451 | + background-color: #14b8bd; |
| 452 | + } |
| 453 | + QPushButton:pressed { |
| 454 | + background-color: #0a5d61; |
| 455 | + } |
| 456 | + """) |
| 457 | + self.ok_btn.clicked.connect(self.accept) |
| 458 | + button_layout.addWidget(self.ok_btn) |
| 459 | + |
| 460 | + # Cancel button |
| 461 | + self.cancel_btn = QPushButton("Cancel") |
| 462 | + self.cancel_btn.setStyleSheet(""" |
| 463 | + QPushButton { |
| 464 | + background-color: #555; |
| 465 | + color: white; |
| 466 | + border: none; |
| 467 | + padding: 8px 20px; |
| 468 | + border-radius: 4px; |
| 469 | + font-weight: bold; |
| 470 | + min-width: 80px; |
| 471 | + } |
| 472 | + QPushButton:hover { |
| 473 | + background-color: #666; |
| 474 | + } |
| 475 | + QPushButton:pressed { |
| 476 | + background-color: #444; |
| 477 | + } |
| 478 | + """) |
| 479 | + self.cancel_btn.clicked.connect(self.reject) |
| 480 | + button_layout.addWidget(self.cancel_btn) |
| 481 | + |
| 482 | + frame_layout.addLayout(button_layout) |
| 483 | + layout.addWidget(frame) |
| 484 | + |
| 485 | + # Focus on input field |
| 486 | + self.input_field.setFocus() |
| 487 | + |
| 488 | + def accept(self): |
| 489 | + """Accept the dialog and store the input.""" |
| 490 | + self.input_text = self.input_field.text() |
| 491 | + super().accept() |
| 492 | + |
| 493 | + def get_text(self) -> str: |
| 494 | + """Get the entered text.""" |
| 495 | + return self.input_text |
| 496 | + |
| 497 | + |
| 359 | 498 | def fast_confirm(parent, title: str, message: str) -> bool: |
| 360 | 499 | """Show fast confirmation dialog and return result.""" |
| 361 | 500 | dialog = FastConfirmDialog(title, message, parent) |
| 362 | | - return dialog.exec() == QDialog.DialogCode.Accepted |
| 501 | + result = dialog.exec() == QDialog.DialogCode.Accepted |
| 502 | + |
| 503 | + # Restore parent focus after dialog |
| 504 | + if parent and hasattr(parent, 'window'): |
| 505 | + parent.window().raise_() |
| 506 | + parent.window().activateWindow() |
| 507 | + |
| 508 | + return result |
| 363 | 509 | |
| 364 | 510 | |
| 365 | 511 | def fast_message(parent, title: str, message: str, message_type: str = "info"): |
@@ -373,4 +519,22 @@ def fast_message(parent, title: str, message: str, message_type: str = "info"): |
| 373 | 519 | |
| 374 | 520 | icon_name, icon_color = icon_configs.get(message_type, icon_configs["info"]) |
| 375 | 521 | dialog = FastMessageDialog(title, message, icon_name, icon_color, parent) |
| 376 | | - dialog.exec() |
| 522 | + dialog.exec() |
| 523 | + |
| 524 | + # Restore parent focus after dialog |
| 525 | + if parent and hasattr(parent, 'window'): |
| 526 | + parent.window().raise_() |
| 527 | + parent.window().activateWindow() |
| 528 | + |
| 529 | + |
| 530 | +def fast_input(parent, title: str, label: str, placeholder: str = "") -> tuple[str, bool]: |
| 531 | + """Show fast input dialog and return (text, ok).""" |
| 532 | + dialog = FastInputDialog(title, label, placeholder, parent) |
| 533 | + ok = dialog.exec() == QDialog.DialogCode.Accepted |
| 534 | + |
| 535 | + # Restore parent focus after dialog |
| 536 | + if parent and hasattr(parent, 'window'): |
| 537 | + parent.window().raise_() |
| 538 | + parent.window().activateWindow() |
| 539 | + |
| 540 | + return dialog.get_text(), ok |