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 "Tracer/TraceBrowser.h" | ||||
| #include "Tracer/TraceWidget.h" | ||||
| #include "Utils/MethodInvoker.h" | ||||
| 
 | ||||
| MainWindow::MainWindow(QWidget* 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(addRecentFile(QString)), this, SLOT(addRecentFile(QString))); | ||||
|     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(showCpu()), this, SLOT(displayCpuWidget())); | ||||
|     connect(Bridge::getBridge(), SIGNAL(showReferences()), this, SLOT(displayReferencesWidget())); | ||||
|  | @ -116,6 +103,25 @@ MainWindow::MainWindow(QWidget* parent) | |||
|     connect(Bridge::getBridge(), SIGNAL(showTraceBrowser()), this, SLOT(displayTraceWidget())); | ||||
| 
 | ||||
|     // 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(); | ||||
|     Bridge::getBridge()->emitMenuAddToList(this, ui->menuPlugins, GUI_PLUGIN_MENU); | ||||
| 
 | ||||
|  | @ -412,6 +418,10 @@ MainWindow::MainWindow(QWidget* parent) | |||
| MainWindow::~MainWindow() | ||||
| { | ||||
|     delete ui; | ||||
| 
 | ||||
|     mMenuMutex->lock(); | ||||
|     mMenuMutex->unlock(); | ||||
|     delete mMenuMutex; | ||||
| } | ||||
| 
 | ||||
| void MainWindow::setupCommandBar() | ||||
|  | @ -1415,6 +1425,7 @@ void MainWindow::findModularCalls() | |||
| 
 | ||||
| void MainWindow::initMenuApi() | ||||
| { | ||||
|     mMenuMutex = new QMutex(QMutex::Recursive); | ||||
|     //256 entries are reserved
 | ||||
|     hEntryMenuPool = 256; | ||||
|     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) | ||||
|         return 0; | ||||
|     int nFound = -1; | ||||
|     for(int i = 0; i < mMenuList.size(); i++) | ||||
|     { | ||||
|         if(hMenu == mMenuList.at(i).hMenu) | ||||
|         { | ||||
|             nFound = i; | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     return nFound == -1 ? 0 : &mMenuList.at(nFound); | ||||
|         return nullptr; | ||||
| 
 | ||||
|     // TODO: optimize with a map
 | ||||
|     for(auto & menu : mMenuList) | ||||
|         if(menu.hMenu == hMenu) | ||||
|             return menu.deleted ? nullptr : &menu; | ||||
| 
 | ||||
|     return nullptr; | ||||
| } | ||||
| 
 | ||||
| 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) | ||||
| { | ||||
|     QMutexLocker locker(mMenuMutex); | ||||
|     if(!findMenu(hMenu)) | ||||
|         mMenuList.push_back(MenuInfo(parent, menu, hMenu, hParentMenu, hMenu == GUI_PLUGIN_MENU)); | ||||
|     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) | ||||
| { | ||||
|     const MenuInfo* menu = findMenu(hMenu); | ||||
|     if(!menu && hMenu != -1) | ||||
|     QMutexLocker locker(mMenuMutex); | ||||
|     auto parentMenu = findMenu(hMenu); | ||||
|     if(hMenu != -1 && parentMenu == nullptr) | ||||
|     { | ||||
|         Bridge::getBridge()->setResult(BridgeResult::MenuAdd, -1); | ||||
|         return; | ||||
|     } | ||||
| 
 | ||||
|     int hMenuNew = hEntryMenuPool++; | ||||
|     // UI queue
 | ||||
|     QWidget* parent = hMenu == -1 ? this : menu->parent; | ||||
|     MenuInfo newInfo; | ||||
|     newInfo.hMenu = hMenuNew; | ||||
|     newInfo.hParentMenu = hMenu; | ||||
|     newInfo.globalMenu = !parentMenu || parentMenu->globalMenu; | ||||
|     mMenuList.push_back(newInfo); | ||||
| 
 | ||||
|     MethodInvoker::invokeMethod([this, hMenuNew, title] | ||||
|     { | ||||
|         QMutexLocker locker(mMenuMutex); | ||||
| 
 | ||||
|         // Abort if another thread deleted the entry or the parent menu
 | ||||
|         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); | ||||
|     mMenuList.push_back(MenuInfo(parent, wMenu, hMenuNew, hMenu, !menu || menu->globalMenu)); | ||||
|     if(hMenu == -1) //top-level
 | ||||
|         if(menu->hParentMenu == -1) //top-level
 | ||||
|             ui->menuBar->addMenu(wMenu); | ||||
|         else //deeper level
 | ||||
|         { | ||||
|         menu->mMenu->addMenu(wMenu); | ||||
|         menu->mMenu->menuAction()->setVisible(true); | ||||
|             parentMenu->mMenu->addMenu(wMenu); | ||||
|             parentMenu->mMenu->menuAction()->setVisible(true); | ||||
|         } | ||||
|     // UI queue
 | ||||
|     }); | ||||
| 
 | ||||
|     Bridge::getBridge()->setResult(BridgeResult::MenuAdd, hMenuNew); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::addMenuEntry(int hMenu, QString title) | ||||
| { | ||||
|     const MenuInfo* menu = findMenu(hMenu); | ||||
|     if(!menu && hMenu != -1) | ||||
|     QMutexLocker locker(mMenuMutex); | ||||
|     if(hMenu != -1 && findMenu(hMenu) == nullptr) | ||||
|     { | ||||
|         Bridge::getBridge()->setResult(BridgeResult::MenuAddEntry, -1); | ||||
|         return; | ||||
|     } | ||||
|     mEntryList.emplace_back(); | ||||
|     MenuEntryInfo & newInfo = mEntryList.back(); | ||||
| 
 | ||||
|     MenuEntryInfo newInfo; | ||||
|     int hEntryNew = hEntryMenuPool++; | ||||
|     newInfo.hEntry = hEntryNew; | ||||
|     newInfo.hParentMenu = hMenu; | ||||
|     // UI thread
 | ||||
|     QWidget* parent = hMenu == -1 ? this : menu->parent; | ||||
|     mEntryList.push_back(newInfo); | ||||
| 
 | ||||
| 
 | ||||
|     MethodInvoker::invokeMethod([this, hEntryNew, title] | ||||
|     { | ||||
|         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 && 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); | ||||
|         parent->addAction(wAction); // TODO: something is wrong here
 | ||||
|         connect(wAction, SIGNAL(triggered()), this, SLOT(menuEntrySlot())); | ||||
|     newInfo.mAction = wAction; | ||||
|     if(hMenu == -1) //top level
 | ||||
|         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); | ||||
|         } | ||||
|     // UI thread
 | ||||
|     }); | ||||
| 
 | ||||
|     Bridge::getBridge()->setResult(BridgeResult::MenuAddEntry, hEntryNew); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::addSeparator(int hMenu) | ||||
| { | ||||
|     const MenuInfo* menu = findMenu(hMenu); | ||||
|     if(menu) | ||||
|     QMutexLocker locker(mMenuMutex); | ||||
|     if(findMenu(hMenu) == nullptr) | ||||
|     { | ||||
|         mEntryList.emplace_back(); | ||||
|         MenuEntryInfo & newInfo = mEntryList.back(); | ||||
|         newInfo.hEntry = -1; | ||||
|         newInfo.hParentMenu = hMenu; | ||||
|         // UI thread
 | ||||
|         newInfo.mAction = menu->mMenu->addSeparator(); | ||||
|         // UI thread
 | ||||
|         Bridge::getBridge()->setResult(BridgeResult::MenuAddSeparator, -1); | ||||
|         return; | ||||
|     } | ||||
|     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
 | ||||
|     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
 | ||||
|     { | ||||
|         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
 | ||||
|     std::vector<int> menuClearQueue; | ||||
|     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[i].hMenu); | ||||
|             if(markAsDeleted) | ||||
|             { | ||||
|                 mMenuList[i].deleted = true; | ||||
|             } | ||||
|             else | ||||
|             { | ||||
|             menuClearQueue.push_back(mMenuList.at(i).hMenu); | ||||
|                 mMenuList.erase(mMenuList.begin() + i); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     //recursively clear the menus
 | ||||
|     for(auto & hMenu : menuClearQueue) | ||||
|         clearMenuHelper(hMenu); | ||||
|         clearMenuHelper(hMenu, markAsDeleted); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::clearMenuImpl(int hMenu, bool erase) | ||||
| { | ||||
|     //this recursively removes the entries from mEntryList and mMenuList
 | ||||
|     clearMenuHelper(hMenu); | ||||
|     clearMenuHelper(hMenu, false); | ||||
|     for(auto it = mMenuList.begin(); it != mMenuList.end(); ++it) | ||||
|     { | ||||
|         auto & curMenu = *it; | ||||
|  | @ -1582,13 +1677,41 @@ void MainWindow::clearMenuImpl(int hMenu, bool erase) | |||
| 
 | ||||
| void MainWindow::clearMenu(int hMenu, bool 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); | ||||
| } | ||||
| 
 | ||||
| void MainWindow::removeMenuEntry(int hEntryMenu) | ||||
| { | ||||
|     //find and remove the hEntryMenu from the mEntryList
 | ||||
|     QMutexLocker locker(mMenuMutex); | ||||
| 
 | ||||
|     auto entry = findMenuEntry(hEntryMenu); | ||||
|     if(entry != nullptr) | ||||
|     { | ||||
|         // Delete a single menu entry
 | ||||
|         entry->deleted = true; | ||||
| 
 | ||||
|         MethodInvoker::invokeMethod([this, hEntryMenu] | ||||
|         { | ||||
|             QMutexLocker locker(mMenuMutex); | ||||
| 
 | ||||
|             for(int i = 0; i < mEntryList.size(); i++) | ||||
|             { | ||||
|                 if(mEntryList.at(i).hEntry == hEntryMenu) | ||||
|  | @ -1602,54 +1725,78 @@ void MainWindow::removeMenuEntry(int hEntryMenu) | |||
|                             parentMenu->mMenu->menuAction()->setVisible(false); | ||||
|                         mEntryList.erase(mEntryList.begin() + i); | ||||
|                     } | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         }); | ||||
| 
 | ||||
|         Bridge::getBridge()->setResult(BridgeResult::MenuRemove); | ||||
|         return; | ||||
|     } | ||||
|     } | ||||
|     //if hEntryMenu is not in mEntryList, clear+erase it from mMenuList
 | ||||
| 
 | ||||
|     auto menu = findMenu(hEntryMenu); | ||||
|     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) | ||||
| { | ||||
|     for(int i = 0; i < mEntryList.size(); i++) | ||||
|     MethodInvoker::invokeMethod([this, hEntry, icon] | ||||
|     { | ||||
|         if(mEntryList.at(i).hEntry == hEntry) | ||||
|         { | ||||
|             const MenuEntryInfo & entry = mEntryList.at(i); | ||||
|             entry.mAction->setIcon(icon); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|         QMutexLocker locker(mMenuMutex); | ||||
| 
 | ||||
|         auto entry = findMenuEntry(hEntry); | ||||
|         if(entry == nullptr) | ||||
|             return; | ||||
| 
 | ||||
|         entry->mAction->setIcon(icon); | ||||
|     }); | ||||
|     Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryIcon); | ||||
| } | ||||
| 
 | ||||
| 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) | ||||
|         { | ||||
|             const MenuInfo & menu = mMenuList.at(i); | ||||
|             menu.mMenu->setIcon(icon); | ||||
|         } | ||||
|     } | ||||
|         QMutexLocker locker(mMenuMutex); | ||||
| 
 | ||||
|         auto menu = findMenu(hMenu); | ||||
|         if(menu == nullptr) | ||||
|             return; | ||||
| 
 | ||||
|         menu->mMenu->setIcon(icon); | ||||
|     }); | ||||
|     Bridge::getBridge()->setResult(BridgeResult::MenuSetIcon); | ||||
| } | ||||
| 
 | ||||
| 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) | ||||
|         { | ||||
|             const MenuEntryInfo & entry = mEntryList.at(i); | ||||
|             entry.mAction->setCheckable(true); | ||||
|             entry.mAction->setChecked(checked); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|         QMutexLocker locker(mMenuMutex); | ||||
| 
 | ||||
|         auto entry = findMenuEntry(hEntry); | ||||
|         if(entry == nullptr) | ||||
|             return; | ||||
| 
 | ||||
|         entry->mAction->setCheckable(true); | ||||
|         entry->mAction->setChecked(checked); | ||||
|     }); | ||||
|     Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryChecked); | ||||
| } | ||||
| 
 | ||||
|  | @ -1685,75 +1832,81 @@ QString MainWindow::nestedMenuEntryDescription(const MenuEntryInfo & entry) | |||
| 
 | ||||
| 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) | ||||
|         { | ||||
|             MenuEntryInfo & entry = mEntryList[i]; | ||||
|             entry.hotkeyId = QString("Plugin_") + id; | ||||
|             id.truncate(id.lastIndexOf('_')); | ||||
|             entry.hotkey = hotkey; | ||||
|             entry.hotkeyGlobal = entry.mAction->shortcutContext() == Qt::ApplicationShortcut; | ||||
|             Config()->setPluginShortcut(entry.hotkeyId, nestedMenuEntryDescription(entry), hotkey, entry.hotkeyGlobal); | ||||
|         QMutexLocker locker(mMenuMutex); | ||||
| 
 | ||||
|         auto entry = findMenuEntry(hEntry); | ||||
|         if(entry == nullptr) | ||||
|             return; | ||||
| 
 | ||||
|         entry->hotkeyId = QString("Plugin_") + id; | ||||
|         entry->hotkey = hotkey; | ||||
|         entry->hotkeyGlobal = entry->mAction->shortcutContext() == Qt::ApplicationShortcut; | ||||
|         Config()->setPluginShortcut(entry->hotkeyId, nestedMenuEntryDescription(*entry), hotkey, entry->hotkeyGlobal); | ||||
|         refreshShortcuts(); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|     }); | ||||
|     Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryHotkey); | ||||
| } | ||||
| 
 | ||||
| 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) | ||||
|         { | ||||
|             const MenuEntryInfo & entry = mEntryList.at(i); | ||||
|             entry.mAction->setVisible(visible); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|         QMutexLocker locker(mMenuMutex); | ||||
| 
 | ||||
|         auto entry = findMenuEntry(hEntry); | ||||
|         if(entry == nullptr) | ||||
|             return; | ||||
| 
 | ||||
|         entry->mAction->setVisible(visible); | ||||
|     }); | ||||
|     Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryVisible); | ||||
| } | ||||
| 
 | ||||
| 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) | ||||
|         { | ||||
|             const MenuInfo & menu = mMenuList.at(i); | ||||
|             menu.mMenu->setVisible(visible); | ||||
|         } | ||||
|     } | ||||
|         QMutexLocker locker(mMenuMutex); | ||||
| 
 | ||||
|         auto menu = findMenu(hMenu); | ||||
|         if(menu == nullptr) | ||||
|             return; | ||||
| 
 | ||||
|         menu->mMenu->setVisible(visible); | ||||
|     }); | ||||
|     Bridge::getBridge()->setResult(BridgeResult::MenuSetVisible); | ||||
| } | ||||
| 
 | ||||
| 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) | ||||
|         { | ||||
|             const MenuEntryInfo & entry = mEntryList.at(i); | ||||
|             entry.mAction->setText(name); | ||||
|             Config()->setPluginShortcut(entry.hotkeyId, nestedMenuEntryDescription(entry), entry.hotkey, entry.hotkeyGlobal); | ||||
|             break; | ||||
|         } | ||||
|     } | ||||
|         QMutexLocker locker(mMenuMutex); | ||||
| 
 | ||||
|         auto entry = findMenuEntry(hEntry); | ||||
|         if(entry == nullptr) | ||||
|             return; | ||||
| 
 | ||||
|         entry->mAction->setText(name); | ||||
|         Config()->setPluginShortcut(entry->hotkeyId, nestedMenuEntryDescription(*entry), entry->hotkey, entry->hotkeyGlobal); | ||||
|     }); | ||||
|     Bridge::getBridge()->setResult(BridgeResult::MenuSetEntryName); | ||||
| } | ||||
| 
 | ||||
| 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) | ||||
|         { | ||||
|             const MenuInfo & menu = mMenuList.at(i); | ||||
|             menu.mMenu->setTitle(name); | ||||
|         } | ||||
|     } | ||||
|         QMutexLocker locker(mMenuMutex); | ||||
| 
 | ||||
|         auto menu = findMenu(hMenu); | ||||
|         if(menu == nullptr) | ||||
|             return; | ||||
| 
 | ||||
|         menu->mMenu->setTitle(name); | ||||
|     }); | ||||
|     Bridge::getBridge()->setResult(BridgeResult::MenuSetName); | ||||
| } | ||||
| 
 | ||||
|  |  | |||
|  | @ -218,6 +218,7 @@ private: | |||
|         QString hotkey; | ||||
|         QString hotkeyId; | ||||
|         bool hotkeyGlobal = false; | ||||
|         bool deleted = false; | ||||
|     }; | ||||
| 
 | ||||
|     struct MenuInfo | ||||
|  | @ -228,22 +229,27 @@ private: | |||
|         { | ||||
|         } | ||||
| 
 | ||||
|         QWidget* parent; | ||||
|         QMenu* mMenu; | ||||
|         int hMenu; | ||||
|         int hParentMenu; | ||||
|         bool globalMenu; | ||||
|         MenuInfo() = default; | ||||
| 
 | ||||
|         QWidget* parent = nullptr; | ||||
|         QMenu* mMenu = nullptr; | ||||
|         int hMenu = -1; | ||||
|         int hParentMenu = -1; | ||||
|         bool globalMenu = false; | ||||
|         bool deleted = false; | ||||
|     }; | ||||
| 
 | ||||
|     QMutex* mMenuMutex = nullptr; | ||||
|     int hEntryMenuPool; | ||||
|     std::vector<MenuEntryInfo> mEntryList; | ||||
|     std::vector<MenuInfo> mMenuList; | ||||
| 
 | ||||
|     void initMenuApi(); | ||||
|     const MenuInfo* findMenu(int hMenu); | ||||
|     MenuInfo* findMenu(int hMenu); | ||||
|     MenuEntryInfo* findMenuEntry(int hEntry); | ||||
|     QString nestedMenuDescription(const MenuInfo* menu); | ||||
|     QString nestedMenuEntryDescription(const MenuEntryInfo & entry); | ||||
|     void clearMenuHelper(int hMenu); | ||||
|     void clearMenuHelper(int hMenu, bool markAsDeleted); | ||||
|     void clearMenuImpl(int hMenu, bool erase); | ||||
| 
 | ||||
|     bool bCanClose; | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue