From 0586b2856b0b8973e5781b9b0a1b5e5acac20532 Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Wed, 13 Dec 2017 09:49:09 +0200 Subject: Locked dialog fixes (fix mainwindow modalness; use normal window to fix parentless scenario) --- src/mainwindow.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src/mainwindow.cpp') diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 632b64e9..1e1a4558 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1471,7 +1471,8 @@ ILockedWaitingForProcess* MainWindow::lock() ++m_LockCount; return m_LockDialog; } - m_LockDialog = new LockedDialog(qApp->activeWindow()); + m_LockDialog = new LockedDialog(this, true); + m_LockDialog->setModal(true); m_LockDialog->show(); setEnabled(false); m_LockDialog->setEnabled(true); //What's the point otherwise? -- cgit v1.3.1 From a5bb70f53bc20f36f695ac5cd9f8b91163f694eb Mon Sep 17 00:00:00 2001 From: Eran Mizrahi Date: Wed, 13 Dec 2017 16:18:54 +0200 Subject: Wait for injected processes on MO close --- src/CMakeLists.txt | 5 ++ src/ilockedwaitingforprocess.h | 2 +- src/lockeddialog.cpp | 37 ++----------- src/lockeddialog.h | 33 ++---------- src/lockeddialogbase.cpp | 73 +++++++++++++++++++++++++ src/lockeddialogbase.h | 62 +++++++++++++++++++++ src/mainwindow.cpp | 20 ++++++- src/mainwindow.h | 6 ++- src/organizercore.cpp | 45 +++++++++------- src/organizercore.h | 1 + src/waitingonclosedialog.cpp | 71 ++++++++++++++++++++++++ src/waitingonclosedialog.h | 56 +++++++++++++++++++ src/waitingonclosedialog.ui | 119 +++++++++++++++++++++++++++++++++++++++++ 13 files changed, 443 insertions(+), 87 deletions(-) create mode 100644 src/lockeddialogbase.cpp create mode 100644 src/lockeddialogbase.h create mode 100644 src/waitingonclosedialog.cpp create mode 100644 src/waitingonclosedialog.h create mode 100644 src/waitingonclosedialog.ui (limited to 'src/mainwindow.cpp') diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 39aabb91..914bdd12 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -42,7 +42,9 @@ SET(organizer_SRCS main.cpp loghighlighter.cpp logbuffer.cpp + lockeddialogbase.cpp lockeddialog.cpp + waitingonclosedialog.cpp loadmechanism.cpp installationmanager.cpp helper.cpp @@ -128,7 +130,9 @@ SET(organizer_HDRS mainwindow.h loghighlighter.h logbuffer.h + lockeddialogbase.h lockeddialog.h + waitingonclosedialog.h loadmechanism.h installationmanager.h helper.h @@ -200,6 +204,7 @@ SET(organizer_UIS messagedialog.ui mainwindow.ui lockeddialog.ui + waitingonclosedialog.ui installdialog.ui finddialog.ui editexecutablesdialog.ui diff --git a/src/ilockedwaitingforprocess.h b/src/ilockedwaitingforprocess.h index 5bf1f1ca..9475ddb9 100644 --- a/src/ilockedwaitingforprocess.h +++ b/src/ilockedwaitingforprocess.h @@ -6,7 +6,7 @@ class QString; class ILockedWaitingForProcess { public: - virtual bool unlockForced() = 0; + virtual bool unlockForced() const = 0; virtual void setProcessName(QString const &) = 0; }; diff --git a/src/lockeddialog.cpp b/src/lockeddialog.cpp index 09538a0f..143d5838 100644 --- a/src/lockeddialog.cpp +++ b/src/lockeddialog.cpp @@ -26,10 +26,8 @@ along with Mod Organizer. If not, see . #include // for Qt::FramelessWindowHint, etc LockedDialog::LockedDialog(QWidget *parent, bool unlockByButton) - : QDialog(parent) + : LockedDialogBase(parent, !unlockByButton) , ui(new Ui::LockedDialog) - , m_Unlocked(false) - , m_allowClose(!unlockByButton) { ui->setupUi(this); @@ -38,7 +36,7 @@ LockedDialog::LockedDialog(QWidget *parent, bool unlockByButton) // seem to work. We will ignore pressing the close button if unlockByButton == true Qt::WindowFlags flags = this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; - if (!unlockByButton) + if (m_allowClose) flags |= Qt::WindowCloseButtonHint; this->setWindowFlags(flags); @@ -48,13 +46,6 @@ LockedDialog::LockedDialog(QWidget *parent, bool unlockByButton) ui->verticalLayout->addItem( new QSpacerItem(20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding)); } - - if (parent != nullptr) { - QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); - position.rx() -= this->width() / 2; - position.ry() -= this->height() / 2; - move(position); - } } LockedDialog::~LockedDialog() @@ -68,35 +59,13 @@ void LockedDialog::setProcessName(const QString &name) ui->processLabel->setText(name); } - -void LockedDialog::resizeEvent(QResizeEvent *event) -{ - QWidget *par = parentWidget(); - if (par != nullptr) { - QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); - position.rx() -= event->size().width() / 2; - position.ry() -= event->size().height() / 2; - move(position); - } -} - void LockedDialog::on_unlockButton_clicked() { unlock(); } -void LockedDialog::reject() -{ - if (m_allowClose) - unlock(); -} - -bool LockedDialog::unlockForced() { - return m_Unlocked; -} - void LockedDialog::unlock() { - m_Unlocked = true; + LockedDialogBase::unlock(); ui->label->setText("unlocking may take a few seconds"); ui->unlockButton->setEnabled(false); } diff --git a/src/lockeddialog.h b/src/lockeddialog.h index 82a15a93..36c16429 100644 --- a/src/lockeddialog.h +++ b/src/lockeddialog.h @@ -17,16 +17,9 @@ You should have received a copy of the GNU General Public License along with Mod Organizer. If not, see . */ -#ifndef LOCKEDDIALOG_H -#define LOCKEDDIALOG_H +#pragma once -#include "ilockedwaitingforprocess.h" -#include // for QDialog -#include // for Q_OBJECT, slots -#include // for QString - -class QResizeEvent; -class QWidget; +#include "lockeddialogbase.h" namespace Ui { class LockedDialog; @@ -40,7 +33,7 @@ namespace Ui { * data on which Mod Organizer works. After the UI is unlocked (manually or after the * external application closed) MO will refresh all of its data sources **/ -class LockedDialog : public QDialog, public ILockedWaitingForProcess +class LockedDialog : public LockedDialogBase { Q_OBJECT @@ -48,35 +41,17 @@ public: explicit LockedDialog(QWidget *parent = 0, bool unlockByButton = false); ~LockedDialog(); - /** - * @brief see if the user clicked the unlock-button - * - * @return true if the user clicked the unlock button - **/ - bool unlockForced() override; - - /** - * @brief set the name of the process being run - * @param name of process - */ void setProcessName(const QString &name) override; protected: - virtual void resizeEvent(QResizeEvent *event); - - virtual void reject(); + void unlock() override; private slots: void on_unlockButton_clicked(); private: - void unlock(); Ui::LockedDialog *ui; - bool m_Unlocked; - bool m_allowClose; }; - -#endif // LOCKEDDIALOG_H diff --git a/src/lockeddialogbase.cpp b/src/lockeddialogbase.cpp new file mode 100644 index 00000000..b18f7429 --- /dev/null +++ b/src/lockeddialogbase.cpp @@ -0,0 +1,73 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "lockeddialogbase.h" + +#include +#include +#include +#include // for Qt::FramelessWindowHint, etc + +LockedDialogBase::LockedDialogBase(QWidget *parent, bool allowClose) + : QDialog(parent) + , m_Unlocked(false) + , m_Canceled(false) + , m_allowClose(allowClose) +{ + if (parent != nullptr) { + QPoint position = parent->mapToGlobal(QPoint(parent->width() / 2, parent->height() / 2)); + position.rx() -= this->width() / 2; + position.ry() -= this->height() / 2; + move(position); + } +} + +void LockedDialogBase::resizeEvent(QResizeEvent *event) +{ + QWidget *par = parentWidget(); + if (par != nullptr) { + QPoint position = par->mapToGlobal(QPoint(par->width() / 2, par->height() / 2)); + position.rx() -= event->size().width() / 2; + position.ry() -= event->size().height() / 2; + move(position); + } +} + +void LockedDialogBase::reject() +{ + if (m_allowClose) + unlock(); +} + +bool LockedDialogBase::unlockForced() const { + return m_Unlocked; +} + +bool LockedDialogBase::canceled() const { + return m_Canceled; +} + +void LockedDialogBase::unlock() { + m_Unlocked = true; +} + +void LockedDialogBase::cancel() { + m_Canceled = true; +} + diff --git a/src/lockeddialogbase.h b/src/lockeddialogbase.h new file mode 100644 index 00000000..3c974a38 --- /dev/null +++ b/src/lockeddialogbase.h @@ -0,0 +1,62 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#pragma once + +#include "ilockedwaitingforprocess.h" +#include // for QDialog +#include // for Q_OBJECT, slots +#include // for QString + +class QResizeEvent; +class QWidget; + +/** + * a small borderless dialog displayed while the Mod Organizer UI is locked + * The dialog contains only a label and a button to force the UI to be unlocked + * + * The UI gets locked while running external applications since they may modify the + * data on which Mod Organizer works. After the UI is unlocked (manually or after the + * external application closed) MO will refresh all of its data sources + **/ +class LockedDialogBase : public QDialog, public ILockedWaitingForProcess +{ + Q_OBJECT + +public: + explicit LockedDialogBase(QWidget *parent, bool allowClose); + + bool unlockForced() const override; + + virtual bool canceled() const; + +protected: + + virtual void resizeEvent(QResizeEvent *event); + + virtual void reject(); + + virtual void unlock(); + + virtual void cancel(); + + bool m_Unlocked; + bool m_Canceled; + bool m_allowClose; +}; diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index 1e1a4558..85808e8d 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -59,6 +59,7 @@ along with Mod Organizer. If not, see . #include "messagedialog.h" #include "installationmanager.h" #include "lockeddialog.h" +#include "waitingonclosedialog.h" #include "logbuffer.h" #include "downloadlistsortproxy.h" #include "motddialog.h" @@ -855,6 +856,8 @@ void MainWindow::showEvent(QShowEvent *event) void MainWindow::closeEvent(QCloseEvent* event) { + m_closing = true; + if (m_OrganizerCore.downloadManager()->downloadsInProgress()) { if (QMessageBox::question(this, tr("Downloads in progress"), tr("There are still downloads in progress, do you really want to quit?"), @@ -866,6 +869,16 @@ void MainWindow::closeEvent(QCloseEvent* event) } } + HANDLE injected_process_still_running = m_OrganizerCore.findAndOpenAUSVFSProcess(); + if (injected_process_still_running != INVALID_HANDLE_VALUE) + { + m_OrganizerCore.waitForApplication(injected_process_still_running); + if (!m_closing) { // if operation cancelled + event->ignore(); + return; + } + } + setCursor(Qt::WaitCursor); } @@ -1471,7 +1484,10 @@ ILockedWaitingForProcess* MainWindow::lock() ++m_LockCount; return m_LockDialog; } - m_LockDialog = new LockedDialog(this, true); + if (m_closing) + m_LockDialog = new WaitingOnCloseDialog(this); + else + m_LockDialog = new LockedDialog(this, true); m_LockDialog->setModal(true); m_LockDialog->show(); setEnabled(false); @@ -1489,6 +1505,8 @@ void MainWindow::unlock() } --m_LockCount; if (m_LockCount == 0) { + if (m_closing && m_LockDialog->canceled()) + m_closing = false; m_LockDialog->hide(); m_LockDialog->deleteLater(); m_LockDialog = nullptr; diff --git a/src/mainwindow.h b/src/mainwindow.h index f6f11157..575bf020 100644 --- a/src/mainwindow.h +++ b/src/mainwindow.h @@ -35,7 +35,7 @@ along with Mod Organizer. If not, see . //when I get round to cleaning up main.cpp struct Executable; class CategoryFactory; -class LockedDialog; +class LockedDialogBase; class OrganizerCore; #include "plugincontainer.h" //class PluginContainer; class PluginListSortProxy; @@ -348,9 +348,11 @@ private: bool m_DidUpdateMasterList; - LockedDialog *m_LockDialog { nullptr }; + LockedDialogBase *m_LockDialog { nullptr }; uint64_t m_LockCount { 0 }; + bool m_closing{ false }; + std::vector> m_PersistedGeometry; enum class ShortcutType { diff --git a/src/organizercore.cpp b/src/organizercore.cpp index 1f42bf95..c4bcaf3d 100644 --- a/src/organizercore.cpp +++ b/src/organizercore.cpp @@ -1367,26 +1367,8 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL QThread::msleep(500); // search if there is another usvfs process active and if so wait for it - // in theory a querySize of 1 is probably enough since the MO process doesn't seem to be returned by GetVFSProcessList - constexpr size_t querySize = 2; // just to be on the safe side - DWORD pids[querySize]; - size_t found = querySize; - if (!::GetVFSProcessList(&found, pids)) { - qWarning() << "Failed waiting for process completion : GetVFSProcessList failed?!"; - break; - } - - for (size_t i = 0; i < found; ++i) { - if (pids[i] == GetCurrentProcessId()) - continue; // obviously don't wait for MO process - handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION|SYNCHRONIZE, FALSE, pids[i]); - if (handle == INVALID_HANDLE_VALUE) { - qWarning() << "Failed waiting for process completion : OpenProcess failed" << GetLastError(); - continue; - } - newHandle = true; - break; - } + handle = findAndOpenAUSVFSProcess(); + newHandle = handle != INVALID_HANDLE_VALUE; } } @@ -1403,6 +1385,29 @@ bool OrganizerCore::waitForProcessCompletion(HANDLE handle, LPDWORD exitCode, IL return res == WAIT_OBJECT_0; } +HANDLE OrganizerCore::findAndOpenAUSVFSProcess() { + // in theory a querySize of 1 is probably enough since the MO process doesn't seem to be returned by GetVFSProcessList + constexpr size_t querySize = 2; // just to be on the safe side + DWORD pids[querySize]; + size_t found = querySize; + if (!::GetVFSProcessList(&found, pids)) { + qWarning() << "Failed seeking USVFS processes : GetVFSProcessList failed?!"; + return INVALID_HANDLE_VALUE; + } + + for (size_t i = 0; i < found; ++i) { + if (pids[i] == GetCurrentProcessId()) + continue; // obviously don't wait for MO process + HANDLE handle = ::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, FALSE, pids[i]); + if (handle != INVALID_HANDLE_VALUE) + return handle; + else + qWarning() << "Failed openning USVFS process " << pids[i] << " : OpenProcess failed" << GetLastError(); + } + + return INVALID_HANDLE_VALUE; +} + bool OrganizerCore::onAboutToRun( const std::function &func) { diff --git a/src/organizercore.h b/src/organizercore.h index f8806289..cf030e0a 100644 --- a/src/organizercore.h +++ b/src/organizercore.h @@ -206,6 +206,7 @@ public: HANDLE runShortcut(const QString &title); HANDLE startApplication(const QString &executable, const QStringList &args, const QString &cwd, const QString &profile); bool waitForApplication(HANDLE processHandle, LPDWORD exitCode = nullptr); + HANDLE findAndOpenAUSVFSProcess(); bool onModInstalled(const std::function &func); bool onAboutToRun(const std::function &func); bool onFinishedRun(const std::function &func); diff --git a/src/waitingonclosedialog.cpp b/src/waitingonclosedialog.cpp new file mode 100644 index 00000000..565d0a36 --- /dev/null +++ b/src/waitingonclosedialog.cpp @@ -0,0 +1,71 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#include "waitingonclosedialog.h" +#include "ui_waitingonclosedialog.h" + +#include +#include +#include +#include // for Qt::FramelessWindowHint, etc + +WaitingOnCloseDialog::WaitingOnCloseDialog(QWidget *parent) + : LockedDialogBase(parent,true) + , ui(new Ui::WaitingOnCloseDialog) +{ + ui->setupUi(this); + + // Supposedly the Qt::CustomizeWindowHint should use a customized window + // allowing us to select if there is a close button. In practice this doesn't + // seem to work. We will ignore pressing the close button if unlockByButton == true + Qt::WindowFlags flags = + this->windowFlags() | Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::WindowMinimizeButtonHint; + if (m_allowClose) + flags |= Qt::WindowCloseButtonHint; + this->setWindowFlags(flags); +} + +WaitingOnCloseDialog::~WaitingOnCloseDialog() +{ + delete ui; +} + + +void WaitingOnCloseDialog::setProcessName(const QString &name) +{ + ui->processLabel->setText(name); +} + +void WaitingOnCloseDialog::on_closeButton_clicked() +{ + unlock(); +} + +void WaitingOnCloseDialog::on_cancelButton_clicked() +{ + cancel(); + unlock(); +} + +void WaitingOnCloseDialog::unlock() { + LockedDialogBase::unlock(); + ui->label->setText("unlocking may take a few seconds"); + ui->closeButton->setEnabled(false); + ui->cancelButton->setEnabled(false); +} diff --git a/src/waitingonclosedialog.h b/src/waitingonclosedialog.h new file mode 100644 index 00000000..6650c390 --- /dev/null +++ b/src/waitingonclosedialog.h @@ -0,0 +1,56 @@ +/* +Copyright (C) 2012 Sebastian Herbord. All rights reserved. + +This file is part of Mod Organizer. + +Mod Organizer is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Mod Organizer is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Mod Organizer. If not, see . +*/ + +#pragma once + +#include "lockeddialogbase.h" + +namespace Ui { + class WaitingOnCloseDialog; +} + +/** + * Similar to the LockedDialog but used for waiting on running process during + * a process close request which requries a slightly different dialog. + **/ +class WaitingOnCloseDialog : public LockedDialogBase +{ + Q_OBJECT + +public: + explicit WaitingOnCloseDialog(QWidget *parent = 0); + ~WaitingOnCloseDialog(); + + bool canceled() const { return m_Canceled; } + + void setProcessName(const QString &name) override; + +protected: + + void unlock() override; + +private slots: + + void on_closeButton_clicked(); + void on_cancelButton_clicked(); + +private: + + Ui::WaitingOnCloseDialog *ui; +}; diff --git a/src/waitingonclosedialog.ui b/src/waitingonclosedialog.ui new file mode 100644 index 00000000..9c7818e0 --- /dev/null +++ b/src/waitingonclosedialog.ui @@ -0,0 +1,119 @@ + + + WaitingOnCloseDialog + + + + 0 + 0 + 317 + 151 + + + + Waiting for virtualized processes + + + + + + This dialog should disappear automatically if the application/game is done. + + + Virtualized processes are still running, it is prefered to keep MO running until they are finished. + + + true + + + + + + + Qt::Vertical + + + + 10 + + + + + + + + + true + + + + color: grey; + + + + + + Qt::AlignCenter + + + + + + + Qt::Vertical + + + + 10 + + + + + + + + + + Close Now + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Cancel + + + + + + + + + Qt::Vertical + + + + 10 + + + + + + + + + -- cgit v1.3.1