From b6bf65c88addce66bc1f898ba45af9533e64f8cc Mon Sep 17 00:00:00 2001 From: Sita Date: Tue, 11 Jun 2024 21:14:31 +0200 Subject: [PATCH] Refactor FxTab - fix #45, #42 --- .../labelle/gui/local/StageConfiguration.java | 63 +++--------- .../gui/local/automation/AutomationTab.java | 4 + .../gui/local/fx/UnstableSceneReporter.java | 2 - .../local/fx/modulefx/ChildrenFactory.java | 4 + .../local/fx/modulefx/FxControllerLoader.java | 9 +- .../fx/modulefx/FxSceneBuilderProcessors.java | 10 +- .../gui/local/fx/modulefx/FxSetupContext.java | 2 +- .../fx/modulefx/SpringChildrenFactory.java | 34 +++++++ .../processors/InjectChildrenProcessor.java | 2 +- .../modulefx/processors/tabs/FxSmartTab.java | 14 +++ .../processors/tabs/FxSmartTabFactory.java | 90 +++++++++++++++++ .../processors/tabs/FxSmartTabManager.java | 50 ++++++++++ .../processors/tabs/InjectTabsProcessor.java | 88 +++++++++++++++++ .../local/fx/registrar/FxTabRegistrar.java | 85 ---------------- .../labelle/gui/local/menu/MainMenuTab.java | 4 + .../sita/labelle/gui/local/menu/Menu.java | 9 +- .../labelle/gui/local/menu/UnloadingTab.java | 98 ------------------- .../repositoriesfx/RepositoriesFxTab.java | 3 +- .../gui/local/repositoryfx/RepositoryTab.java | 3 +- .../labelle/gui/local/root/RootFxTab.java | 3 +- .../ScheduleExecutorFxTab.java | 3 +- .../SchedulerExecutionTab.java | 3 +- .../labelle/gui/local/tab/ApplicationTab.java | 10 -- .../labelle/gui/local/tab/TabRegistrar.java | 7 -- .../labelle/gui/local/tab/UnloadAware.java | 9 -- gui-local/src/main/resources/fx/mainmenu.fxml | 32 ------ .../java/place/sita/modulefx/LoadMode.java | 8 ++ .../modulefx/annotations/FxInjectTabs.java | 10 ++ .../sita/modulefx/annotations/FxTab.java | 3 + 29 files changed, 346 insertions(+), 316 deletions(-) create mode 100644 gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/FxSmartTab.java create mode 100644 gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/FxSmartTabFactory.java create mode 100644 gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/FxSmartTabManager.java create mode 100644 gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/InjectTabsProcessor.java delete mode 100644 gui-local/src/main/java/place/sita/labelle/gui/local/fx/registrar/FxTabRegistrar.java create mode 100644 gui-local/src/main/java/place/sita/labelle/gui/local/menu/MainMenuTab.java delete mode 100644 gui-local/src/main/java/place/sita/labelle/gui/local/menu/UnloadingTab.java delete mode 100644 gui-local/src/main/java/place/sita/labelle/gui/local/tab/ApplicationTab.java delete mode 100644 gui-local/src/main/java/place/sita/labelle/gui/local/tab/TabRegistrar.java delete mode 100644 gui-local/src/main/java/place/sita/labelle/gui/local/tab/UnloadAware.java create mode 100644 module-fx/src/main/java/place/sita/modulefx/LoadMode.java create mode 100644 module-fx/src/main/java/place/sita/modulefx/annotations/FxInjectTabs.java diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/StageConfiguration.java b/gui-local/src/main/java/place/sita/labelle/gui/local/StageConfiguration.java index 9dbbb64..24a8f9a 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/StageConfiguration.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/StageConfiguration.java @@ -18,44 +18,30 @@ import place.sita.labelle.gui.local.fx.modulefx.FxSceneBuilderProcessors; import place.sita.labelle.gui.local.fx.threading.ThreadingSupportSupplier; import place.sita.labelle.gui.local.menu.Menu; -import place.sita.labelle.gui.local.tab.ApplicationTab; -import place.sita.labelle.gui.local.tab.TabRegistrar; -import place.sita.labelle.gui.local.tab.UnloadAware; import java.util.*; @Component public class StageConfiguration { - private final List applicationTabs; - private final List tabRegistrars; - private final Menu menu; private final ConfigurableApplicationContext applicationContext; private final ShutdownRegistry shutdownRegistry; private final List stages = new ArrayList<>(); private final ChildrenFactory childrenFactory; - private final UnstableSceneReporter unstableSceneReporter; - - public StageConfiguration(List applicationTabs, - List tabRegistrars, - Menu menu, - ConfigurableApplicationContext applicationContext, - ShutdownRegistry shutdownRegistry, ChildrenFactory childrenFactory, UnstableSceneReporter unstableSceneReporter) { - this.applicationTabs = applicationTabs; - this.tabRegistrars = tabRegistrars; - this.menu = menu; + + public StageConfiguration(ConfigurableApplicationContext applicationContext, + ShutdownRegistry shutdownRegistry, ChildrenFactory childrenFactory) { this.applicationContext = applicationContext; this.shutdownRegistry = shutdownRegistry; this.childrenFactory = childrenFactory; - this.unstableSceneReporter = unstableSceneReporter; } - public void configureTestStage(Stage stage) { - configureStage(stage, StageType.TEST); + public UnstableSceneReporter configureTestStage(Stage stage) { + return configureStage(stage, StageType.TEST); } - public void configureStage(Stage stage, StageType stageType) { + public UnstableSceneReporter configureStage(Stage stage, StageType stageType) { if (stageType != StageType.ADDITIONAL) { System.setProperty("java.awt.headless", "false"); } @@ -63,9 +49,12 @@ public void configureStage(Stage stage, StageType stageType) { stage.setTitle("Labelle"); //TransitTheme transitTheme = new TransitTheme(com.pixelduke.transit.Style.LIGHT); JMetro jMetro = new JMetro(Style.DARK); - FxSceneBuilderProcessors processors = new FxSceneBuilderProcessors(childrenFactory); + UnstableSceneReporter unstableSceneReporter = new UnstableSceneReporter(); + + FxSceneBuilderProcessors processors = new FxSceneBuilderProcessors(childrenFactory, unstableSceneReporter); UUID loadId = UUID.randomUUID(); unstableSceneReporter.markUnstable(loadId, "Loading new stage"); + Menu menu = applicationContext.getBean(Menu.class); Node node; try { node = FxControllerLoader.setupForController(menu, "/fx/mainmenu.fxml", processors); @@ -79,36 +68,6 @@ public void configureStage(Stage stage, StageType stageType) { stage.setScene(scene); scene.getStylesheets().add("dark_metro_labelle.css"); - List allTabs = new ArrayList<>(); - for (var registrar : tabRegistrars) { - allTabs.addAll(registrar.tabs()); - } - allTabs.addAll(applicationTabs); - - allTabs.sort(Comparator.comparingInt(ApplicationTab::getOrder)); - - menu.mainTabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { - new Thread(() -> { - for (var applicationTab : allTabs) { - if (applicationTab instanceof UnloadAware unloadAware) { - if (newValue != applicationTab.tab()) { - unloadAware.unload(); - } - } - } - for (var applicationTab : allTabs) { - if (applicationTab instanceof UnloadAware unloadAware) { - if (newValue == applicationTab.tab()) { - unloadAware.load(); - } - } - } - }).start(); - }); - allTabs.forEach(applicationTab -> { - menu.mainTabPane.getTabs().add(applicationTab.tab()); - }); - UUID id = UUID.randomUUID(); ExistingStage thisStage = new ExistingStage(id, stage); stages.add(thisStage); @@ -128,6 +87,8 @@ public void handle(WindowEvent t) { } }); } + + return unstableSceneReporter; } public enum StageType { diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/automation/AutomationTab.java b/gui-local/src/main/java/place/sita/labelle/gui/local/automation/AutomationTab.java index 095200a..122acac 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/automation/AutomationTab.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/automation/AutomationTab.java @@ -8,10 +8,14 @@ import org.fife.ui.rsyntaxtextarea.RSyntaxTextArea; import org.fife.ui.rsyntaxtextarea.SyntaxConstants; import org.fife.ui.rtextarea.RTextScrollPane; +import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import place.sita.modulefx.annotations.FxTab; import place.sita.modulefx.annotations.PostFxConstruct; +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +@Scope(scopeName = SCOPE_PROTOTYPE) @Component @FxTab(resourceFile = "/fx/automation.fxml", order = 5, tabName = "Automation") public class AutomationTab { diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/UnstableSceneReporter.java b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/UnstableSceneReporter.java index 7374f95..d0871e6 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/UnstableSceneReporter.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/UnstableSceneReporter.java @@ -2,11 +2,9 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.stereotype.Component; import java.util.*; -@Component public class UnstableSceneReporter { private static final Logger log = LoggerFactory.getLogger(UnstableSceneReporter.class); diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/ChildrenFactory.java b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/ChildrenFactory.java index dcee3a2..d68393d 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/ChildrenFactory.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/ChildrenFactory.java @@ -1,7 +1,11 @@ package place.sita.labelle.gui.local.fx.modulefx; +import java.util.List; + public interface ChildrenFactory { T create(Class clazz); + List> getClasses(Class clazz); + } diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/FxControllerLoader.java b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/FxControllerLoader.java index acdb6f7..ad4384d 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/FxControllerLoader.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/FxControllerLoader.java @@ -1,11 +1,6 @@ package place.sita.labelle.gui.local.fx.modulefx; import javafx.scene.Node; -import org.springframework.context.ApplicationContext; -import org.springframework.stereotype.Component; -import place.sita.labelle.gui.local.fx.UnstableSceneReporter; - -import java.util.UUID; public class FxControllerLoader { @@ -43,8 +38,8 @@ public FxSceneBuilderProcessors processors() { } @Override - public Node setupForController(Object bean, Object parentController, Node parentNode, String resource, FxSetupContext context) { - return internalSetupForController(bean, parentController, parentNode, resource, context.processors()); + public Node setupForController(Object bean, String resource, FxSetupContext context) { + return internalSetupForController(bean, controller, results, resource, context.processors()); } }); diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/FxSceneBuilderProcessors.java b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/FxSceneBuilderProcessors.java index 5d46002..a83128f 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/FxSceneBuilderProcessors.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/FxSceneBuilderProcessors.java @@ -1,9 +1,8 @@ package place.sita.labelle.gui.local.fx.modulefx; -import place.sita.labelle.gui.local.fx.modulefx.processors.CheckSetHeightCorrelationProcessor; -import place.sita.labelle.gui.local.fx.modulefx.processors.InjectChildrenProcessor; -import place.sita.labelle.gui.local.fx.modulefx.processors.InjectParentFieldProcessor; -import place.sita.labelle.gui.local.fx.modulefx.processors.PostFxInjectProcessor; +import place.sita.labelle.gui.local.fx.UnstableSceneReporter; +import place.sita.labelle.gui.local.fx.modulefx.processors.*; +import place.sita.labelle.gui.local.fx.modulefx.processors.tabs.InjectTabsProcessor; import java.util.ArrayList; import java.util.List; @@ -12,10 +11,11 @@ public class FxSceneBuilderProcessors { private final List processors = new ArrayList<>(); - public FxSceneBuilderProcessors(ChildrenFactory childrenFactory) { + public FxSceneBuilderProcessors(ChildrenFactory childrenFactory, UnstableSceneReporter unstableSceneReporter) { processors.add(new CheckSetHeightCorrelationProcessor()); processors.add(new InjectParentFieldProcessor()); processors.add(new InjectChildrenProcessor(childrenFactory)); + processors.add(new InjectTabsProcessor(childrenFactory, unstableSceneReporter)); processors.add(new PostFxInjectProcessor()); } diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/FxSetupContext.java b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/FxSetupContext.java index 9de4372..dd370a4 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/FxSetupContext.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/FxSetupContext.java @@ -14,5 +14,5 @@ public interface FxSetupContext { FxSceneBuilderProcessors processors(); - Node setupForController(Object bean, Object parentController, Node parentNode, String resource, FxSetupContext context); + Node setupForController(Object bean, String resource, FxSetupContext context); } diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/SpringChildrenFactory.java b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/SpringChildrenFactory.java index 71b3b3b..a23713a 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/SpringChildrenFactory.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/SpringChildrenFactory.java @@ -1,8 +1,14 @@ package place.sita.labelle.gui.local.fx.modulefx; +import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.context.ApplicationContext; +import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; +import org.springframework.core.type.filter.AssignableTypeFilter; import org.springframework.stereotype.Component; +import java.util.ArrayList; +import java.util.List; + @Component public class SpringChildrenFactory implements ChildrenFactory { @@ -16,4 +22,32 @@ public SpringChildrenFactory(ApplicationContext context) { public T create(Class clazz) { return context.getBean(clazz); } + + @Override + public List> getClasses(Class clazz) { + ClassPathScanningCandidateComponentProvider provider = + new ClassPathScanningCandidateComponentProvider(false); + provider.addIncludeFilter(new AssignableTypeFilter(clazz)); + + List> classes = new ArrayList<>(); + + for (BeanDefinition beanDef : provider.findCandidateComponents("place.sita.labelle")) { + try { + // let's hope that this is the actual class, and not something else. + // as it doesn't have to be, see javadoc + Class beanClass = Class.forName(beanDef.getBeanClassName()); + classes.add(beanClass); + } catch (ClassNotFoundException e) { + throw new LoadClassesException(e); + } + } + + return classes; + } + + private static class LoadClassesException extends RuntimeException { + public LoadClassesException(Exception e) { + super(e); + } + } } diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/InjectChildrenProcessor.java b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/InjectChildrenProcessor.java index bf8070a..0ea21b8 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/InjectChildrenProcessor.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/InjectChildrenProcessor.java @@ -42,7 +42,7 @@ private void injectChild(Object parentController, Field fieldWithAnnotation, Cla String resource = classType.getAnnotation(FxNode.class).resourceFile(); Object bean = childrenFactory.create(classType); - Node node = context.setupForController(bean, parentController, parentNode, resource, context); + Node node = context.setupForController(bean, resource, context); fieldWithAnnotation.setAccessible(true); fieldWithAnnotation.set(parentController, bean); diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/FxSmartTab.java b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/FxSmartTab.java new file mode 100644 index 0000000..ab9b673 --- /dev/null +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/FxSmartTab.java @@ -0,0 +1,14 @@ +package place.sita.labelle.gui.local.fx.modulefx.processors.tabs; + +import javafx.scene.control.Tab; +import place.sita.labelle.gui.local.fx.UnstableSceneReporter; + +public interface FxSmartTab { + + Tab tab(); + + void load(UnstableSceneReporter unstableSceneReporter); + + void unload(); + +} diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/FxSmartTabFactory.java b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/FxSmartTabFactory.java new file mode 100644 index 0000000..ddcd9ee --- /dev/null +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/FxSmartTabFactory.java @@ -0,0 +1,90 @@ +package place.sita.labelle.gui.local.fx.modulefx.processors.tabs; + +import javafx.application.Platform; +import javafx.scene.Node; +import javafx.scene.control.ProgressIndicator; +import javafx.scene.control.Tab; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.Region; +import place.sita.labelle.gui.local.fx.UnstableSceneReporter; +import place.sita.labelle.gui.local.fx.modulefx.ChildrenFactory; +import place.sita.labelle.gui.local.fx.modulefx.FxSetupContext; +import place.sita.modulefx.annotations.FxTab; + +import java.util.UUID; + +public class FxSmartTabFactory { + + public static FxSmartTab create(FxTab fxTab, Class tabClass, FxSetupContext setupContext, ChildrenFactory factory) { + switch (fxTab.loadMode()) { + case ONLY_WHEN_NEEDED: + return createUnloadingTab(fxTab, tabClass, setupContext, factory); + default: + throw new IllegalArgumentException("Unknown load mode: " + fxTab.loadMode()); + } + } + + private static FxSmartTab createUnloadingTab(FxTab fxTab, Class tabClass, FxSetupContext setupContext, ChildrenFactory factory) { + UUID id = UUID.randomUUID(); + String stringId = id.toString(); + + Tab internalTab = new Tab(fxTab.tabName()); + internalTab.setId(stringId); + internalTab.setClosable(false); + + Platform.runLater(() -> { + internalTab.setContent(new ProgressIndicator(-1)); + }); + + return new FxSmartTab() { + private boolean loaded = false; + @Override + public Tab tab() { + return internalTab; + } + + @Override + public void load(UnstableSceneReporter unstableSceneReporter) { + if (loaded) { + return; + } + + UUID loadId = UUID.randomUUID(); + unstableSceneReporter.markUnstable(loadId, "Loading tab: " + tabClass.getName()); + + Object bean = factory.create(tabClass); + Node component = setupContext.setupForController(bean, fxTab.resourceFile(), setupContext); + + AnchorPane anchorPane = new AnchorPane(); + if (component instanceof Region region) { + region.setMinWidth(100); + region.setMinHeight(100); + } + anchorPane.getChildren().add(component); + AnchorPane.setTopAnchor(component, 0.0); + AnchorPane.setLeftAnchor(component, 0.0); + AnchorPane.setRightAnchor(component, 0.0); + AnchorPane.setBottomAnchor(component, 0.0); + Platform.runLater(() -> { + internalTab.setContent(anchorPane); + unstableSceneReporter.markStable(loadId); + }); + loaded = true; + } + + @Override + public void unload() { + if (!loaded) { + return; + } + + Platform.runLater(() -> { + internalTab.setContent(new ProgressIndicator(-1)); + }); + + loaded = false; + } + }; + } + +} diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/FxSmartTabManager.java b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/FxSmartTabManager.java new file mode 100644 index 0000000..353f174 --- /dev/null +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/FxSmartTabManager.java @@ -0,0 +1,50 @@ +package place.sita.labelle.gui.local.fx.modulefx.processors.tabs; + +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; +import place.sita.labelle.gui.local.fx.UnstableSceneReporter; +import place.sita.labelle.gui.local.fx.threading.Threading; + +import java.util.HashMap; +import java.util.Map; + +public class FxSmartTabManager { + + private final Map tabs = new HashMap<>(); + + private final UnstableSceneReporter unstableSceneReporter; + + public FxSmartTabManager(UnstableSceneReporter unstableSceneReporter) { + this.unstableSceneReporter = unstableSceneReporter; + } + + public void add(FxSmartTab tab) { + if (tabs.containsKey(tab.tab().getId())) { + throw new IllegalArgumentException("Tab with id " + tab.tab().getId() + " already exists"); + } + tabs.put(tab.tab().getId(), tab); + } + + public void register(TabPane tabPane) { + tabs.forEach((id, tab) -> tabPane.getTabs().add(tab.tab())); + + Threading.KeyStone keyStone = Threading.keyStone(); + + tabPane.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + Threading.onSeparateThread(keyStone, toolkit -> { + if (oldValue != null) { + FxSmartTab oldTab = tabs.get(oldValue.getId()); + oldTab.unload(); + } + if (newValue != null) { + FxSmartTab newTab = tabs.get(newValue.getId()); + newTab.load(unstableSceneReporter); + } + }); + }); + + Tab firstTab = tabPane.getTabs().get(0); + FxSmartTab firstFxTab = tabs.get(firstTab.getId()); + firstFxTab.load(unstableSceneReporter); + } +} diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/InjectTabsProcessor.java b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/InjectTabsProcessor.java new file mode 100644 index 0000000..1f3814e --- /dev/null +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/modulefx/processors/tabs/InjectTabsProcessor.java @@ -0,0 +1,88 @@ +package place.sita.labelle.gui.local.fx.modulefx.processors.tabs; + +import javafx.scene.control.TabPane; +import place.sita.labelle.gui.local.fx.UnstableSceneReporter; +import place.sita.labelle.gui.local.fx.modulefx.ChildrenFactory; +import place.sita.labelle.gui.local.fx.modulefx.FxSceneBuilderProcessor; +import place.sita.labelle.gui.local.fx.modulefx.FxSetupContext; +import place.sita.modulefx.annotations.FxInjectTabs; +import place.sita.modulefx.annotations.FxTab; + +import java.lang.reflect.Field; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.List; + +public class InjectTabsProcessor implements FxSceneBuilderProcessor { + + private final ChildrenFactory childrenFactory; + private final UnstableSceneReporter unstableSceneReporter; + + public InjectTabsProcessor(ChildrenFactory childrenFactory, UnstableSceneReporter unstableSceneReporter) { + this.childrenFactory = childrenFactory; + this.unstableSceneReporter = unstableSceneReporter; + } + + @Override + public void process(FxSetupContext context) { + Class controllerClass = context.controller().getClass(); + + Arrays.stream(controllerClass.getDeclaredFields()) + .filter(field -> field.isAnnotationPresent(FxInjectTabs.class)) + .forEach(field -> { + injectTabs(context.controller(), field, context); + }); + } + + private void injectTabs(Object controller, Field field, FxSetupContext context) { + FxInjectTabs tabsConfig = field.getAnnotation(FxInjectTabs.class); + Class filter = tabsConfig.value(); + + List> classes = childrenFactory.getClasses(filter); + List tabInfos = fetchTabInfoOrder(classes); + + TabPane tabPane = getTabPane(controller, field); + FxSmartTabManager manager = new FxSmartTabManager(unstableSceneReporter); + + for (TabInfo tabInfo : tabInfos) { + FxTab fxTab = tabInfo.clazz.getAnnotation(FxTab.class); + if (fxTab == null) { + throw new RuntimeException("Cannot inject something that is not an @FxTab"); + } + FxSmartTab tab = FxSmartTabFactory.create(fxTab, tabInfo.clazz, context, childrenFactory); + manager.add(tab); + } + + manager.register(tabPane); + } + + private static TabPane getTabPane(Object controller, Field field) { + TabPane tabPane; + try { + field.setAccessible(true); + tabPane = (TabPane) field.get(controller); + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + return tabPane; + } + + private List fetchTabInfoOrder(List> classes) { + List tabInfos = new ArrayList<>(); + + for (Class clazz : classes) { + FxTab tab = clazz.getAnnotation(FxTab.class); + if (tab == null) { + throw new RuntimeException("Cannot inject something that is not an @FxTab"); + } + tabInfos.add(new TabInfo(tab.order(), clazz)); + } + tabInfos.sort(Comparator.comparingInt(TabInfo::order)); + + return tabInfos; + } + + private record TabInfo(int order, Class clazz) {} + +} diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/registrar/FxTabRegistrar.java b/gui-local/src/main/java/place/sita/labelle/gui/local/fx/registrar/FxTabRegistrar.java deleted file mode 100644 index 2bdfc1c..0000000 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/fx/registrar/FxTabRegistrar.java +++ /dev/null @@ -1,85 +0,0 @@ -package place.sita.labelle.gui.local.fx.registrar; - -import org.springframework.beans.factory.config.BeanDefinition; -import org.springframework.context.ApplicationContext; -import org.springframework.context.annotation.ClassPathScanningCandidateComponentProvider; -import org.springframework.core.type.filter.AnnotationTypeFilter; -import org.springframework.stereotype.Component; -import place.sita.labelle.gui.local.fx.modulefx.ChildrenFactory; -import place.sita.labelle.gui.local.fx.modulefx.FxControllerLoader; -import place.sita.labelle.gui.local.fx.LazyLoadable; -import place.sita.labelle.gui.local.fx.UnstableSceneReporter; -import place.sita.labelle.gui.local.fx.modulefx.FxSceneBuilderProcessors; -import place.sita.labelle.gui.local.menu.UnloadingTab; -import place.sita.labelle.gui.local.tab.ApplicationTab; -import place.sita.labelle.gui.local.tab.TabRegistrar; -import place.sita.modulefx.annotations.FxTab; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -@Component -public class FxTabRegistrar implements TabRegistrar { - - private final ApplicationContext applicationContext; - private final UnstableSceneReporter unstableSceneReporter; - private final ChildrenFactory childrenFactory; - - public FxTabRegistrar( - ApplicationContext applicationContext, - UnstableSceneReporter unstableSceneReporter, - ChildrenFactory childrenFactory) { - this.applicationContext = applicationContext; - this.unstableSceneReporter = unstableSceneReporter; - this.childrenFactory = childrenFactory; - } - - @Override - public List tabs() { - ClassPathScanningCandidateComponentProvider provider = - new ClassPathScanningCandidateComponentProvider(false); - provider.addIncludeFilter(new AnnotationTypeFilter(FxTab.class)); - - List tabs = new ArrayList<>(); - - for (BeanDefinition beanDef : provider.findCandidateComponents("place.sita.labelle.gui.local")) { - - tabs.add(makeIntoDefinition(beanDef)); - } - - return tabs; - } - - private ApplicationTab makeIntoDefinition(BeanDefinition beanDef) { - String className = beanDef.getBeanClassName(); // let's hope that this is the actual class, and not something else. - // as it doesn't have to be, see javadoc - - try { - Class clazz = Class.forName(className); - if (clazz.isAnnotationPresent(FxTab.class)) { - FxTab annotation = clazz.getAnnotation(FxTab.class); - - LazyLoadable tabSupplier = () -> { - Object controller = applicationContext.getBean(clazz); - - UUID loadId = UUID.randomUUID(); - try { - unstableSceneReporter.markUnstable(loadId, "Loading tab " + annotation.tabName()); - FxSceneBuilderProcessors processors = new FxSceneBuilderProcessors(childrenFactory); - return FxControllerLoader.setupForController(controller, annotation.resourceFile(), processors); - } finally { - unstableSceneReporter.markStable(loadId); - } - }; - - return new UnloadingTab<>(className, tabSupplier, annotation.tabName(), annotation.order(), unstableSceneReporter); - } else { - throw new RuntimeException("No FxTab annotation present in a situation where it should: " + clazz); - } - } catch (ClassNotFoundException e) { - throw new RuntimeException(e); - } - } - -} diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/menu/MainMenuTab.java b/gui-local/src/main/java/place/sita/labelle/gui/local/menu/MainMenuTab.java new file mode 100644 index 0000000..6882b5e --- /dev/null +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/menu/MainMenuTab.java @@ -0,0 +1,4 @@ +package place.sita.labelle.gui.local.menu; + +public interface MainMenuTab { +} diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/menu/Menu.java b/gui-local/src/main/java/place/sita/labelle/gui/local/menu/Menu.java index c7cf62f..d09e717 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/menu/Menu.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/menu/Menu.java @@ -6,28 +6,31 @@ import javafx.scene.control.TabPane; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.context.annotation.Scope; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Component; import place.sita.magicscheduler.scheduler.events.TaskExecutionCompleteEvent; import place.sita.magicscheduler.scheduler.events.TaskPickedUpEvent; import place.sita.labelle.gui.local.fx.threading.Threading; +import place.sita.modulefx.annotations.FxInjectTabs; import java.util.concurrent.atomic.AtomicInteger; -// todo annotation specifying resource/? +import static org.springframework.beans.factory.config.BeanDefinition.SCOPE_PROTOTYPE; + +@Scope(scopeName = SCOPE_PROTOTYPE) @Component public class Menu { private static final Logger log = LoggerFactory.getLogger(Menu.class); - @FXML private ProgressBar progressBar; @FXML private Label statusLabel; - // todo @FXML + @FxInjectTabs(MainMenuTab.class) public TabPane mainTabPane; private AtomicInteger scheduled = new AtomicInteger(0); diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/menu/UnloadingTab.java b/gui-local/src/main/java/place/sita/labelle/gui/local/menu/UnloadingTab.java deleted file mode 100644 index 9946375..0000000 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/menu/UnloadingTab.java +++ /dev/null @@ -1,98 +0,0 @@ -package place.sita.labelle.gui.local.menu; - -import javafx.application.Platform; -import javafx.scene.control.ProgressIndicator; -import javafx.scene.control.Tab; -import javafx.scene.layout.AnchorPane; -import javafx.scene.layout.Region; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import place.sita.labelle.gui.local.fx.LazyLoadable; -import place.sita.labelle.gui.local.fx.UnstableSceneReporter; -import place.sita.labelle.gui.local.tab.ApplicationTab; -import place.sita.labelle.gui.local.tab.UnloadAware; - -import java.util.UUID; - - -public class UnloadingTab implements ApplicationTab, UnloadAware { - private static final Logger log = LoggerFactory.getLogger(UnloadingTab.class); - - private final Tab internalTab; - private final String internalClassName; - private final T lazyLoadable; - private final int order; - private boolean loaded = false; - private final UnstableSceneReporter unstableSceneReporter; - - public UnloadingTab( - String internalClassName, - T lazyLoadable, - String title, - int order, - UnstableSceneReporter unstableSceneReporter) { - this.internalClassName = internalClassName; - this.lazyLoadable = lazyLoadable; - this.order = order; - internalTab = new Tab(title); - this.unstableSceneReporter = unstableSceneReporter; - internalTab.setClosable(false); - } - - @Override - public Tab tab() { - return internalTab; - } - - public void load() { - UUID id = UUID.randomUUID(); - unstableSceneReporter.markUnstable(id, "Loading tab: " + internalClassName); - log.debug("Loading class {}", internalClassName); - if (loaded) { - log.debug("Unloading previous version of this ({}) interface", internalClassName); - unloadLoaded(); - } else { - log.debug("Not unloading self ({}) as it shouldn't be loaded", internalClassName); - } - - var component = lazyLoadable.getComponent(); - - Platform.runLater(() -> { - log.debug("Setting up new interface for {}", internalClassName); - AnchorPane anchorPane = new AnchorPane(); - if (component instanceof Region region) { - region.setMinWidth(100); - region.setMinHeight(100); - } - anchorPane.getChildren().add(component); - AnchorPane.setTopAnchor(component, 0.0); - AnchorPane.setLeftAnchor(component, 0.0); - AnchorPane.setRightAnchor(component, 0.0); - AnchorPane.setBottomAnchor(component, 0.0); - internalTab.setContent(anchorPane); - log.debug("New interface done for {}", internalClassName); - loaded = true; - unstableSceneReporter.markStable(id); - }); - } - - public void unload() { - if (!loaded) { - return; - } - unloadLoaded(); - } - - private void unloadLoaded() { - Platform.runLater(() -> { - internalTab.setContent(new ProgressIndicator(-1)); - }); - loaded = false; - } - - @Override - public int getOrder() { - return order; - } - -} diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/repositoriesfx/RepositoriesFxTab.java b/gui-local/src/main/java/place/sita/labelle/gui/local/repositoriesfx/RepositoriesFxTab.java index eacb6bc..5e6e7fe 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/repositoriesfx/RepositoriesFxTab.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/repositoriesfx/RepositoriesFxTab.java @@ -15,6 +15,7 @@ import javafx.fxml.FXML; import javafx.scene.control.ListView; import javafx.scene.control.TextField; +import place.sita.labelle.gui.local.menu.MainMenuTab; import place.sita.modulefx.annotations.FxTab; import place.sita.modulefx.annotations.PostFxConstruct; @@ -24,7 +25,7 @@ @Scope(scopeName = SCOPE_PROTOTYPE) @Component @FxTab(resourceFile = "/fx/repositories.fxml", order = 1, tabName = "Repositories") -public class RepositoriesFxTab { +public class RepositoriesFxTab implements MainMenuTab { private final RepositoryService repositoryService; public RepositoriesFxTab(RepositoryService repositoryService) { diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/repositoryfx/RepositoryTab.java b/gui-local/src/main/java/place/sita/labelle/gui/local/repositoryfx/RepositoryTab.java index 48454f4..37429db 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/repositoryfx/RepositoryTab.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/repositoryfx/RepositoryTab.java @@ -32,6 +32,7 @@ import place.sita.labelle.core.repository.repositories.RepositoryService; import place.sita.labelle.core.utils.Result2; import place.sita.labelle.gui.local.fx.threading.Threading.KeyStone; +import place.sita.labelle.gui.local.menu.MainMenuTab; import place.sita.modulefx.annotations.FxChild; import place.sita.modulefx.annotations.FxTab; import place.sita.modulefx.annotations.PostFxConstruct; @@ -50,7 +51,7 @@ @Scope(scopeName = SCOPE_PROTOTYPE) @Component @FxTab(order = 3, tabName = "Repository", resourceFile = "/fx/repository/repository.fxml") -public class RepositoryTab { +public class RepositoryTab implements MainMenuTab { private static final Logger log = LoggerFactory.getLogger(RepositoryTab.class); diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/root/RootFxTab.java b/gui-local/src/main/java/place/sita/labelle/gui/local/root/RootFxTab.java index 634a8b9..a7591fb 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/root/RootFxTab.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/root/RootFxTab.java @@ -12,6 +12,7 @@ import javafx.fxml.FXML; import javafx.scene.control.ListView; import javafx.scene.control.TextField; +import place.sita.labelle.gui.local.menu.MainMenuTab; import place.sita.modulefx.annotations.FxTab; import place.sita.modulefx.annotations.PostFxConstruct; @@ -23,7 +24,7 @@ @Scope(scopeName = SCOPE_PROTOTYPE) @Component @FxTab(resourceFile = "/fx/root.fxml", order = 2, tabName = "Data roots") -public class RootFxTab { +public class RootFxTab implements MainMenuTab { private final ImageLocatorService imageLocatorService; diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/schedulerexecutionfx/ScheduleExecutorFxTab.java b/gui-local/src/main/java/place/sita/labelle/gui/local/schedulerexecutionfx/ScheduleExecutorFxTab.java index 413bc73..6cc1618 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/schedulerexecutionfx/ScheduleExecutorFxTab.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/schedulerexecutionfx/ScheduleExecutorFxTab.java @@ -8,6 +8,7 @@ import javafx.scene.control.*; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; +import place.sita.labelle.gui.local.menu.MainMenuTab; import place.sita.magicscheduler.TasksService; import place.sita.modulefx.annotations.FxTab; import place.sita.modulefx.annotations.PostFxConstruct; @@ -19,7 +20,7 @@ @Scope(scopeName = SCOPE_PROTOTYPE) @Component @FxTab(resourceFile = "/fx/scheduler_executor.fxml", order = 0, tabName = "Scheduler Executor") -public class ScheduleExecutorFxTab { +public class ScheduleExecutorFxTab implements MainMenuTab { private final TasksService tasksService; public ScheduleExecutorFxTab(TasksService tasksService) { diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/schedulerexecutionsfx/SchedulerExecutionTab.java b/gui-local/src/main/java/place/sita/labelle/gui/local/schedulerexecutionsfx/SchedulerExecutionTab.java index 80be8ae..d99ab8f 100644 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/schedulerexecutionsfx/SchedulerExecutionTab.java +++ b/gui-local/src/main/java/place/sita/labelle/gui/local/schedulerexecutionsfx/SchedulerExecutionTab.java @@ -9,6 +9,7 @@ import org.springframework.stereotype.Component; import place.sita.labelle.gui.local.fx.LabPaginatorFactory; import place.sita.labelle.gui.local.fx.LabPaginatorFactory.LabPaginator; +import place.sita.labelle.gui.local.menu.MainMenuTab; import place.sita.magicscheduler.ExecutionsService; import place.sita.magicscheduler.ExecutionsService.ScheduledTaskResponse; import place.sita.magicscheduler.TaskTypeRepository; @@ -25,7 +26,7 @@ @Scope(scopeName = SCOPE_PROTOTYPE) @Component @FxTab(resourceFile = "/fx/schedulerexecutions/scheduler_executions.fxml", order = 1, tabName = "Scheduler Executions") -public class SchedulerExecutionTab { +public class SchedulerExecutionTab implements MainMenuTab { private final TaskTypeRepository taskTypeRepository; private final ExecutionsService executionsService; diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/tab/ApplicationTab.java b/gui-local/src/main/java/place/sita/labelle/gui/local/tab/ApplicationTab.java deleted file mode 100644 index 0b22b6e..0000000 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/tab/ApplicationTab.java +++ /dev/null @@ -1,10 +0,0 @@ -package place.sita.labelle.gui.local.tab; - -import javafx.scene.control.Tab; -import org.springframework.core.Ordered; - -public interface ApplicationTab extends Ordered { - - Tab tab(); - -} diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/tab/TabRegistrar.java b/gui-local/src/main/java/place/sita/labelle/gui/local/tab/TabRegistrar.java deleted file mode 100644 index ee99c4f..0000000 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/tab/TabRegistrar.java +++ /dev/null @@ -1,7 +0,0 @@ -package place.sita.labelle.gui.local.tab; - -import java.util.List; - -public interface TabRegistrar { - List tabs(); -} diff --git a/gui-local/src/main/java/place/sita/labelle/gui/local/tab/UnloadAware.java b/gui-local/src/main/java/place/sita/labelle/gui/local/tab/UnloadAware.java deleted file mode 100644 index 36b22c4..0000000 --- a/gui-local/src/main/java/place/sita/labelle/gui/local/tab/UnloadAware.java +++ /dev/null @@ -1,9 +0,0 @@ -package place.sita.labelle.gui.local.tab; - -public interface UnloadAware { - - void load(); - - void unload(); - -} diff --git a/gui-local/src/main/resources/fx/mainmenu.fxml b/gui-local/src/main/resources/fx/mainmenu.fxml index bc4f116..9905af5 100644 --- a/gui-local/src/main/resources/fx/mainmenu.fxml +++ b/gui-local/src/main/resources/fx/mainmenu.fxml @@ -1,37 +1,5 @@ - - diff --git a/module-fx/src/main/java/place/sita/modulefx/LoadMode.java b/module-fx/src/main/java/place/sita/modulefx/LoadMode.java new file mode 100644 index 0000000..c6228b5 --- /dev/null +++ b/module-fx/src/main/java/place/sita/modulefx/LoadMode.java @@ -0,0 +1,8 @@ +package place.sita.modulefx; + +public enum LoadMode { + //EAGER, + //LAZY, + ONLY_WHEN_NEEDED, + ; +} diff --git a/module-fx/src/main/java/place/sita/modulefx/annotations/FxInjectTabs.java b/module-fx/src/main/java/place/sita/modulefx/annotations/FxInjectTabs.java new file mode 100644 index 0000000..3996617 --- /dev/null +++ b/module-fx/src/main/java/place/sita/modulefx/annotations/FxInjectTabs.java @@ -0,0 +1,10 @@ +package place.sita.modulefx.annotations; + +import java.lang.annotation.*; + +@Documented +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.FIELD) +public @interface FxInjectTabs { + Class value(); +} diff --git a/module-fx/src/main/java/place/sita/modulefx/annotations/FxTab.java b/module-fx/src/main/java/place/sita/modulefx/annotations/FxTab.java index a43dcc1..079c6a8 100644 --- a/module-fx/src/main/java/place/sita/modulefx/annotations/FxTab.java +++ b/module-fx/src/main/java/place/sita/modulefx/annotations/FxTab.java @@ -1,5 +1,7 @@ package place.sita.modulefx.annotations; +import place.sita.modulefx.LoadMode; + import java.lang.annotation.*; @Documented @@ -9,4 +11,5 @@ String resourceFile(); int order(); String tabName(); + LoadMode loadMode() default LoadMode.ONLY_WHEN_NEEDED; }