|
6 | 6 | #include <QComboBox> |
7 | 7 | #include <QCursor> |
8 | 8 | #include <QEvent> |
| 9 | +#include <QGuiApplication> |
9 | 10 | #include <QHBoxLayout> |
10 | 11 | #include <QKeyEvent> |
11 | 12 | #include <QLabel> |
@@ -49,6 +50,9 @@ UnitedEntry::UnitedEntry(ServiceLocator &p_services, UnitedEntryMgr *p_mgr, QWid |
49 | 50 |
|
50 | 51 | connect(qApp, &QApplication::focusChanged, this, &UnitedEntry::handleFocusChanged); |
51 | 52 |
|
| 53 | + connect(qApp, &QGuiApplication::applicationStateChanged, this, |
| 54 | + &UnitedEntry::handleAppStateChanged); |
| 55 | + |
52 | 56 | connect(m_mgr, &UnitedEntryMgr::entryFinished, this, &UnitedEntry::handleEntryFinished); |
53 | 57 | connect(m_mgr, &UnitedEntryMgr::entryItemActivated, this, &UnitedEntry::handleEntryItemActivated); |
54 | 58 |
|
@@ -180,6 +184,7 @@ void UnitedEntry::deactivate() { |
180 | 184 |
|
181 | 185 | m_activated = false; |
182 | 186 | m_previousFocusWidget = nullptr; |
| 187 | + m_popupNeedsRestore = false; |
183 | 188 |
|
184 | 189 | m_popup->hide(); |
185 | 190 | m_comboBox->lineEdit()->clearFocus(); |
@@ -495,6 +500,11 @@ bool UnitedEntry::eventFilter(QObject *p_watched, QEvent *p_event) { |
495 | 500 | if (handleLineEditKeyPress(eve)) { |
496 | 501 | return true; |
497 | 502 | } |
| 503 | + } else if (p_event->type() == QEvent::MouseButtonPress) { |
| 504 | + // Returning by clicking the input keeps focus unchanged, so focusChanged |
| 505 | + // never fires; re-show the popup if we are still activated. Do NOT consume |
| 506 | + // the event, or the click won't position the text cursor. |
| 507 | + scheduleRestorePopup(); |
498 | 508 | } |
499 | 509 | } |
500 | 510 |
|
@@ -572,6 +582,70 @@ void UnitedEntry::handleFocusChanged(QWidget *p_old, QWidget *p_now) { |
572 | 582 | } |
573 | 583 | } |
574 | 584 |
|
| 585 | +void UnitedEntry::handleAppStateChanged(Qt::ApplicationState p_state) { |
| 586 | + if (p_state != Qt::ApplicationActive) { |
| 587 | + // The app is deactivating; Windows hides the parentless Qt::Tool popup at |
| 588 | + // the native level. Remember to re-map it when we become active again. |
| 589 | + if (m_activated) { |
| 590 | + m_popupNeedsRestore = true; |
| 591 | + } |
| 592 | + return; |
| 593 | + } |
| 594 | + if (!m_activated) { |
| 595 | + return; |
| 596 | + } |
| 597 | + scheduleRestorePopup(); |
| 598 | +} |
| 599 | + |
| 600 | +void UnitedEntry::scheduleRestorePopup() { |
| 601 | + if (!m_activated || !m_popupNeedsRestore || m_restorePending) { |
| 602 | + return; |
| 603 | + } |
| 604 | + m_restorePending = true; |
| 605 | + // Defer so focus restoration / the activating click settle first. |
| 606 | + QTimer::singleShot(0, this, [this]() { |
| 607 | + m_restorePending = false; |
| 608 | + restorePopupVisibility(); |
| 609 | + }); |
| 610 | +} |
| 611 | + |
| 612 | +void UnitedEntry::restorePopupVisibility() { |
| 613 | + if (!m_activated) { |
| 614 | + return; |
| 615 | + } |
| 616 | + |
| 617 | + // Only restore while the input still holds focus; otherwise the normal |
| 618 | + // deactivate flow in handleFocusChanged should win. Return without clearing |
| 619 | + // m_popupNeedsRestore so a later trigger can retry once focus settles. |
| 620 | + auto *fw = QApplication::focusWidget(); |
| 621 | + if (fw != m_comboBox && fw != m_comboBox->lineEdit()) { |
| 622 | + return; |
| 623 | + } |
| 624 | + |
| 625 | + // Suppress when the native combo dropdown is showing history items. |
| 626 | + if (m_comboBox->view()->isVisible() && m_comboBox->count() > 0) { |
| 627 | + return; |
| 628 | + } |
| 629 | + |
| 630 | + m_popupNeedsRestore = false; |
| 631 | + |
| 632 | + if (m_popup->hasWidget()) { |
| 633 | + // Re-show existing content WITHOUT reprocessing, so an ongoing async entry |
| 634 | + // (e.g. find) is not cancelled. Force a native re-map: Windows hides |
| 635 | + // Qt::Tool windows at the native level without clearing the widget's |
| 636 | + // visible state, so a plain show() can be a no-op. |
| 637 | + updatePopupGeometry(); |
| 638 | + m_popup->hide(); |
| 639 | + m_popup->show(); |
| 640 | + m_popup->raise(); |
| 641 | + } else { |
| 642 | + // No content yet (native dropdown suppressed the initial processInput); no |
| 643 | + // entry can be ongoing here, so building content is safe. |
| 644 | + m_processTimer->stop(); |
| 645 | + processInput(); |
| 646 | + } |
| 647 | +} |
| 648 | + |
575 | 649 | void UnitedEntry::refreshIcons() { |
576 | 650 | auto *themeService = m_services.get<ThemeService>(); |
577 | 651 | const auto fg = themeService->paletteColor("widgets#unitedentry#icon#fg"); |
|
0 commit comments