#include "lockwidget.h" #include "mainwindow.h" #include #include #include class LockInterface { public: LockInterface(QWidget* mainUI) : m_mainUI(mainUI), m_target(nullptr), m_message(nullptr), m_info(nullptr), m_buttons(nullptr), m_reason(LockWidget::NoReason) { m_timer.reset(new QTimer); QObject::connect(m_timer.get(), &QTimer::timeout, [&]{ checkTarget(); }); m_timer->start(200); set(); } ~LockInterface() { } void checkTarget() { if (set()) { update(m_reason); } } bool set() { QWidget* newTarget = nullptr; newTarget = m_mainUI; if (auto* w = QApplication::activeModalWidget()) { newTarget = w; } if (newTarget == m_target) { return false; } m_target = newTarget; QFrame* center = nullptr; if (m_target) { center = createOverlay(m_target); } else { center = createDialog(); } createMessageLabel(); createInfoLabel(); createButtonsPanel(); center->layout()->addWidget(m_message); center->layout()->addWidget(m_info); center->layout()->addWidget(m_buttons); m_topLevel->setFocusPolicy(Qt::TabFocus); m_topLevel->setFocus(); m_topLevel->show(); m_topLevel->setEnabled(true); m_topLevel->raise(); m_topLevel->activateWindow(); return true; } void update(LockWidget::Reasons reason) { m_reason = reason; updateMessage(reason); updateButtons(reason); setInfo(m_infoText); } void setInfo(const QString& s) { m_infoText = s; m_info->setText(s); } QWidget* topLevel() { return m_topLevel.get(); } private: class Filter : public QObject { public: std::function resized; std::function closed; protected: bool eventFilter(QObject* o, QEvent* e) override { if (e->type() == QEvent::Resize) { if (resized) { resized(); } } else if (e->type() == QEvent::Close) { if (closed) { closed(); } } return QObject::eventFilter(o, e); } }; std::unique_ptr m_timer; QWidget* m_mainUI; QWidget* m_target; std::unique_ptr m_topLevel; QLabel* m_message; QLabel* m_info; QString m_infoText; QWidget* m_buttons; std::unique_ptr m_filter; LockWidget::Reasons m_reason; bool hasMainUI() const { return (m_target != nullptr); } QWidget* createTransparentWidget(QWidget* parent=nullptr) { auto* w = new QWidget(parent); w->setWindowOpacity(0); w->setAttribute(Qt::WA_NoSystemBackground); w->setAttribute(Qt::WA_TranslucentBackground); return w; } QFrame* createOverlay(QWidget* mainUI) { m_topLevel.reset(createTransparentWidget(mainUI)); m_topLevel->setWindowFlags(m_topLevel->windowFlags() & Qt::FramelessWindowHint); m_topLevel->setGeometry(mainUI->rect()); m_filter.reset(new Filter); m_filter->resized = [=]{ m_topLevel->setGeometry(mainUI->rect()); }; m_filter->closed = [=]{ checkTarget(); }; mainUI->installEventFilter(m_filter.get()); return createFrame(); } QFrame* createDialog() { m_topLevel.reset(new QDialog); return createFrame(); } QFrame* createFrame() { auto* frame = new QFrame; auto* ly = new QVBoxLayout(frame); if (hasMainUI()) { frame->setFrameStyle(QFrame::StyledPanel); frame->setLineWidth(1); frame->setAutoFillBackground(true); auto* shadow = new QGraphicsDropShadowEffect; shadow->setBlurRadius(50); shadow->setOffset(0); shadow->setColor(QColor(0, 0, 0, 100)); frame->setGraphicsEffect(shadow); } else { ly->setContentsMargins(0, 0, 0, 0); } auto* grid = new QGridLayout(m_topLevel.get()); grid->addWidget(createTransparentWidget(), 0, 1); grid->addWidget(createTransparentWidget(), 2, 1); grid->addWidget(createTransparentWidget(), 1, 0); grid->addWidget(createTransparentWidget(), 1, 2); grid->addWidget(frame, 1, 1); if (!hasMainUI()) { grid->setContentsMargins(0, 0, 0, 0); } grid->setRowStretch(0, 1); grid->setRowStretch(2, 1); grid->setColumnStretch(0, 1); grid->setColumnStretch(2, 1); return frame; } void createMessageLabel() { m_message = new QLabel; } void createInfoLabel() { m_info = new QLabel(" "); m_info->setAlignment(Qt::AlignCenter | Qt::AlignHCenter); } void createButtonsPanel() { m_buttons = new QWidget; m_buttons->setLayout(new QHBoxLayout); } void updateMessage(LockWidget::Reasons reason) { switch (reason) { case LockWidget::LockUI: { QString s; if (hasMainUI()) { s = QObject::tr( "Mod Organizer is locked while the application is running."); } else { s = QObject::tr("Mod Organizer is currently running an application."); } m_message->setText(s); break; } case LockWidget::OutputRequired: { m_message->setText(QObject::tr( "The application must run to completion because its output is " "required.")); break; } case LockWidget::PreventExit: { m_message->setText(QObject::tr( "Mod Organizer is waiting on application to close before exiting.")); break; } } } void updateButtons(LockWidget::Reasons reason) { MOBase::deleteChildWidgets(m_buttons); auto* ly = m_buttons->layout(); switch (reason) { case LockWidget::LockUI: // fall-through case LockWidget::OutputRequired: { auto* unlock = new QPushButton(QObject::tr("Unlock")); QObject::connect(unlock, &QPushButton::clicked, [&]{ LockWidget::instance().onForceUnlock(); }); ly->addWidget(unlock); break; } case LockWidget::PreventExit: { auto* exit = new QPushButton(QObject::tr("Exit Now")); QObject::connect(exit, &QPushButton::clicked, [&]{ LockWidget::instance().onForceUnlock(); }); ly->addWidget(exit); auto* cancel = new QPushButton(QObject::tr("Cancel")); QObject::connect(cancel, &QPushButton::clicked, [&]{ LockWidget::instance().onCancel(); }); ly->addWidget(cancel); break; } } } }; LockWidget::Session::~Session() { LockWidget::instance().unlock(this); } void LockWidget::Session::setInfo(DWORD pid, const QString& name) { m_pid = pid; m_name = name; LockWidget::instance().updateLabel(); } DWORD LockWidget::Session::pid() const { return m_pid; } const QString& LockWidget::Session::name() const { return m_name; } LockWidget::Results LockWidget::Session::result() const { return LockWidget::instance().result(); } static LockWidget* g_instance = nullptr; LockWidget::LockWidget() : m_parent(nullptr), m_result(NoResult) { Q_ASSERT(!g_instance); g_instance = this; } LockWidget::~LockWidget() { const auto v = m_sessions; for (auto& wp : v) { if (auto s=wp.lock()) { unlock(s.get()); } } } LockWidget& LockWidget::instance() { Q_ASSERT(g_instance); return *g_instance; } void LockWidget::setUserInterface(QWidget* parent) { m_parent = parent; } std::shared_ptr LockWidget::lock(Reasons reason) { m_result = StillLocked; createUi(reason); auto ls = std::make_shared(); m_sessions.push_back(ls); updateLabel(); return ls; } void LockWidget::unlock(Session* s) { auto itor = m_sessions.begin(); for (;;) { if (itor == m_sessions.end()) { break; } if (auto ss=itor->lock()) { if (ss.get() == s) { itor = m_sessions.erase(itor); continue; } } else { itor = m_sessions.erase(itor); continue; } ++itor; } if (m_sessions.empty()) { m_ui.reset(); enableAll(); } else { updateLabel(); } } void LockWidget::unlockCurrent() { if (m_sessions.empty()) { return; } auto s = m_sessions.back().lock(); if (!s) { m_sessions.pop_back(); return; } unlock(s.get()); } void LockWidget::updateLabel() { QString label; for (auto itor=m_sessions.rbegin(); itor!=m_sessions.rend(); ++itor) { if (auto ss=itor->lock()) { label += QString("%1 (%2)").arg(ss->name()).arg(ss->pid()); break; } } m_ui->setInfo(label); } LockWidget::Results LockWidget::result() const { return m_result; } void LockWidget::createUi(Reasons reason) { if (!m_ui) { m_ui.reset(new LockInterface(m_parent)); } m_ui->update(reason); disableAll(); } void LockWidget::onForceUnlock() { m_result = ForceUnlocked; unlockCurrent(); } void LockWidget::onCancel() { m_result = Cancelled; unlockCurrent(); } template QList findChildrenImmediate(QWidget* parent) { return parent->findChildren(QString(), Qt::FindDirectChildrenOnly); } void LockWidget::disableAll() { const auto topLevels = QApplication::topLevelWidgets(); for (auto* w : topLevels) { if (auto* mw=dynamic_cast(w)) { disable(mw->centralWidget()); disable(mw->menuBar()); disable(mw->statusBar()); for (auto* tb : findChildrenImmediate(w)) { disable(tb); } for (auto* d : findChildrenImmediate(w)) { disable(d); } } if (auto* d=dynamic_cast(w)) { // don't disable stuff if this dialog is the overlay, which happens when // there's no ui if (d != m_ui->topLevel()) { // no central widget, just disable the children, except for the overlay for (auto* child : findChildrenImmediate(d)) { if (child != m_ui->topLevel()) { disable(child); } } } } } } void LockWidget::enableAll() { for (auto w : m_disabled) { if (w) { w->setEnabled(true); } } m_disabled.clear(); } void LockWidget::disable(QWidget* w) { if (w->isEnabled()) { w->setEnabled(false); m_disabled.push_back(w); } }