ModOrganizer2 / modorganizer

Mod manager for various PC games. Discord Server: https://discord.gg/ewUVAqyrQX if you would like to be more involved
http://www.nexusmods.com/skyrimspecialedition/mods/6194
GNU General Public License v3.0
2.16k stars 160 forks source link

Allow plugin-provided Context Menu Entries #1016

Open Al12rs opened 4 years ago

Al12rs commented 4 years ago

Problem:

Currently Mo2 plugins can't add context menu options to the main context menus present in Mo2. This severely limits their usability and functionality, reducing the interest in creating new plugins since they would result less useful.

Goals:

A system that can be easily added to all interested context menus once implemented that allows plugins to:

The usage of such a system should be made as easy as possible for the plugin authors.

The list of interesting context menus in order of probable usefulness: [ ] Modlist [ ] Pluginlist [ ] Donwloads list [ ] Data tab [ ] Mod Info Filetree [ ] Overwrite Filetree [ ] Saves tab [ ] Archives tab (if it survives) [ ] ... more as requested ...

Proposed solution:

System based on functions added to the MO2 interfaces that the plugins can use to register new options, for example: void Modlist::registerContextMenuOptions(MenuStructure subMenuToAdd, QString pluginName?)

Immediate problem with this approach is that some of these lists might not be initialized during plugin initialization so instead we could have a class were we store the options that each modlist can then query. So maybe something like this: void OrganizerCore::registerContextMenuOptions(MenuStructure menuToAdd, OrganizerCore::MenuTargetsEnum target, QString pluginName) substitute OrganizerCore with a dedicated class if necessary.

What data does MO2 need from the Plugins: Possibly some kind of way to identify which plugin added the menu.

A list or vector of "Elements" to add. Each element can be either

A menu group needs the following data:

A menu separator has no info needed.

For a menu option Mo2 requires

I don't actually know much about the python interface and how that works so I don't know how difficult or problematic creating a new structure such as the one described above could be.

Otherwise could the plugin simply return a QMenu object? Would that work?

The relative order of these elements should be preserved for the presentation.

The Plugins on the other hand require Mo2 to give data regarding the current context menu request, specifically the selected items (for multiple selection it would be useful to know which item is the one the user actually pressed on to still perform single target operations on that element at least). Most target lists for context menus (like the modlist) are already exposed in someway to the plugins so they would just require to know what is currently selected.

Limitations of proposed solution:

Notes

This is just what I was able to think about for now, there could be issues I have not considered yet so please discuss here of possible problems or alternatives of this proposition.

AnyOldName3 commented 4 years ago

Here's a link to the middle of a post from the last time this was discussed: https://discordapp.com/channels/265929299490635777/396267240267317249/664999923376193537

AnyOldName3 commented 4 years ago

And another one from the time before that (thanks Isa) https://discordapp.com/channels/265929299490635777/412414009694748693/681612534171566103

Al12rs commented 4 years ago

Iteration after discussions on discord:

Goals:

What plugins should be able to do:

To allow all these while still exposing a simple interface we will likely need multiple ways to add entries to a menu, allowing for very easy simple additions while still providing greater control with more complex alternatives.

Solution proposal:

The main code exposes a register for each expandable menu where plugins can add new entries. This register would be a templated class (due to the fact that the selection context type is different for different menus), that exposes register() functions to plugins.

A mock example:

MenuRegister<std::Vector<ModInfo>> modlistMenu;

m_OrganizerCore->PluginContributionPoints->modlistMenu->registerEntry(name, triggeredCallback, showCallback);

triggeredCallbackand showCallbackwould take a vector of ModInfos representing the currently selected mods on the modlist.

A variation of registerEntrycould take a callback for the name as well to allow context aware naming of the entry.

Consecutive calls to registerEntrywill remember entry order.

Something like registerSeparator() could be used to add separators in.

For adding submenus another function would be needed but this time the plugin author needs to pass an entire structure of entries and their callbacks. To allow this we need to expose a structure type to plugins that they can use to generate their desidered entries and pass them back.

Using such a structure would allow late entry generation. Instead of registering the entries immediately the plugins could just register their interest in adding entries later so basically just a callback that returns a generated structure of entries to add based on the selection context. This callback will be executed by the menu register when the menu is requested by the user.

On the mo2 side in a OnContextMenuRequested() all that would be needed to do is to query the respective menuRegisterpassing the current context to get back the entries to add. The register will be the one that executes all the show() callbacks to test whether the entries are valid and potentially requests plugins to return a custom entry structure based on context.

Things still to decide:

With the proposed method plugins can't decide where in the menu their new entries will end up and they can't extend submenus of other plugins since the QMenu object isn't passed around.

My idea was to simply have the mo2 dev decide a sensible place in the menu where to put potential addition and have all the plugin added entries relegated to that place. Entries from the same plugin would be sequential and keep order, but the relative order of additions from different plugins would not be adjustable.

For concerns about menus getting too long due to plugins adding too many options, the Mo2 developer could set a limit to the number of entries that can stay at top level and move them under an "> Others..." submenu in case there are too many of them. The number would depend on the menu in question.

Another thing to define is the structure of late added entries and submenus, that will need to be exposed to the python interface.