Skip to content

Commit

Permalink
Begin moving out plugin handling from UI layer, now done with PluginW…
Browse files Browse the repository at this point in the history
…rappers owning this pre-load of UI
  • Loading branch information
Rexeh committed Feb 6, 2024
1 parent 9ca4d61 commit 28483e2
Show file tree
Hide file tree
Showing 9 changed files with 183 additions and 100 deletions.
2 changes: 1 addition & 1 deletion joystick_diagrams/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
window = MainWindow()
window.show()

apply_stylesheet(app, theme="dark_blue.xml", invert_secondary=False)
apply_stylesheet(app, theme="light_blue.xml", invert_secondary=True)

app.exec()
except Exception as error: # pylint: disable=broad-except
Expand Down
18 changes: 17 additions & 1 deletion joystick_diagrams/app_init.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,31 @@
import logging

from joystick_diagrams.app_state import AppState
from joystick_diagrams.db import db_init
from joystick_diagrams.plugin_manager import ParserPluginManager

_logger = logging.getLogger(__name__)


def init():
# TODO log and orchestrate this better

# Setup datastore
db_init.init()
# -------------------------------

# Get global state object
_state = AppState()
# -------------------------------

# -- Initialise Plugins System --

# Initialise Plugins System
plugins = ParserPluginManager()
plugins.load_discovered_plugins()
_state.init_plugins(plugins)

# -------------------------------


if __name__ == "__main__":
pass
33 changes: 13 additions & 20 deletions joystick_diagrams/app_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,31 +21,26 @@ def __new__(cls, *args, **kwargs):

def _init(self) -> None:
self.plugin_manager: ParserPluginManager | None = None
self.plugin_profile_collections: list
self.raw_profiles: list = [] # TODO repalce with parsed profiles from Plugins
self.profileObjectMapping = self.update_profile_object_mapping()
self.profileObjectMapping: dict[str, Profile_] = {}
self.profileParentMapping: dict[str, list[str]] = {} # TODO correctly initialise these
self.processedProfileObjectMapping: dict[str, Profile_] = {} # TODO Think here about name colissions
self.update_processed_profiles()

def init_plugins(self, plugin_manager: ParserPluginManager):
self.plugin_manager = plugin_manager

def process_loaded_plugins(self):
self.plugin_profile_collections = self.plugin_manager.process_loaded_plugins()
# TODO clean up
self.raw_profiles.clear()
for pc in self.plugin_profile_collections:
name = pc[0]
for profile in pc[1].profiles.values():
# TODO tidy up and push up
profile.name = f"{profile.name} - {name}"
self.raw_profiles.append(profile)
self.profileObjectMapping = self.update_profile_object_mapping()
self.update_processed_profiles()
def process_loaded_plugins(self, profile_collections: dict[str, ProfileCollection]):
# Clear existing processed profiles
self.profileObjectMapping.clear()

for profile_source, profiles in profile_collections.items():
for profile_name, profile_obj in profiles.profiles.items():
combined_name = f"{profile_source} - {profile_name}"
self.profileObjectMapping[combined_name] = profile_obj

def update_profile_object_mapping(self):
return {x.name: x for x in self.raw_profiles}
_logger.debug(f"Loaded plugins resulted in the following profiles being detected {self.profileObjectMapping}")

self.update_processed_profiles()

def get_processed_profile(self, profile_identifier: str) -> Profile_:
"""Return inherited profile for given Profile Identifier."""
Expand Down Expand Up @@ -133,6 +128,4 @@ def profile_mock(self):


if __name__ == "__main__":
_appState = AppState()
_appState.update_processed_profiles()
print(_appState)
pass
1 change: 1 addition & 0 deletions joystick_diagrams/plugins/dcs_world_plugin/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

class ParserPlugin(PluginInterface):
def __init__(self):
super().__init__()
self.settings = settings
self.settings.validators.register()
self.path = None
Expand Down
3 changes: 2 additions & 1 deletion joystick_diagrams/plugins/plugin_interface.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ def __init__(self, dialog_title: str, default_path: str, supported_extensions: l
@property
@abstractmethod
def path_type(self) -> FolderPath | FilePath:
"""Returns a valid path type object to specify the plugins path sourcing method."""
...

def file_not_valid_exception(self, exception_message: str):
Expand Down Expand Up @@ -71,7 +72,7 @@ def name(self) -> str:

@property
def version(self) -> str:
"""Returns a version property"""
"""Returns a valid path type object""" """Returns a version property"""
return f"{self.settings.VERSION}"

@property
Expand Down
4 changes: 2 additions & 2 deletions joystick_diagrams/ui/mock_main/configure_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@ def get_profiles(self):
def initialise_available_profiles(self):
self.profileList.clear()
profiles = self.get_profiles()
for i in profiles.values():
self.profileList.addItem(i.name)
for key in profiles.keys():
self.profileList.addItem(key)

def initialise_customise_binds(self):
profiles = self.appState.get_processed_profiles()
Expand Down
80 changes: 43 additions & 37 deletions joystick_diagrams/ui/mock_main/plugin_settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,54 +35,81 @@ def __init__(self, *args, **kwargs):
self.plugin = None

# Attributes

# Connections
self.pluginEnabled.stateChanged.connect(self.handle_enabled_change)
self.configureLink.clicked.connect(self.open_file_dialog)
self.pluginModified.connect(self.trigger_plugin_save)

# Setup

# Style Overrides
self.configureLink.setStyleSheet("color: white !important;")

def setup(self):
"""Initialise the widget configuration"""

# Setup labels
self.pluginEnabled.setChecked(self.plugin.enabled)
self.plugin_name_label.setText(self.plugin.plugin_name)
self.plugin_version_label.setText(self.plugin.plugin_version)
self.plugin_name_label.setText(self.plugin.name)
self.plugin_version_label.setText(self.plugin.version)

# Setup file selection
self.configureLink.setText(self.get_plugin_path_type().dialog_title)
self.configureLink.setDescription("")
if self.plugin.path:
self.configureLink.setText(RECONFIGURE_TEXT)
self.configureLink.setDescription(self.plugin.path.__str__())
else:
self.configureLink.setText(self.get_plugin_path_type().dialog_title)
self.configureLink.setDescription("")

# Handle plugin procesing depending on its enabled state
self.handle_plugin_enabled_state()

self.plugin.plugin_loaded = True

def handle_plugin_enabled_state(self):
"""Setup the plugin based on the enabled state. Only fully load enabled plugins for safety"""
state = self.plugin.enabled

if self.plugin.plugin.path:
self.set_plugin_path(self.plugin.plugin.path)
match state:
case 0:
self.configureLink.setDisabled(1)

# TODO if file already set from storage...
case 1:
self.configureLink.setDisabled(0)

case _:
self.configureLink.setDisabled(1)

def trigger_plugin_save(self):
self.plugin.store_plugin_configuration()

@Slot()
def handle_enabled_change(self, data):
# Set the enabled state based on data
self.plugin.enabled = data if data == 0 else 1
print(f"Plugin enabled state is now {self.plugin.enabled}")
self.pluginModified.emit(self.plugin)

def get_plugin_file_extensions(self):
return []
# Process based on the new state
self.handle_plugin_enabled_state()

_logger.debug(f"Plugin enabled state is now {self.plugin.enabled}")

self.pluginModified.emit(self.plugin)

def get_plugin_path_type(self) -> PluginInterface.FilePath | PluginInterface.FolderPath:
return self.plugin.plugin.path_type
return self.plugin.path_type

def set_plugin_path(self, path: Path):
if not isinstance(path, Path):
_logger.error(f"Plugin path for {self.plugin.name} was not a Path object")
return

try:
load = self.plugin.plugin.set_path(path)
_logger.debug(f"Atempting path set for plugin {self.plugin.name}")
load = self.plugin.set_path(path)

if not load:
_logger.error(f"An error occured seting the path for {self.plugin.name}")
raise JoystickDiagramsException("Error loading plugin")
if load:
_logger.info(f"Path successfully set for {self.plugin.name}")
self.configureLink.setText(RECONFIGURE_TEXT)
self.configureLink.setDescription(path.__str__())
self.pluginPathConfigured.emit(self.plugin)
Expand Down Expand Up @@ -118,27 +145,6 @@ def open_file_dialog(self) -> None:
case _:
_logger.error("Unexpected plugin path type given.")

# def load_plugin_settings(self, data):


# self.pluginVersionInfo.setText(f"Version {data.version}")
# self.pluginName.setText(f"{data.name} Settings")

# # Path Setup
# self.pluginPath.setText(data.path)

# # Prevents duplicate signals from being connected
# if self.pluginPathButton.isSignalConnected(QMetaMethod.fromSignal(self.pluginPathButton.clicked)):
# self.pluginPathButton.clicked.disconnect()

# # Clean up
# # Additional validation to be added to PluginInterface/Plugin loading
# if isinstance(data.path_type, PluginInterface.FilePath):
# self.pluginPathButton.clicked.connect(lambda: self.file_dialog(data.path_type))

# if isinstance(data.path_type, PluginInterface.FolderPath):
# self.pluginPathButton.clicked.connect(lambda: self.folder_dialog(data.path_type))


if __name__ == "__main__":
app = QApplication(sys.argv)
Expand Down
70 changes: 36 additions & 34 deletions joystick_diagrams/ui/mock_main/setting_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from joystick_diagrams.app_state import AppState
from joystick_diagrams.db import db_init, db_plugin_data
from joystick_diagrams.exceptions import JoystickDiagramsException
from joystick_diagrams.input.profile_collection import ProfileCollection
from joystick_diagrams.plugins.plugin_interface import PluginInterface
from joystick_diagrams.ui.mock_main.plugin_settings import PluginSettings
from joystick_diagrams.ui.mock_main.qt_designer import setting_page_ui
Expand All @@ -25,20 +26,21 @@


class PluginsPage(QMainWindow, setting_page_ui.Ui_Form): # Refactor pylint: disable=too-many-instance-attributes
parsePlugins = Signal()
profileCollectionChange = Signal()

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.setupUi(self)
self.appState = AppState()

# Attributes
self.plugin_wrappers = []
self.plugin_wrappers: list[PluginWrapper] = []
self.window_content = None

# Connections
self.parserPluginList.itemClicked.connect(self.plugin_selected)
self.parsePlugins.connect(self.execute_plugin_parsers)
self.parserPluginList.itemChanged.connect(self.plugin_selected)
self.profileCollectionChange.connect(self.update_profile_collections)

# Setup
self.remove_defaults()
Expand All @@ -50,71 +52,71 @@ def __init__(self, *args, **kwargs):
def remove_defaults(self):
self.parserPluginList.clear()

def get_plugin_configuration(self, plugin_name: str):
return db_plugin_data.get_plugin_configuration(plugin_name)

@Slot()
def store_plugin_configuration(self, plugin_object: PluginWrapper):
db_plugin_data.add__update_plugin_configuration(plugin_object.plugin_name, plugin_object.enabled)

def initialise_plugins(self):
"""Initialise the available plugins into wrapper objects for use in UI.
PluginWrapper enriches the Plugin model with UI specific data
"""
for plugin in self.appState.plugin_manager.get_available_plugins():
plugin_lookup = self.get_plugin_configuration(plugin.name)

if plugin_lookup:
enabled_flag = plugin_lookup[1]
self.plugin_wrappers.append(
PluginWrapper(plugin.name, plugin.version, plugin.icon, plugin, enabled=enabled_flag)
)
else:
# Create Wrapper
wrapper = PluginWrapper(plugin.name, plugin.version, plugin.icon, plugin, enabled=False)
# Store the plugin with default of FALSE for enabled
self.store_plugin_configuration(wrapper)

self.plugin_wrappers.append(wrapper)
self.plugin_wrappers.append(PluginWrapper(plugin))

def populate_available_plugin_list(self):
for plugin_data in self.plugin_wrappers:
item = QListWidgetItem(QIcon(plugin_data.plugin_icon), plugin_data.plugin_name)
item = QListWidgetItem(QIcon(plugin_data.icon), plugin_data.name)
item.setData(Qt.UserRole, plugin_data)
self.parserPluginList.addItem(item)

self.pre_intiialise_plugin_wrappers()

def pre_intiialise_plugin_wrappers(self):
"""Sets up the wrappers in the UI, so that users don't need to click on them individually
This could be done better but for now it works
"""
for item in range(self.parserPluginList.count()):
self.parserPluginList.setCurrentRow(item)
self.plugin_selected()

@Slot()
def plugin_selected(self, item):
def plugin_selected(self):
if self.window_content:
self.window_content.hide()

self.window_content = PluginSettings()

# Signals/Slots
self.window_content.pluginPathConfigured.connect(self.handle_plugin_path_load)

# Page Setup For now
self.window_content.plugin = self.get_selected_plugin_object()
self.window_content.setup()

# Signals/Slots
self.window_content.pluginModified.connect(self.store_plugin_configuration)
self.window_content.pluginPathConfigured.connect(self.handle_plugin_path_load)
self.window_content.setup()

self.window_content.setParent(self.pluginOptionsWidget)
self.window_content.show()

@Slot()
def handle_plugin_path_load(self, plugin: PluginWrapper):
print(f"Data is {plugin}")
_logger.debug(f"Plugin path changed for {plugin}, attempting to process plugin")
try:
plugin.plugin_profile_collection = plugin.plugin.process()
print(f"Data is {plugin.plugin_profile_collection}")
self.profileCollectionChange.emit()
except JoystickDiagramsException:
pass

def get_plugin_wrapper_collections(self) -> dict[str, ProfileCollection]:
"""Returns a list of Profile Collections that are tagged with the Plugin Name where the plugin is enabled"""
return {
x.name: x.plugin_profile_collection
for x in self.plugin_wrappers
if x.enabled and x.plugin_profile_collection
}

@Slot()
def execute_plugin_parsers(self):
print("Executing plugin parsers")
self.appState.process_loaded_plugins()
def update_profile_collections(self):
_logger.debug(f"Updating profile collections from all plugins")
self.appState.process_loaded_plugins(self.get_plugin_wrapper_collections())

def get_selected_plugin_object(self) -> PluginInterface:
return self.parserPluginList.currentItem().data(Qt.UserRole)
Expand Down
Loading

0 comments on commit 28483e2

Please sign in to comment.