Rewrite the menu API to be thread-safe
This is achieved by using a Qt::DirectConnection for the menu-related signals. The bookkeeping of the data structures is done from the calling thread and then the Qt-related operations are scheduled on the main thread.
This commit is contained in:
parent
bb839ad14e
commit
3ba63be199
|
@ -54,6 +54,7 @@
|
||||||
#include "UpdateChecker.h"
|
#include "UpdateChecker.h"
|
||||||
#include "Tracer/TraceBrowser.h"
|
#include "Tracer/TraceBrowser.h"
|
||||||
#include "Tracer/TraceWidget.h"
|
#include "Tracer/TraceWidget.h"
|
||||||
|
#include "Utils/MethodInvoker.h"
|
||||||
|
|
||||||
MainWindow::MainWindow(QWidget* parent)
|
MainWindow::MainWindow(QWidget* parent)
|
||||||
: QMainWindow(parent),
|
: QMainWindow(parent),
|
||||||
|
@ -86,20 +87,6 @@ MainWindow::MainWindow(QWidget* parent)
|
||||||
connect(Bridge::getBridge(), SIGNAL(updateWindowTitle(QString)), this, SLOT(updateWindowTitleSlot(QString)));
|
connect(Bridge::getBridge(), SIGNAL(updateWindowTitle(QString)), this, SLOT(updateWindowTitleSlot(QString)));
|
||||||
connect(Bridge::getBridge(), SIGNAL(addRecentFile(QString)), this, SLOT(addRecentFile(QString)));
|
connect(Bridge::getBridge(), SIGNAL(addRecentFile(QString)), this, SLOT(addRecentFile(QString)));
|
||||||
connect(Bridge::getBridge(), SIGNAL(setLastException(uint)), this, SLOT(setLastException(uint)));
|
connect(Bridge::getBridge(), SIGNAL(setLastException(uint)), this, SLOT(setLastException(uint)));
|
||||||
connect(Bridge::getBridge(), SIGNAL(menuAddMenuToList(QWidget*, QMenu*, GUIMENUTYPE, int)), this, SLOT(addMenuToList(QWidget*, QMenu*, GUIMENUTYPE, int)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(menuAddMenu(int, QString)), this, SLOT(addMenu(int, QString)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(menuAddMenuEntry(int, QString)), this, SLOT(addMenuEntry(int, QString)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(menuAddSeparator(int)), this, SLOT(addSeparator(int)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(menuClearMenu(int, bool)), this, SLOT(clearMenu(int, bool)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(menuRemoveMenuEntry(int)), this, SLOT(removeMenuEntry(int)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(setIconMenu(int, QIcon)), this, SLOT(setIconMenu(int, QIcon)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(setIconMenuEntry(int, QIcon)), this, SLOT(setIconMenuEntry(int, QIcon)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(setCheckedMenuEntry(int, bool)), this, SLOT(setCheckedMenuEntry(int, bool)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(setHotkeyMenuEntry(int, QString, QString)), this, SLOT(setHotkeyMenuEntry(int, QString, QString)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(setVisibleMenuEntry(int, bool)), this, SLOT(setVisibleMenuEntry(int, bool)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(setVisibleMenu(int, bool)), this, SLOT(setVisibleMenu(int, bool)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(setNameMenuEntry(int, QString)), this, SLOT(setNameMenuEntry(int, QString)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(setNameMenu(int, QString)), this, SLOT(setNameMenu(int, QString)));
|
|
||||||
connect(Bridge::getBridge(), SIGNAL(getStrWindow(QString, QString*)), this, SLOT(getStrWindow(QString, QString*)));
|
connect(Bridge::getBridge(), SIGNAL(getStrWindow(QString, QString*)), this, SLOT(getStrWindow(QString, QString*)));
|
||||||
connect(Bridge::getBridge(), SIGNAL(showCpu()), this, SLOT(displayCpuWidget()));
|
connect(Bridge::getBridge(), SIGNAL(showCpu()), this, SLOT(displayCpuWidget()));
|
||||||
connect(Bridge::getBridge(), SIGNAL(showReferences()), this, SLOT(displayReferencesWidget()));
|
connect(Bridge::getBridge(), SIGNAL(showReferences()), this, SLOT(displayReferencesWidget()));
|
||||||
|
@ -116,6 +103,25 @@ MainWindow::MainWindow(QWidget* parent)
|
||||||
connect(Bridge::getBridge(), SIGNAL(showTraceBrowser()), this, SLOT(displayTraceWidget()));
|
connect(Bridge::getBridge(), SIGNAL(showTraceBrowser()), this, SLOT(displayTraceWidget()));
|
||||||
|
|
||||||
// Setup menu API
|
// Setup menu API
|
||||||
|
|
||||||
|
// Because of race conditions with this API we create a direct connection. This means that the slot will directly execute on the thread that emits the signal.
|
||||||
|
// Inside the slots we need to take special care to only do bookkeeping and not interact with the QWidgets without scheduling it on the main thread
|
||||||
|
auto menuType = (Qt::ConnectionType)(Qt::UniqueConnection | Qt::DirectConnection);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(menuAddMenuToList(QWidget*, QMenu*, GUIMENUTYPE, int)), this, SLOT(addMenuToList(QWidget*, QMenu*, GUIMENUTYPE, int)), menuType);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(menuAddMenu(int, QString)), this, SLOT(addMenu(int, QString)), menuType);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(menuAddMenuEntry(int, QString)), this, SLOT(addMenuEntry(int, QString)), menuType);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(menuAddSeparator(int)), this, SLOT(addSeparator(int)), menuType);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(menuClearMenu(int, bool)), this, SLOT(clearMenu(int, bool)), menuType);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(menuRemoveMenuEntry(int)), this, SLOT(removeMenuEntry(int)), menuType);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(setIconMenu(int, QIcon)), this, SLOT(setIconMenu(int, QIcon)), menuType);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(setIconMenuEntry(int, QIcon)), this, SLOT(setIconMenuEntry(int, QIcon)), menuType);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(setCheckedMenuEntry(int, bool)), this, SLOT(setCheckedMenuEntry(int, bool)), menuType);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(setHotkeyMenuEntry(int, QString, QString)), this, SLOT(setHotkeyMenuEntry(int, QString, QString)), menuType);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(setVisibleMenuEntry(int, bool)), this, SLOT(setVisibleMenuEntry(int, bool)), menuType);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(setVisibleMenu(int, bool)), this, SLOT(setVisibleMenu(int, bool)), menuType);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(setNameMenuEntry(int, QString)), this, SLOT(setNameMenuEntry(int, QString)), menuType);
|
||||||
|
connect(Bridge::getBridge(), SIGNAL(setNameMenu(int, QString)), this, SLOT(setNameMenu(int, QString)), menuType);
|
||||||
|
|
||||||
initMenuApi();
|
initMenuApi();
|
||||||
Bridge::getBridge()->emitMenuAddToList(this, ui->menuPlugins, GUI_PLUGIN_MENU);
|
Bridge::getBridge()->emitMenuAddToList(this, ui->menuPlugins, GUI_PLUGIN_MENU);
|
||||||
|
|
||||||
|
@ -412,6 +418,10 @@ MainWindow::MainWindow(QWidget* parent)
|
||||||
MainWindow::~MainWindow()
|
MainWindow::~MainWindow()
|
||||||
{
|
{
|
||||||
delete ui;
|
delete ui;
|
||||||
|
|
||||||
|
mMenuMutex->lock();
|
||||||
|
mMenuMutex->unlock();
|
||||||
|
delete mMenuMutex;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setupCommandBar()
|
void MainWindow::setupCommandBar()
|
||||||
|
@ -1415,6 +1425,7 @@ void MainWindow::findModularCalls()
|
||||||
|
|
||||||
void MainWindow::initMenuApi()
|
void MainWindow::initMenuApi()
|
||||||
{
|
{
|
||||||
|
mMenuMutex = new QMutex(QMutex::Recursive);
|
||||||
//256 entries are reserved
|
//256 entries are reserved
|
||||||
hEntryMenuPool = 256;
|
hEntryMenuPool = 256;
|
||||||
mEntryList.reserve(1024);
|
mEntryList.reserve(1024);
|
||||||
|
@ -1432,24 +1443,35 @@ void MainWindow::menuEntrySlot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const MainWindow::MenuInfo* MainWindow::findMenu(int hMenu)
|
MainWindow::MenuInfo* MainWindow::findMenu(int hMenu)
|
||||||
{
|
{
|
||||||
if(hMenu == -1)
|
if(hMenu == -1)
|
||||||
return 0;
|
return nullptr;
|
||||||
int nFound = -1;
|
|
||||||
for(int i = 0; i < mMenuList.size(); i++)
|
// TODO: optimize with a map
|
||||||
{
|
for(auto & menu : mMenuList)
|
||||||
if(hMenu == mMenuList.at(i).hMenu)
|
if(menu.hMenu == hMenu)
|
||||||
{
|
return menu.deleted ? nullptr : &menu;
|
||||||
nFound = i;
|
|
||||||
break;
|
return nullptr;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return nFound == -1 ? 0 : &mMenuList.at(nFound);
|
MainWindow::MenuEntryInfo* MainWindow::findMenuEntry(int hEntry)
|
||||||
|
{
|
||||||
|
if(hEntry == -1)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
// TODO: optimize with a map
|
||||||
|
for(auto & entry : mEntryList)
|
||||||
|
if(entry.hEntry == hEntry)
|
||||||
|
return entry.deleted ? nullptr : &entry;
|
||||||
|
|
||||||
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::addMenuToList(QWidget* parent, QMenu* menu, GUIMENUTYPE hMenu, int hParentMenu)
|
void MainWindow::addMenuToList(QWidget* parent, QMenu* menu, GUIMENUTYPE hMenu, int hParentMenu)
|
||||||
{
|
{
|
||||||
|
QMutexLocker locker(mMenuMutex);
|
||||||
if(!findMenu(hMenu))
|
if(!findMenu(hMenu))
|
||||||
mMenuList.push_back(MenuInfo(parent, menu, hMenu, hParentMenu, hMenu == GUI_PLUGIN_MENU));
|
mMenuList.push_back(MenuInfo(parent, menu, hMenu, hParentMenu, hMenu == GUI_PLUGIN_MENU));
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuAddToList);
|
Bridge::getBridge()->setResult(BridgeResult::MenuAddToList);
|
||||||
|
@ -1457,103 +1479,176 @@ void MainWindow::addMenuToList(QWidget* parent, QMenu* menu, GUIMENUTYPE hMenu,
|
||||||
|
|
||||||
void MainWindow::addMenu(int hMenu, QString title)
|
void MainWindow::addMenu(int hMenu, QString title)
|
||||||
{
|
{
|
||||||
const MenuInfo* menu = findMenu(hMenu);
|
QMutexLocker locker(mMenuMutex);
|
||||||
if(!menu && hMenu != -1)
|
auto parentMenu = findMenu(hMenu);
|
||||||
|
if(hMenu != -1 && parentMenu == nullptr)
|
||||||
{
|
{
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuAdd, -1);
|
Bridge::getBridge()->setResult(BridgeResult::MenuAdd, -1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int hMenuNew = hEntryMenuPool++;
|
int hMenuNew = hEntryMenuPool++;
|
||||||
// UI queue
|
MenuInfo newInfo;
|
||||||
QWidget* parent = hMenu == -1 ? this : menu->parent;
|
newInfo.hMenu = hMenuNew;
|
||||||
QMenu* wMenu = new QMenu(title, parent);
|
newInfo.hParentMenu = hMenu;
|
||||||
wMenu->menuAction()->setVisible(false);
|
newInfo.globalMenu = !parentMenu || parentMenu->globalMenu;
|
||||||
mMenuList.push_back(MenuInfo(parent, wMenu, hMenuNew, hMenu, !menu || menu->globalMenu));
|
mMenuList.push_back(newInfo);
|
||||||
if(hMenu == -1) //top-level
|
|
||||||
ui->menuBar->addMenu(wMenu);
|
MethodInvoker::invokeMethod([this, hMenuNew, title]
|
||||||
else //deeper level
|
|
||||||
{
|
{
|
||||||
menu->mMenu->addMenu(wMenu);
|
QMutexLocker locker(mMenuMutex);
|
||||||
menu->mMenu->menuAction()->setVisible(true);
|
|
||||||
}
|
// Abort if another thread deleted the entry or the parent menu
|
||||||
// UI queue
|
auto menu = findMenu(hMenuNew);
|
||||||
|
if(!menu)
|
||||||
|
return;
|
||||||
|
|
||||||
|
auto parentMenu = findMenu(menu->hParentMenu);
|
||||||
|
if(parentMenu == nullptr && menu->hParentMenu != -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Actually create the menu
|
||||||
|
QWidget* parent = menu->hParentMenu == -1 ? this : parentMenu->parent;
|
||||||
|
menu->parent = parent;
|
||||||
|
QMenu* wMenu = new QMenu(title, parent);
|
||||||
|
menu->mMenu = wMenu;
|
||||||
|
wMenu->menuAction()->setVisible(false);
|
||||||
|
if(menu->hParentMenu == -1) //top-level
|
||||||
|
ui->menuBar->addMenu(wMenu);
|
||||||
|
else //deeper level
|
||||||
|
{
|
||||||
|
parentMenu->mMenu->addMenu(wMenu);
|
||||||
|
parentMenu->mMenu->menuAction()->setVisible(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuAdd, hMenuNew);
|
Bridge::getBridge()->setResult(BridgeResult::MenuAdd, hMenuNew);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::addMenuEntry(int hMenu, QString title)
|
void MainWindow::addMenuEntry(int hMenu, QString title)
|
||||||
{
|
{
|
||||||
const MenuInfo* menu = findMenu(hMenu);
|
QMutexLocker locker(mMenuMutex);
|
||||||
if(!menu && hMenu != -1)
|
if(hMenu != -1 && findMenu(hMenu) == nullptr)
|
||||||
{
|
{
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuAddEntry, -1);
|
Bridge::getBridge()->setResult(BridgeResult::MenuAddEntry, -1);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
mEntryList.emplace_back();
|
|
||||||
MenuEntryInfo & newInfo = mEntryList.back();
|
MenuEntryInfo newInfo;
|
||||||
int hEntryNew = hEntryMenuPool++;
|
int hEntryNew = hEntryMenuPool++;
|
||||||
newInfo.hEntry = hEntryNew;
|
newInfo.hEntry = hEntryNew;
|
||||||
newInfo.hParentMenu = hMenu;
|
newInfo.hParentMenu = hMenu;
|
||||||
// UI thread
|
mEntryList.push_back(newInfo);
|
||||||
QWidget* parent = hMenu == -1 ? this : menu->parent;
|
|
||||||
QAction* wAction = new QAction(title, parent);
|
|
||||||
parent->addAction(wAction);
|
MethodInvoker::invokeMethod([this, hEntryNew, title]
|
||||||
wAction->setObjectName(QString().sprintf("ENTRY|%d", hEntryNew));
|
|
||||||
wAction->setShortcutContext((!menu || menu->globalMenu) ? Qt::ApplicationShortcut : Qt::WidgetShortcut);
|
|
||||||
parent->addAction(wAction);
|
|
||||||
connect(wAction, SIGNAL(triggered()), this, SLOT(menuEntrySlot()));
|
|
||||||
newInfo.mAction = wAction;
|
|
||||||
if(hMenu == -1) //top level
|
|
||||||
ui->menuBar->addAction(wAction);
|
|
||||||
else //deeper level
|
|
||||||
{
|
{
|
||||||
menu->mMenu->addAction(wAction);
|
QMutexLocker locker(mMenuMutex);
|
||||||
menu->mMenu->menuAction()->setVisible(true);
|
|
||||||
}
|
// Abort if another thread deleted the entry or the parent menu
|
||||||
// UI thread
|
auto entry = findMenuEntry(hEntryNew);
|
||||||
|
if(entry == nullptr)
|
||||||
|
return;
|
||||||
|
auto menu = findMenu(entry->hParentMenu);
|
||||||
|
if(menu == nullptr && entry->hParentMenu != -1)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Actually create the menu action
|
||||||
|
QWidget* parent = entry->hParentMenu == -1 ? this : menu->parent;
|
||||||
|
QAction* wAction = new QAction(title, parent);
|
||||||
|
parent->addAction(wAction);
|
||||||
|
wAction->setObjectName(QString().sprintf("ENTRY|%d", hEntryNew));
|
||||||
|
wAction->setShortcutContext((!menu || menu->globalMenu) ? Qt::ApplicationShortcut : Qt::WidgetShortcut);
|
||||||
|
parent->addAction(wAction); // TODO: something is wrong here
|
||||||
|
connect(wAction, SIGNAL(triggered()), this, SLOT(menuEntrySlot()));
|
||||||
|
entry->mAction = wAction;
|
||||||
|
if(entry->hParentMenu == -1) //top level
|
||||||
|
ui->menuBar->addAction(wAction);
|
||||||
|
else //deeper level
|
||||||
|
{
|
||||||
|
menu->mMenu->addAction(wAction);
|
||||||
|
menu->mMenu->menuAction()->setVisible(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuAddEntry, hEntryNew);
|
Bridge::getBridge()->setResult(BridgeResult::MenuAddEntry, hEntryNew);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::addSeparator(int hMenu)
|
void MainWindow::addSeparator(int hMenu)
|
||||||
{
|
{
|
||||||
const MenuInfo* menu = findMenu(hMenu);
|
QMutexLocker locker(mMenuMutex);
|
||||||
if(menu)
|
if(findMenu(hMenu) == nullptr)
|
||||||
{
|
{
|
||||||
mEntryList.emplace_back();
|
Bridge::getBridge()->setResult(BridgeResult::MenuAddSeparator, -1);
|
||||||
MenuEntryInfo & newInfo = mEntryList.back();
|
return;
|
||||||
newInfo.hEntry = -1;
|
|
||||||
newInfo.hParentMenu = hMenu;
|
|
||||||
// UI thread
|
|
||||||
newInfo.mAction = menu->mMenu->addSeparator();
|
|
||||||
// UI thread
|
|
||||||
}
|
}
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuAddSeparator);
|
|
||||||
|
MenuEntryInfo newInfo;
|
||||||
|
auto hEntryNew = hEntryMenuPool++;
|
||||||
|
newInfo.hEntry = hEntryNew;
|
||||||
|
newInfo.hParentMenu = hMenu;
|
||||||
|
mEntryList.push_back(newInfo);
|
||||||
|
|
||||||
|
MethodInvoker::invokeMethod([this, hEntryNew]
|
||||||
|
{
|
||||||
|
QMutexLocker locker(mMenuMutex);
|
||||||
|
|
||||||
|
// Abort if another thread deleted the entry or the parent menu
|
||||||
|
auto entry = findMenuEntry(hEntryNew);
|
||||||
|
if(entry == nullptr)
|
||||||
|
return;
|
||||||
|
auto menu = findMenu(entry->hParentMenu);
|
||||||
|
if(menu == nullptr)
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Actually create the separator
|
||||||
|
entry->mAction = menu->mMenu->addSeparator();
|
||||||
|
});
|
||||||
|
|
||||||
|
Bridge::getBridge()->setResult(BridgeResult::MenuAddSeparator, hEntryNew);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::clearMenuHelper(int hMenu)
|
void MainWindow::clearMenuHelper(int hMenu, bool markAsDeleted)
|
||||||
{
|
{
|
||||||
//delete menu entries
|
//delete menu entries
|
||||||
for(auto i = mEntryList.size() - 1; i != -1; i--)
|
for(auto i = mEntryList.size() - 1; i != -1; i--)
|
||||||
if(hMenu == mEntryList.at(i).hParentMenu) //we found an entry that has the menu as parent
|
{
|
||||||
mEntryList.erase(mEntryList.begin() + i);
|
if(hMenu == mEntryList[i].hParentMenu) //we found an entry that has the menu as parent
|
||||||
|
{
|
||||||
|
if(markAsDeleted)
|
||||||
|
mEntryList[i].deleted = true;
|
||||||
|
else
|
||||||
|
mEntryList.erase(mEntryList.begin() + i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
//delete the menus
|
//delete the menus
|
||||||
std::vector<int> menuClearQueue;
|
std::vector<int> menuClearQueue;
|
||||||
for(auto i = mMenuList.size() - 1; i != -1; i--)
|
for(auto i = mMenuList.size() - 1; i != -1; i--)
|
||||||
{
|
{
|
||||||
if(hMenu == mMenuList.at(i).hParentMenu) //we found a menu that has the menu as parent
|
if(hMenu == mMenuList[i].hParentMenu) //we found a menu that has the menu as parent
|
||||||
{
|
{
|
||||||
menuClearQueue.push_back(mMenuList.at(i).hMenu);
|
menuClearQueue.push_back(mMenuList[i].hMenu);
|
||||||
mMenuList.erase(mMenuList.begin() + i);
|
if(markAsDeleted)
|
||||||
|
{
|
||||||
|
mMenuList[i].deleted = true;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
mMenuList.erase(mMenuList.begin() + i);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//recursively clear the menus
|
//recursively clear the menus
|
||||||
for(auto & hMenu : menuClearQueue)
|
for(auto & hMenu : menuClearQueue)
|
||||||
clearMenuHelper(hMenu);
|
clearMenuHelper(hMenu, markAsDeleted);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::clearMenuImpl(int hMenu, bool erase)
|
void MainWindow::clearMenuImpl(int hMenu, bool erase)
|
||||||
{
|
{
|
||||||
//this recursively removes the entries from mEntryList and mMenuList
|
//this recursively removes the entries from mEntryList and mMenuList
|
||||||
clearMenuHelper(hMenu);
|
clearMenuHelper(hMenu, false);
|
||||||
for(auto it = mMenuList.begin(); it != mMenuList.end(); ++it)
|
for(auto it = mMenuList.begin(); it != mMenuList.end(); ++it)
|
||||||
{
|
{
|
||||||
auto & curMenu = *it;
|
auto & curMenu = *it;
|
||||||
|
@ -1582,74 +1677,126 @@ void MainWindow::clearMenuImpl(int hMenu, bool erase)
|
||||||
|
|
||||||
void MainWindow::clearMenu(int hMenu, bool erase)
|
void MainWindow::clearMenu(int hMenu, bool erase)
|
||||||
{
|
{
|
||||||
clearMenuImpl(hMenu, erase);
|
QMutexLocker locker(mMenuMutex);
|
||||||
|
if(findMenu(hMenu) == nullptr)
|
||||||
|
{
|
||||||
|
Bridge::getBridge()->setResult(BridgeResult::MenuClear, -1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mark all the children of the menu as deleted
|
||||||
|
clearMenuHelper(hMenu, true);
|
||||||
|
|
||||||
|
MethodInvoker::invokeMethod([this, hMenu, erase]
|
||||||
|
{
|
||||||
|
QMutexLocker locker(mMenuMutex);
|
||||||
|
// Actually clear the menu
|
||||||
|
clearMenuImpl(hMenu, erase);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuClear);
|
Bridge::getBridge()->setResult(BridgeResult::MenuClear);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::removeMenuEntry(int hEntryMenu)
|
void MainWindow::removeMenuEntry(int hEntryMenu)
|
||||||
{
|
{
|
||||||
//find and remove the hEntryMenu from the mEntryList
|
QMutexLocker locker(mMenuMutex);
|
||||||
for(int i = 0; i < mEntryList.size(); i++)
|
|
||||||
|
auto entry = findMenuEntry(hEntryMenu);
|
||||||
|
if(entry != nullptr)
|
||||||
{
|
{
|
||||||
if(mEntryList.at(i).hEntry == hEntryMenu)
|
// Delete a single menu entry
|
||||||
|
entry->deleted = true;
|
||||||
|
|
||||||
|
MethodInvoker::invokeMethod([this, hEntryMenu]
|
||||||
{
|
{
|
||||||
auto & entry = mEntryList.at(i);
|
QMutexLocker locker(mMenuMutex);
|
||||||
auto parentMenu = findMenu(entry.hParentMenu);
|
|
||||||
if(parentMenu)
|
for(int i = 0; i < mEntryList.size(); i++)
|
||||||
{
|
{
|
||||||
parentMenu->mMenu->removeAction(entry.mAction);
|
if(mEntryList.at(i).hEntry == hEntryMenu)
|
||||||
if(parentMenu->mMenu->actions().empty())
|
{
|
||||||
parentMenu->mMenu->menuAction()->setVisible(false);
|
auto & entry = mEntryList.at(i);
|
||||||
mEntryList.erase(mEntryList.begin() + i);
|
auto parentMenu = findMenu(entry.hParentMenu);
|
||||||
|
if(parentMenu)
|
||||||
|
{
|
||||||
|
parentMenu->mMenu->removeAction(entry.mAction);
|
||||||
|
if(parentMenu->mMenu->actions().empty())
|
||||||
|
parentMenu->mMenu->menuAction()->setVisible(false);
|
||||||
|
mEntryList.erase(mEntryList.begin() + i);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuRemove);
|
});
|
||||||
return;
|
|
||||||
}
|
Bridge::getBridge()->setResult(BridgeResult::MenuRemove);
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
//if hEntryMenu is not in mEntryList, clear+erase it from mMenuList
|
|
||||||
clearMenuImpl(hEntryMenu, true);
|
auto menu = findMenu(hEntryMenu);
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuRemove);
|
if(menu != nullptr)
|
||||||
|
{
|
||||||
|
// Mark the menu and all submenus as deleted
|
||||||
|
menu->deleted = true;
|
||||||
|
clearMenuHelper(hEntryMenu, true);
|
||||||
|
|
||||||
|
MethodInvoker::invokeMethod([this, hEntryMenu]
|
||||||
|
{
|
||||||
|
// Actually delete the menu and all submenus
|
||||||
|
clearMenuImpl(hEntryMenu, true);
|
||||||
|
});
|
||||||
|
|
||||||
|
Bridge::getBridge()->setResult(BridgeResult::MenuRemove);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Bridge::getBridge()->setResult(BridgeResult::MenuRemove, -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setIconMenuEntry(int hEntry, QIcon icon)
|
void MainWindow::setIconMenuEntry(int hEntry, QIcon icon)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < mEntryList.size(); i++)
|
MethodInvoker::invokeMethod([this, hEntry, icon]
|
||||||
{
|
{
|
||||||
if(mEntryList.at(i).hEntry == hEntry)
|
QMutexLocker locker(mMenuMutex);
|
||||||
{
|
|
||||||
const MenuEntryInfo & entry = mEntryList.at(i);
|
auto entry = findMenuEntry(hEntry);
|
||||||
entry.mAction->setIcon(icon);
|
if(entry == nullptr)
|
||||||
break;
|
return;
|
||||||
}
|
|
||||||
}
|
entry->mAction->setIcon(icon);
|
||||||
|
});
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryIcon);
|
Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setIconMenu(int hMenu, QIcon icon)
|
void MainWindow::setIconMenu(int hMenu, QIcon icon)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < mMenuList.size(); i++)
|
MethodInvoker::invokeMethod([this, hMenu, icon]
|
||||||
{
|
{
|
||||||
if(mMenuList.at(i).hMenu == hMenu)
|
QMutexLocker locker(mMenuMutex);
|
||||||
{
|
|
||||||
const MenuInfo & menu = mMenuList.at(i);
|
auto menu = findMenu(hMenu);
|
||||||
menu.mMenu->setIcon(icon);
|
if(menu == nullptr)
|
||||||
}
|
return;
|
||||||
}
|
|
||||||
|
menu->mMenu->setIcon(icon);
|
||||||
|
});
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuSetIcon);
|
Bridge::getBridge()->setResult(BridgeResult::MenuSetIcon);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setCheckedMenuEntry(int hEntry, bool checked)
|
void MainWindow::setCheckedMenuEntry(int hEntry, bool checked)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < mEntryList.size(); i++)
|
MethodInvoker::invokeMethod([this, hEntry, checked]
|
||||||
{
|
{
|
||||||
if(mEntryList.at(i).hEntry == hEntry)
|
QMutexLocker locker(mMenuMutex);
|
||||||
{
|
|
||||||
const MenuEntryInfo & entry = mEntryList.at(i);
|
auto entry = findMenuEntry(hEntry);
|
||||||
entry.mAction->setCheckable(true);
|
if(entry == nullptr)
|
||||||
entry.mAction->setChecked(checked);
|
return;
|
||||||
break;
|
|
||||||
}
|
entry->mAction->setCheckable(true);
|
||||||
}
|
entry->mAction->setChecked(checked);
|
||||||
|
});
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryChecked);
|
Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryChecked);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1685,75 +1832,81 @@ QString MainWindow::nestedMenuEntryDescription(const MenuEntryInfo & entry)
|
||||||
|
|
||||||
void MainWindow::setHotkeyMenuEntry(int hEntry, QString hotkey, QString id)
|
void MainWindow::setHotkeyMenuEntry(int hEntry, QString hotkey, QString id)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < mEntryList.size(); i++)
|
MethodInvoker::invokeMethod([this, hEntry, hotkey, id]
|
||||||
{
|
{
|
||||||
if(mEntryList.at(i).hEntry == hEntry)
|
QMutexLocker locker(mMenuMutex);
|
||||||
{
|
|
||||||
MenuEntryInfo & entry = mEntryList[i];
|
auto entry = findMenuEntry(hEntry);
|
||||||
entry.hotkeyId = QString("Plugin_") + id;
|
if(entry == nullptr)
|
||||||
id.truncate(id.lastIndexOf('_'));
|
return;
|
||||||
entry.hotkey = hotkey;
|
|
||||||
entry.hotkeyGlobal = entry.mAction->shortcutContext() == Qt::ApplicationShortcut;
|
entry->hotkeyId = QString("Plugin_") + id;
|
||||||
Config()->setPluginShortcut(entry.hotkeyId, nestedMenuEntryDescription(entry), hotkey, entry.hotkeyGlobal);
|
entry->hotkey = hotkey;
|
||||||
refreshShortcuts();
|
entry->hotkeyGlobal = entry->mAction->shortcutContext() == Qt::ApplicationShortcut;
|
||||||
break;
|
Config()->setPluginShortcut(entry->hotkeyId, nestedMenuEntryDescription(*entry), hotkey, entry->hotkeyGlobal);
|
||||||
}
|
refreshShortcuts();
|
||||||
}
|
});
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryHotkey);
|
Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryHotkey);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setVisibleMenuEntry(int hEntry, bool visible)
|
void MainWindow::setVisibleMenuEntry(int hEntry, bool visible)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < mEntryList.size(); i++)
|
MethodInvoker::invokeMethod([this, hEntry, visible]
|
||||||
{
|
{
|
||||||
if(mEntryList.at(i).hEntry == hEntry)
|
QMutexLocker locker(mMenuMutex);
|
||||||
{
|
|
||||||
const MenuEntryInfo & entry = mEntryList.at(i);
|
auto entry = findMenuEntry(hEntry);
|
||||||
entry.mAction->setVisible(visible);
|
if(entry == nullptr)
|
||||||
break;
|
return;
|
||||||
}
|
|
||||||
}
|
entry->mAction->setVisible(visible);
|
||||||
|
});
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryVisible);
|
Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setVisibleMenu(int hMenu, bool visible)
|
void MainWindow::setVisibleMenu(int hMenu, bool visible)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < mMenuList.size(); i++)
|
MethodInvoker::invokeMethod([this, hMenu, visible]
|
||||||
{
|
{
|
||||||
if(mMenuList.at(i).hMenu == hMenu)
|
QMutexLocker locker(mMenuMutex);
|
||||||
{
|
|
||||||
const MenuInfo & menu = mMenuList.at(i);
|
auto menu = findMenu(hMenu);
|
||||||
menu.mMenu->setVisible(visible);
|
if(menu == nullptr)
|
||||||
}
|
return;
|
||||||
}
|
|
||||||
|
menu->mMenu->setVisible(visible);
|
||||||
|
});
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuSetVisible);
|
Bridge::getBridge()->setResult(BridgeResult::MenuSetVisible);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setNameMenuEntry(int hEntry, QString name)
|
void MainWindow::setNameMenuEntry(int hEntry, QString name)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < mEntryList.size(); i++)
|
MethodInvoker::invokeMethod([this, hEntry, name]
|
||||||
{
|
{
|
||||||
if(mEntryList.at(i).hEntry == hEntry)
|
QMutexLocker locker(mMenuMutex);
|
||||||
{
|
|
||||||
const MenuEntryInfo & entry = mEntryList.at(i);
|
auto entry = findMenuEntry(hEntry);
|
||||||
entry.mAction->setText(name);
|
if(entry == nullptr)
|
||||||
Config()->setPluginShortcut(entry.hotkeyId, nestedMenuEntryDescription(entry), entry.hotkey, entry.hotkeyGlobal);
|
return;
|
||||||
break;
|
|
||||||
}
|
entry->mAction->setText(name);
|
||||||
}
|
Config()->setPluginShortcut(entry->hotkeyId, nestedMenuEntryDescription(*entry), entry->hotkey, entry->hotkeyGlobal);
|
||||||
|
});
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryName);
|
Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void MainWindow::setNameMenu(int hMenu, QString name)
|
void MainWindow::setNameMenu(int hMenu, QString name)
|
||||||
{
|
{
|
||||||
for(int i = 0; i < mMenuList.size(); i++)
|
MethodInvoker::invokeMethod([this, hMenu, name]
|
||||||
{
|
{
|
||||||
if(mMenuList.at(i).hMenu == hMenu)
|
QMutexLocker locker(mMenuMutex);
|
||||||
{
|
|
||||||
const MenuInfo & menu = mMenuList.at(i);
|
auto menu = findMenu(hMenu);
|
||||||
menu.mMenu->setTitle(name);
|
if(menu == nullptr)
|
||||||
}
|
return;
|
||||||
}
|
|
||||||
|
menu->mMenu->setTitle(name);
|
||||||
|
});
|
||||||
Bridge::getBridge()->setResult(BridgeResult::MenuSetName);
|
Bridge::getBridge()->setResult(BridgeResult::MenuSetName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -218,6 +218,7 @@ private:
|
||||||
QString hotkey;
|
QString hotkey;
|
||||||
QString hotkeyId;
|
QString hotkeyId;
|
||||||
bool hotkeyGlobal = false;
|
bool hotkeyGlobal = false;
|
||||||
|
bool deleted = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct MenuInfo
|
struct MenuInfo
|
||||||
|
@ -228,22 +229,27 @@ private:
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
QWidget* parent;
|
MenuInfo() = default;
|
||||||
QMenu* mMenu;
|
|
||||||
int hMenu;
|
QWidget* parent = nullptr;
|
||||||
int hParentMenu;
|
QMenu* mMenu = nullptr;
|
||||||
bool globalMenu;
|
int hMenu = -1;
|
||||||
|
int hParentMenu = -1;
|
||||||
|
bool globalMenu = false;
|
||||||
|
bool deleted = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
QMutex* mMenuMutex = nullptr;
|
||||||
int hEntryMenuPool;
|
int hEntryMenuPool;
|
||||||
std::vector<MenuEntryInfo> mEntryList;
|
std::vector<MenuEntryInfo> mEntryList;
|
||||||
std::vector<MenuInfo> mMenuList;
|
std::vector<MenuInfo> mMenuList;
|
||||||
|
|
||||||
void initMenuApi();
|
void initMenuApi();
|
||||||
const MenuInfo* findMenu(int hMenu);
|
MenuInfo* findMenu(int hMenu);
|
||||||
|
MenuEntryInfo* findMenuEntry(int hEntry);
|
||||||
QString nestedMenuDescription(const MenuInfo* menu);
|
QString nestedMenuDescription(const MenuInfo* menu);
|
||||||
QString nestedMenuEntryDescription(const MenuEntryInfo & entry);
|
QString nestedMenuEntryDescription(const MenuEntryInfo & entry);
|
||||||
void clearMenuHelper(int hMenu);
|
void clearMenuHelper(int hMenu, bool markAsDeleted);
|
||||||
void clearMenuImpl(int hMenu, bool erase);
|
void clearMenuImpl(int hMenu, bool erase);
|
||||||
|
|
||||||
bool bCanClose;
|
bool bCanClose;
|
||||||
|
|
Loading…
Reference in New Issue