Refactor ViewLoader for proper injection

ViewLoader is now modeled as a stateless singleton and injected into all
components (usually controllers) that need it. This is as opposed to the
prior situation in which a ViewLoader was instatiated every time view
loading was required. This was an understerstandable approach, given
that FXMLLoader (which ViewLoader wraps) assumes the same
construction-per-use approach, but it is nevertheless problematic.

 - Return Item tuple from ViewLoader#load.

   This avoids the need to call ViewLoader#load followed by individual
   calls to get the view and then the controller, as this requires
   mutable state to be held in the ViewLoader (which is now a shared
   singleton injected throughout the application). The previous approach
   is (a) confusing and (b) could create problems in a multithreaded
   environment. While (b) is unlikely in our case, the new approach is
   still clearer anyway.

 - Refactor ViewLoader#load to accept URL vs FxmlResource.

   This decouples the ViewLoader abstraction away from the
   Navigation.Item / FxmlResource abstraction completely.
This commit is contained in:
Chris Beams 2014-11-15 23:18:02 +01:00
parent cc75aec8f0
commit a4d3fab462
No known key found for this signature in database
GPG key ID: 3D214F8F5BC5ED73
21 changed files with 215 additions and 219 deletions

View file

@ -26,6 +26,8 @@ import java.util.Set;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import static com.google.common.base.Preconditions.checkNotNull;
public abstract class BitsquareModule extends AbstractModule { public abstract class BitsquareModule extends AbstractModule {
protected final Environment env; protected final Environment env;
@ -33,6 +35,7 @@ public abstract class BitsquareModule extends AbstractModule {
private final Set<BitsquareModule> modules = Sets.newHashSet(); private final Set<BitsquareModule> modules = Sets.newHashSet();
protected BitsquareModule(Environment env) { protected BitsquareModule(Environment env) {
checkNotNull(env, "Environment must not be null");
this.env = env; this.env = env;
} }

View file

@ -19,6 +19,7 @@ package io.bitsquare.app.gui;
import io.bitsquare.BitsquareException; import io.bitsquare.BitsquareException;
import io.bitsquare.account.AccountSettings; import io.bitsquare.account.AccountSettings;
import io.bitsquare.gui.GuiceControllerFactory;
import io.bitsquare.gui.Navigation; import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.SystemTray; import io.bitsquare.gui.SystemTray;
import io.bitsquare.gui.ViewLoader; import io.bitsquare.gui.ViewLoader;
@ -47,7 +48,6 @@ import javafx.stage.Stage;
import org.springframework.core.env.Environment; import org.springframework.core.env.Environment;
import static com.google.common.base.Preconditions.checkNotNull;
import static io.bitsquare.app.BitsquareEnvironment.*; import static io.bitsquare.app.BitsquareEnvironment.*;
public class BitsquareApp extends Application { public class BitsquareApp extends Application {
@ -62,9 +62,9 @@ public class BitsquareApp extends Application {
@Override @Override
public void start(Stage primaryStage) throws IOException { public void start(Stage primaryStage) throws IOException {
checkNotNull(env, "Environment must not be null");
bitsquareAppModule = new BitsquareAppModule(env, primaryStage); bitsquareAppModule = new BitsquareAppModule(env, primaryStage);
injector = Guice.createInjector(bitsquareAppModule); injector = Guice.createInjector(bitsquareAppModule);
injector.getInstance(GuiceControllerFactory.class).setInjector(injector);
// route uncaught exceptions to a user-facing dialog // route uncaught exceptions to a user-facing dialog
@ -93,11 +93,10 @@ public class BitsquareApp extends Application {
// load the main view and create the main scene // load the main view and create the main scene
ViewLoader.setInjector(injector); ViewLoader viewLoader = injector.getInstance(ViewLoader.class);
ViewLoader loader = new ViewLoader(Navigation.Item.MAIN, false); ViewLoader.Item loaded = viewLoader.load(Navigation.Item.MAIN.getFxmlUrl(), false);
Parent view = loader.load();
Scene scene = new Scene(view, 1000, 600); Scene scene = new Scene((Parent) loaded.view, 1000, 600);
scene.getStylesheets().setAll( scene.getStylesheets().setAll(
"/io/bitsquare/gui/bitsquare.css", "/io/bitsquare/gui/bitsquare.css",
"/io/bitsquare/gui/images.css"); "/io/bitsquare/gui/images.css");

View file

@ -45,6 +45,9 @@ public class GuiModule extends BitsquareModule {
@Override @Override
protected void configure() { protected void configure() {
bind(GuiceControllerFactory.class).asEagerSingleton();
bind(ViewLoader.class).asEagerSingleton();
bind(OfferBook.class).asEagerSingleton(); bind(OfferBook.class).asEagerSingleton();
bind(Navigation.class).asEagerSingleton(); bind(Navigation.class).asEagerSingleton();
bind(OverlayManager.class).asEagerSingleton(); bind(OverlayManager.class).asEagerSingleton();

View file

@ -0,0 +1,47 @@
/*
* This file is part of Bitsquare.
*
* Bitsquare is free software: you can redistribute it and/or modify it
* under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation, either version 3 of the License, or (at
* your option) any later version.
*
* Bitsquare is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public
* License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with Bitsquare. If not, see <http://www.gnu.org/licenses/>.
*/
package io.bitsquare.gui;
import com.google.common.base.Preconditions;
import com.google.inject.Injector;
import javafx.util.Callback;
/**
* A JavaFX controller factory for constructing controllers via Guice DI. To
* install this in the {@link javafx.fxml.FXMLLoader}, pass it as a parameter to
* {@link javafx.fxml.FXMLLoader#setControllerFactory(javafx.util.Callback)}.
* <p>
* Once set, make sure you do <b>not</b> use the static methods on
* {@link javafx.fxml.FXMLLoader} when creating your JavaFX node.
*/
public class GuiceControllerFactory implements Callback<Class<?>, Object> {
private Injector injector;
public void setInjector(Injector injector) {
this.injector = injector;
}
@Override
public Object call(Class<?> aClass) {
Preconditions.checkNotNull(injector, "Injector has not yet been provided");
return injector.getInstance(aClass);
}
}

View file

@ -17,10 +17,13 @@
package io.bitsquare.gui; package io.bitsquare.gui;
import io.bitsquare.BitsquareException;
import io.bitsquare.persistence.Persistence; import io.bitsquare.persistence.Persistence;
import com.google.inject.Inject; import com.google.inject.Inject;
import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
@ -137,16 +140,12 @@ public class Navigation {
void onNavigationRequested(Item... items); void onNavigationRequested(Item... items);
} }
public interface FxmlResource {
String getFxmlUrl();
}
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Enum // Enum
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
public static enum Item implements FxmlResource { public static enum Item {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Application // Application
@ -219,7 +218,10 @@ public class Navigation {
ARBITRATOR_PROFILE("/io/bitsquare/gui/main/account/arbitrator/profile/ArbitratorProfileView.fxml"), ARBITRATOR_PROFILE("/io/bitsquare/gui/main/account/arbitrator/profile/ArbitratorProfileView.fxml"),
ARBITRATOR_BROWSER("/io/bitsquare/gui/main/account/arbitrator/browser/ArbitratorBrowserView.fxml"), ARBITRATOR_BROWSER("/io/bitsquare/gui/main/account/arbitrator/browser/ArbitratorBrowserView.fxml"),
ARBITRATOR_REGISTRATION( ARBITRATOR_REGISTRATION(
"/io/bitsquare/gui/main/account/arbitrator/registration/ArbitratorRegistrationView.fxml"); "/io/bitsquare/gui/main/account/arbitrator/registration/ArbitratorRegistrationView.fxml"),
// for testing, does not actually exist
BOGUS("/io/bitsquare/BogusView.fxml");
private final String displayName; private final String displayName;
@ -234,9 +236,11 @@ public class Navigation {
this.fxmlUrl = fxmlUrl; this.fxmlUrl = fxmlUrl;
} }
@Override public URL getFxmlUrl() {
public String getFxmlUrl() { URL url = Navigation.class.getResource(fxmlUrl);
return fxmlUrl; if (url == null)
throw new BitsquareException("'%s' could not be loaded as a resource", fxmlUrl);
return url;
} }
public String getDisplayName() { public String getDisplayName() {

View file

@ -39,7 +39,7 @@ public class ViewCB<T extends PresentationModel> implements Initializable {
public static final String TITLE_KEY = "view.title"; public static final String TITLE_KEY = "view.title";
protected T presentationModel; protected T presentationModel;
protected ViewCB<? extends PresentationModel> childController; protected Initializable childController;
protected ViewCB<? extends PresentationModel> parent; protected ViewCB<? extends PresentationModel> parent;
@FXML protected Parent root; @FXML protected Parent root;

View file

@ -20,8 +20,6 @@ package io.bitsquare.gui;
import io.bitsquare.BitsquareException; import io.bitsquare.BitsquareException;
import io.bitsquare.locale.BSResources; import io.bitsquare.locale.BSResources;
import com.google.inject.Injector;
import java.io.IOException; import java.io.IOException;
import java.net.URL; import java.net.URL;
@ -29,105 +27,58 @@ import java.net.URL;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import javafx.fxml.FXMLLoader; import javax.inject.Inject;
import javafx.util.Callback;
import org.slf4j.Logger; import javafx.fxml.FXMLLoader;
import org.slf4j.LoggerFactory; import javafx.fxml.Initializable;
import javafx.fxml.JavaFXBuilderFactory;
import javafx.scene.*;
import javafx.util.BuilderFactory;
/** /**
* Guice support for fxml controllers * Guice support for fxml controllers
* Support caching to speed up switches between UI screens. * Support caching to speed up switches between UI screens.
*/ */
public class ViewLoader { public class ViewLoader {
private static final Logger log = LoggerFactory.getLogger(ViewLoader.class);
private static Injector injector = null;
private FXMLLoader loader;
private final boolean isCached;
private final URL url;
private Item item;
public static void setInjector(Injector injector) { private final Map<URL, Item> cache = new HashMap<>();
ViewLoader.injector = injector; private final BuilderFactory builderFactory = new JavaFXBuilderFactory();
private final GuiceControllerFactory controllerFactory;
@Inject
public ViewLoader(GuiceControllerFactory controllerFactory) {
this.controllerFactory = controllerFactory;
} }
// TODO maybe add more sophisticated caching strategy with removal of rarely accessed items public Item load(URL url) {
private static final Map<URL, Item> cachedGUIItems = new HashMap<>(); return load(url, true);
public ViewLoader(Navigation.FxmlResource navItem, boolean useCaching) {
this.url = ViewLoader.class.getResource(navItem.getFxmlUrl());
if (this.url == null) {
throw new BitsquareException("'%s' could not be loaded as a resource", navItem.getFxmlUrl());
} }
isCached = useCaching && cachedGUIItems.containsKey(url); public Item load(URL url, boolean useCaching) {
if (!isCached) { Item item;
loader = new FXMLLoader(url, BSResources.getResourceBundle());
if (ViewLoader.injector != null) if (useCaching && cache.containsKey(url)) {
loader.setControllerFactory(new GuiceControllerFactory(ViewLoader.injector)); return cache.get(url);
}
} }
public ViewLoader(Navigation.FxmlResource navItem) { FXMLLoader loader = new FXMLLoader(url, BSResources.getResourceBundle(), builderFactory, controllerFactory);
this(navItem, true);
}
@SuppressWarnings("unchecked")
public <T> T load() {
if (isCached) {
item = cachedGUIItems.get(url);
log.debug("loaded from cache " + url);
return (T) cachedGUIItems.get(url).view;
}
log.debug("load from disc " + url);
try { try {
T result = loader.load(); item = new Item(loader.load(), loader.getController());
item = new Item(result, loader.getController()); cache.put(url, item);
cachedGUIItems.put(url, item); return item;
return result;
} catch (IOException e) { } catch (IOException e) {
throw new BitsquareException(e, "Failed to load view at %s", url); throw new BitsquareException(e, "Failed to load view at %s", url);
} }
} }
@SuppressWarnings("unchecked") public static class Item {
public <T> T getController() { public final Node view;
return (T) item.controller; public final Initializable controller;
}
Item(Node view, Initializable controller) {
class Item<T> {
final T view;
final T controller;
Item(T view, T controller) {
this.view = view; this.view = view;
this.controller = controller; this.controller = controller;
} }
} }
} }
/**
* A JavaFX controller factory for constructing controllers via Guice DI. To
* install this in the {@link javafx.fxml.FXMLLoader}, pass it as a parameter to
* {@link javafx.fxml.FXMLLoader#setControllerFactory(javafx.util.Callback)}.
* <p>
* Once set, make sure you do <b>not</b> use the static methods on
* {@link javafx.fxml.FXMLLoader} when creating your JavaFX node.
*/
class GuiceControllerFactory implements Callback<Class<?>, Object> {
private final Injector injector;
public GuiceControllerFactory(Injector injector) {
this.injector = injector;
}
@Override
public Object call(Class<?> aClass) {
return injector.getInstance(aClass);
}
}

View file

@ -62,6 +62,7 @@ public class MainViewCB extends ViewCB<MainPM> {
setBottomAnchor(this, 25d); setBottomAnchor(this, 25d);
}}; }};
private final ViewLoader viewLoader;
private final Navigation navigation; private final Navigation navigation;
private final OverlayManager overlayManager; private final OverlayManager overlayManager;
private final Transitions transitions; private final Transitions transitions;
@ -69,11 +70,11 @@ public class MainViewCB extends ViewCB<MainPM> {
private final String title; private final String title;
@Inject @Inject
public MainViewCB(MainPM presentationModel, Navigation navigation, OverlayManager overlayManager, public MainViewCB(MainPM presentationModel, ViewLoader viewLoader, Navigation navigation,
TradeManager tradeManager, Transitions transitions, BitcoinNetwork bitcoinNetwork, OverlayManager overlayManager, TradeManager tradeManager, Transitions transitions,
@Named(TITLE_KEY) String title) { BitcoinNetwork bitcoinNetwork, @Named(TITLE_KEY) String title) {
super(presentationModel); super(presentationModel);
this.viewLoader = viewLoader;
this.navigation = navigation; this.navigation = navigation;
this.overlayManager = overlayManager; this.overlayManager = overlayManager;
this.transitions = transitions; this.transitions = transitions;
@ -152,11 +153,12 @@ public class MainViewCB extends ViewCB<MainPM> {
} }
private void loadSelectedNavigation(Navigation.Item selected) { private void loadSelectedNavigation(Navigation.Item selected) {
ViewLoader loader = new ViewLoader(selected);
contentContainer.getChildren().setAll(loader.<Node>load()); ViewLoader.Item loaded = viewLoader.load(selected.getFxmlUrl());
childController = loader.getController(); contentContainer.getChildren().setAll(loaded.view);
if (childController != null) childController = loaded.controller;
childController.setParent(this); if (childController instanceof ViewCB)
((ViewCB) childController).setParent(this);
navButtons.getToggles().stream() navButtons.getToggles().stream()
.filter(toggle -> toggle instanceof ToggleButton) .filter(toggle -> toggle instanceof ToggleButton)

View file

@ -31,7 +31,6 @@ import javax.inject.Inject;
import javafx.beans.value.ChangeListener; import javafx.beans.value.ChangeListener;
import javafx.fxml.FXML; import javafx.fxml.FXML;
import javafx.fxml.Initializable; import javafx.fxml.Initializable;
import javafx.scene.*;
import javafx.scene.control.*; import javafx.scene.control.*;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -41,22 +40,23 @@ public class AccountViewCB extends CachedViewCB<AccountPM> {
private static final Logger log = LoggerFactory.getLogger(AccountViewCB.class); private static final Logger log = LoggerFactory.getLogger(AccountViewCB.class);
private final Navigation navigation;
private Navigation.Listener navigationListener; private Navigation.Listener navigationListener;
private ChangeListener<Tab> tabChangeListener; private ChangeListener<Tab> tabChangeListener;
@FXML Tab accountSettingsTab, arbitratorSettingsTab; @FXML Tab accountSettingsTab, arbitratorSettingsTab;
private final ViewLoader viewLoader;
private final Navigation navigation;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
private AccountViewCB(AccountPM presentationModel, Navigation navigation) { private AccountViewCB(AccountPM presentationModel, ViewLoader viewLoader, Navigation navigation) {
super(presentationModel); super(presentationModel);
this.viewLoader = viewLoader;
this.navigation = navigation; this.navigation = navigation;
} }
@ -134,9 +134,8 @@ public class AccountViewCB extends CachedViewCB<AccountPM> {
protected Initializable loadView(Navigation.Item navigationItem) { protected Initializable loadView(Navigation.Item navigationItem) {
super.loadView(navigationItem); super.loadView(navigationItem);
final ViewLoader loader = new ViewLoader(navigationItem); ViewLoader.Item loaded = viewLoader.load(navigationItem.getFxmlUrl());
Node view = loader.load(); final Tab tab;
Tab tab = null;
switch (navigationItem) { switch (navigationItem) {
case ACCOUNT_SETTINGS: case ACCOUNT_SETTINGS:
tab = accountSettingsTab; tab = accountSettingsTab;
@ -151,14 +150,16 @@ public class AccountViewCB extends CachedViewCB<AccountPM> {
case ARBITRATOR_SETTINGS: case ARBITRATOR_SETTINGS:
tab = arbitratorSettingsTab; tab = arbitratorSettingsTab;
break; break;
default:
throw new IllegalArgumentException("navigation item of type " + navigationItem + " is not allowed");
} }
// for IRC demo we deactivate the arbitratorSettingsTab // for IRC demo we deactivate the arbitratorSettingsTab
arbitratorSettingsTab.setDisable(true); arbitratorSettingsTab.setDisable(true);
tab.setContent(view); tab.setContent(loaded.view);
((TabPane) root).getSelectionModel().select(tab); ((TabPane) root).getSelectionModel().select(tab);
Initializable childController = loader.getController(); Initializable childController = loaded.controller;
((ViewCB) childController).setParent(this); ((ViewCB) childController).setParent(this);
return childController; return childController;

View file

@ -34,14 +34,10 @@ import javafx.scene.*;
import javafx.stage.Modality; import javafx.stage.Modality;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// TODO Arbitration is very basic yet // TODO Arbitration is very basic yet
public class ArbitratorSettingsViewCB extends CachedViewCB { public class ArbitratorSettingsViewCB extends CachedViewCB {
private static final Logger log = LoggerFactory.getLogger(ArbitratorSettingsViewCB.class); private final ViewLoader viewLoader;
private final Navigation navigation; private final Navigation navigation;
private final Stage primaryStage; private final Stage primaryStage;
@ -53,8 +49,9 @@ public class ArbitratorSettingsViewCB extends CachedViewCB {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
private ArbitratorSettingsViewCB(Navigation navigation, Stage primaryStage) { private ArbitratorSettingsViewCB(ViewLoader viewLoader, Navigation navigation, Stage primaryStage) {
super(); super();
this.viewLoader = viewLoader;
this.navigation = navigation; this.navigation = navigation;
this.primaryStage = primaryStage; this.primaryStage = primaryStage;
} }
@ -95,11 +92,8 @@ public class ArbitratorSettingsViewCB extends CachedViewCB {
@Override @Override
protected Initializable loadView(Navigation.Item navigationItem) { protected Initializable loadView(Navigation.Item navigationItem) {
// don't use caching here, cause exc. -> need to investigate and is rarely called so no caching is better ViewLoader.Item loaded = viewLoader.load(navigationItem.getFxmlUrl(), false);
final ViewLoader loader = new ViewLoader(navigationItem, false); arbitratorRegistrationViewCB = (ArbitratorRegistrationViewCB) loaded.controller;
final Parent view = loader.load();
arbitratorRegistrationViewCB = loader.getController();
final Stage stage = new Stage(); final Stage stage = new Stage();
stage.setTitle("Arbitrator"); stage.setTitle("Arbitrator");
@ -111,7 +105,7 @@ public class ArbitratorSettingsViewCB extends CachedViewCB {
stage.setY(primaryStage.getY() + 50); stage.setY(primaryStage.getY() + 50);
stage.initModality(Modality.WINDOW_MODAL); stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(primaryStage); stage.initOwner(primaryStage);
Scene scene = new Scene(view, 800, 600); Scene scene = new Scene((Parent) loaded.view, 800, 600);
stage.setScene(scene); stage.setScene(scene);
stage.show(); stage.show();

View file

@ -44,18 +44,16 @@ import javafx.scene.control.*;
import javafx.scene.layout.*; import javafx.scene.layout.*;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// TODO Arbitration is very basic yet // TODO Arbitration is very basic yet
public class ArbitratorBrowserViewCB extends CachedViewCB implements ArbitratorListener { public class ArbitratorBrowserViewCB extends CachedViewCB implements ArbitratorListener {
private static final Logger log = LoggerFactory.getLogger(ArbitratorBrowserViewCB.class);
private final ViewLoader viewLoader;
private final AccountSettings accountSettings; private final AccountSettings accountSettings;
private final Persistence persistence; private final Persistence persistence;
private final MessageService messageService; private final MessageService messageService;
private final List<Arbitrator> allArbitrators = new ArrayList<>(); private final List<Arbitrator> allArbitrators = new ArrayList<>();
private Arbitrator currentArbitrator; private Arbitrator currentArbitrator;
private ArbitratorProfileViewCB arbitratorProfileViewCB; private ArbitratorProfileViewCB arbitratorProfileViewCB;
private int index = -1; private int index = -1;
@ -63,13 +61,15 @@ public class ArbitratorBrowserViewCB extends CachedViewCB implements ArbitratorL
@FXML Button prevButton, nextButton, selectButton, closeButton; @FXML Button prevButton, nextButton, selectButton, closeButton;
@FXML Pane arbitratorProfile; @FXML Pane arbitratorProfile;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
public ArbitratorBrowserViewCB(AccountSettings accountSettings, Persistence persistence, public ArbitratorBrowserViewCB(ViewLoader viewLoader, AccountSettings accountSettings, Persistence persistence,
MessageService messageService) { MessageService messageService) {
this.viewLoader = viewLoader;
this.accountSettings = accountSettings; this.accountSettings = accountSettings;
this.persistence = persistence; this.persistence = persistence;
this.messageService = messageService; this.messageService = messageService;
@ -139,10 +139,9 @@ public class ArbitratorBrowserViewCB extends CachedViewCB implements ArbitratorL
protected Initializable loadView(Navigation.Item navigationItem) { protected Initializable loadView(Navigation.Item navigationItem) {
super.loadView(navigationItem); super.loadView(navigationItem);
final ViewLoader loader = new ViewLoader(navigationItem); ViewLoader.Item loaded = viewLoader.load(navigationItem.getFxmlUrl());
Node view = loader.load(); ((Pane) root).getChildren().set(0, loaded.view);
((Pane) root).getChildren().set(0, view); Initializable childController = arbitratorProfileViewCB = (ArbitratorProfileViewCB) loaded.controller;
Initializable childController = arbitratorProfileViewCB = loader.getController();
((ViewCB) childController).setParent(this); ((ViewCB) childController).setParent(this);
return childController; return childController;

View file

@ -53,7 +53,6 @@ import org.slf4j.LoggerFactory;
public class RestrictionsViewCB extends CachedViewCB<RestrictionsPM> implements ContextAware { public class RestrictionsViewCB extends CachedViewCB<RestrictionsPM> implements ContextAware {
private static final Logger log = LoggerFactory.getLogger(RestrictionsViewCB.class); private static final Logger log = LoggerFactory.getLogger(RestrictionsViewCB.class);
private final Stage primaryStage;
@FXML ListView<Locale> languagesListView; @FXML ListView<Locale> languagesListView;
@FXML ListView<Country> countriesListView; @FXML ListView<Country> countriesListView;
@ -63,14 +62,18 @@ public class RestrictionsViewCB extends CachedViewCB<RestrictionsPM> implements
@FXML ComboBox<Country> countryComboBox; @FXML ComboBox<Country> countryComboBox;
@FXML Button completedButton, addAllEuroCountriesButton; @FXML Button completedButton, addAllEuroCountriesButton;
private final ViewLoader viewLoader;
private final Stage primaryStage;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
private RestrictionsViewCB(RestrictionsPM presentationModel, Stage primaryStage) { private RestrictionsViewCB(RestrictionsPM presentationModel, ViewLoader viewLoader, Stage primaryStage) {
super(presentationModel); super(presentationModel);
this.viewLoader = viewLoader;
this.primaryStage = primaryStage; this.primaryStage = primaryStage;
} }
@ -187,12 +190,7 @@ public class RestrictionsViewCB extends CachedViewCB<RestrictionsPM> implements
@Override @Override
protected Initializable loadView(Navigation.Item navigationItem) { protected Initializable loadView(Navigation.Item navigationItem) {
// TODO caching causes exception ViewLoader.Item loaded = viewLoader.load(navigationItem.getFxmlUrl(), false);
final ViewLoader loader = new ViewLoader(navigationItem, false);
final Node view = loader.load();
//TODO Resolve type problem...
Initializable childController = loader.getController();
//childController.setParentController(this);
final Stage stage = new Stage(); final Stage stage = new Stage();
stage.setTitle("Arbitrator selection"); stage.setTitle("Arbitrator selection");
@ -204,7 +202,7 @@ public class RestrictionsViewCB extends CachedViewCB<RestrictionsPM> implements
stage.setY(primaryStage.getY() + 50); stage.setY(primaryStage.getY() + 50);
stage.initModality(Modality.WINDOW_MODAL); stage.initModality(Modality.WINDOW_MODAL);
stage.initOwner(primaryStage); stage.initOwner(primaryStage);
Scene scene = new Scene((Parent) view, 800, 600); Scene scene = new Scene((Parent) loaded.view, 800, 600);
stage.setScene(scene); stage.setScene(scene);
stage.setOnHidden(windowEvent -> { stage.setOnHidden(windowEvent -> {
if (navigationItem == Navigation.Item.ARBITRATOR_BROWSER) if (navigationItem == Navigation.Item.ARBITRATOR_BROWSER)
@ -212,7 +210,7 @@ public class RestrictionsViewCB extends CachedViewCB<RestrictionsPM> implements
}); });
stage.show(); stage.show();
return childController; return loaded.controller;
} }

View file

@ -48,8 +48,10 @@ public class AccountSettingsViewCB extends CachedViewCB {
private static final Logger log = LoggerFactory.getLogger(AccountSettingsViewCB.class); private static final Logger log = LoggerFactory.getLogger(AccountSettingsViewCB.class);
private MenuItem seedWords, password, restrictions, fiatAccount, registration; private final ViewLoader viewLoader;
private final Navigation navigation; private final Navigation navigation;
private MenuItem seedWords, password, restrictions, fiatAccount, registration;
private Navigation.Listener listener; private Navigation.Listener listener;
@FXML private VBox leftVBox; @FXML private VBox leftVBox;
@ -61,9 +63,9 @@ public class AccountSettingsViewCB extends CachedViewCB {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
private AccountSettingsViewCB(Navigation navigation) { private AccountSettingsViewCB(ViewLoader viewLoader, Navigation navigation) {
super(); super();
this.viewLoader = viewLoader;
this.navigation = navigation; this.navigation = navigation;
} }
@ -146,10 +148,9 @@ public class AccountSettingsViewCB extends CachedViewCB {
@Override @Override
protected Initializable loadView(Navigation.Item navigationItem) { protected Initializable loadView(Navigation.Item navigationItem) {
final ViewLoader loader = new ViewLoader(navigationItem); ViewLoader.Item loaded = viewLoader.load(navigationItem.getFxmlUrl());
final Pane view = loader.load(); content.getChildren().setAll(loaded.view);
content.getChildren().setAll(view); childController = loaded.controller;
childController = loader.getController();
((ViewCB<? extends PresentationModel>) childController).setParent(this); ((ViewCB<? extends PresentationModel>) childController).setParent(this);
((ContextAware) childController).useSettingsContext(true); ((ContextAware) childController).useSettingsContext(true);
return childController; return childController;

View file

@ -53,20 +53,23 @@ public class AccountSetupViewCB extends ViewCB implements MultiStepNavigation {
private static final Logger log = LoggerFactory.getLogger(AccountSetupViewCB.class); private static final Logger log = LoggerFactory.getLogger(AccountSetupViewCB.class);
private WizardItem seedWords, password, fiatAccount, restrictions, registration; private WizardItem seedWords, password, fiatAccount, restrictions, registration;
private final Navigation navigation;
private Navigation.Listener listener; private Navigation.Listener listener;
@FXML VBox leftVBox; @FXML VBox leftVBox;
@FXML AnchorPane content; @FXML AnchorPane content;
private final ViewLoader viewLoader;
private final Navigation navigation;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
private AccountSetupViewCB(Navigation navigation) { private AccountSetupViewCB(ViewLoader viewLoader, Navigation navigation) {
super(); super();
this.viewLoader = viewLoader;
this.navigation = navigation; this.navigation = navigation;
} }
@ -186,10 +189,9 @@ public class AccountSetupViewCB extends ViewCB implements MultiStepNavigation {
@Override @Override
protected Initializable loadView(Navigation.Item navigationItem) { protected Initializable loadView(Navigation.Item navigationItem) {
final ViewLoader loader = new ViewLoader(navigationItem); ViewLoader.Item loaded = viewLoader.load(navigationItem.getFxmlUrl());
final Pane view = loader.load(); content.getChildren().setAll(loaded.view);
content.getChildren().setAll(view); childController = loaded.controller;
childController = loader.getController();
((ViewCB<? extends PresentationModel>) childController).setParent(this); ((ViewCB<? extends PresentationModel>) childController).setParent(this);
((ContextAware) childController).useSettingsContext(false); ((ContextAware) childController).useSettingsContext(false);
return childController; return childController;

View file

@ -34,13 +34,7 @@ import javafx.fxml.Initializable;
import javafx.scene.*; import javafx.scene.*;
import javafx.scene.control.*; import javafx.scene.control.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class FundsViewCB extends CachedViewCB { public class FundsViewCB extends CachedViewCB {
private static final Logger log = LoggerFactory.getLogger(FundsViewCB.class);
private final Navigation navigation;
private Navigation.Listener navigationListener; private Navigation.Listener navigationListener;
private ChangeListener<Tab> tabChangeListener; private ChangeListener<Tab> tabChangeListener;
@ -48,15 +42,18 @@ public class FundsViewCB extends CachedViewCB {
@FXML Tab withdrawalTab, transactionsTab; @FXML Tab withdrawalTab, transactionsTab;
private final ViewLoader viewLoader;
private final Navigation navigation;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
FundsViewCB(Navigation navigation) { FundsViewCB(ViewLoader viewLoader, Navigation navigation) {
super(); super();
this.viewLoader = viewLoader;
this.navigation = navigation; this.navigation = navigation;
} }
@ -117,8 +114,7 @@ public class FundsViewCB extends CachedViewCB {
if (currentTab != null) if (currentTab != null)
currentTab.setContent(null); currentTab.setContent(null);
final ViewLoader loader = new ViewLoader(navigationItem); ViewLoader.Item loaded = viewLoader.load(navigationItem.getFxmlUrl());
Node view = loader.load();
switch (navigationItem) { switch (navigationItem) {
case WITHDRAWAL: case WITHDRAWAL:
currentTab = withdrawalTab; currentTab = withdrawalTab;
@ -127,9 +123,9 @@ public class FundsViewCB extends CachedViewCB {
currentTab = transactionsTab; currentTab = transactionsTab;
break; break;
} }
currentTab.setContent(view); currentTab.setContent(loaded.view);
((TabPane) root).getSelectionModel().select(currentTab); ((TabPane) root).getSelectionModel().select(currentTab);
Initializable childController = loader.getController(); Initializable childController = loaded.controller;
((ViewCB) childController).setParent(this); ((ViewCB) childController).setParent(this);
return childController; return childController;

View file

@ -35,14 +35,7 @@ import javafx.fxml.Initializable;
import javafx.scene.*; import javafx.scene.*;
import javafx.scene.control.*; import javafx.scene.control.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PortfolioViewCB extends CachedViewCB { public class PortfolioViewCB extends CachedViewCB {
private static final Logger log = LoggerFactory.getLogger(PortfolioViewCB.class);
private final Navigation navigation;
private final TradeManager tradeManager;
private Tab currentTab; private Tab currentTab;
private Navigation.Listener navigationListener; private Navigation.Listener navigationListener;
@ -50,15 +43,19 @@ public class PortfolioViewCB extends CachedViewCB {
@FXML Tab offersTab, openTradesTab, closedTradesTab; @FXML Tab offersTab, openTradesTab, closedTradesTab;
private final ViewLoader viewLoader;
private final Navigation navigation;
private final TradeManager tradeManager;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
PortfolioViewCB(Navigation navigation, TradeManager tradeManager) { PortfolioViewCB(ViewLoader viewLoader, Navigation navigation, TradeManager tradeManager) {
super(); super();
this.viewLoader = viewLoader;
this.navigation = navigation; this.navigation = navigation;
this.tradeManager = tradeManager; this.tradeManager = tradeManager;
} }
@ -130,8 +127,7 @@ public class PortfolioViewCB extends CachedViewCB {
if (currentTab != null) if (currentTab != null)
currentTab.setContent(null); currentTab.setContent(null);
final ViewLoader loader = new ViewLoader(navigationItem); ViewLoader.Item loaded = viewLoader.load(navigationItem.getFxmlUrl());
Node view = loader.load();
switch (navigationItem) { switch (navigationItem) {
case OFFERS: case OFFERS:
currentTab = offersTab; currentTab = offersTab;
@ -143,9 +139,9 @@ public class PortfolioViewCB extends CachedViewCB {
currentTab = closedTradesTab; currentTab = closedTradesTab;
break; break;
} }
currentTab.setContent(view); currentTab.setContent(loaded.view);
((TabPane) root).getSelectionModel().select(currentTab); ((TabPane) root).getSelectionModel().select(currentTab);
Initializable childController = loader.getController(); Initializable childController = loaded.controller;
((ViewCB) childController).setParent(this); ((ViewCB) childController).setParent(this);
return childController; return childController;

View file

@ -35,12 +35,9 @@ import javafx.fxml.Initializable;
import javafx.scene.*; import javafx.scene.*;
import javafx.scene.control.*; import javafx.scene.control.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class SettingsViewCB extends CachedViewCB { public class SettingsViewCB extends CachedViewCB {
private static final Logger log = LoggerFactory.getLogger(SettingsViewCB.class);
private final ViewLoader viewLoader;
private final Navigation navigation; private final Navigation navigation;
private Preferences preferences; private Preferences preferences;
@ -55,9 +52,9 @@ public class SettingsViewCB extends CachedViewCB {
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
@Inject @Inject
SettingsViewCB(Navigation navigation, Preferences preferences) { SettingsViewCB(ViewLoader viewLoader, Navigation navigation, Preferences preferences) {
super(); super();
this.viewLoader = viewLoader;
this.navigation = navigation; this.navigation = navigation;
this.preferences = preferences; this.preferences = preferences;
} }
@ -121,9 +118,8 @@ public class SettingsViewCB extends CachedViewCB {
protected Initializable loadView(Navigation.Item navigationItem) { protected Initializable loadView(Navigation.Item navigationItem) {
super.loadView(navigationItem); super.loadView(navigationItem);
final ViewLoader loader = new ViewLoader(navigationItem); ViewLoader.Item loaded = viewLoader.load(navigationItem.getFxmlUrl());
Parent view = loader.load(); final Tab tab;
Tab tab = null;
switch (navigationItem) { switch (navigationItem) {
case PREFERENCES: case PREFERENCES:
tab = preferencesTab; tab = preferencesTab;
@ -131,10 +127,12 @@ public class SettingsViewCB extends CachedViewCB {
case NETWORK_SETTINGS: case NETWORK_SETTINGS:
tab = networkSettingsTab; tab = networkSettingsTab;
break; break;
default:
throw new IllegalArgumentException("navigation item of type " + navigationItem + " is not allowed");
} }
tab.setContent(view); tab.setContent(loaded.view);
((TabPane) root).getSelectionModel().select(tab); ((TabPane) root).getSelectionModel().select(tab);
Initializable childController = loader.getController(); Initializable childController = loaded.controller;
if (childController instanceof ViewCB) if (childController instanceof ViewCB)
((ViewCB) childController).setParent(this); ((ViewCB) childController).setParent(this);

View file

@ -18,14 +18,15 @@
package io.bitsquare.gui.main.trade; package io.bitsquare.gui.main.trade;
import io.bitsquare.gui.Navigation; import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.ViewLoader;
import javax.inject.Inject; import javax.inject.Inject;
public class BuyViewCB extends TradeViewCB { public class BuyViewCB extends TradeViewCB {
@Inject @Inject
public BuyViewCB(Navigation navigation) { public BuyViewCB(ViewLoader viewLoader, Navigation navigation) {
super(navigation); super(viewLoader, navigation);
} }
} }

View file

@ -18,14 +18,15 @@
package io.bitsquare.gui.main.trade; package io.bitsquare.gui.main.trade;
import io.bitsquare.gui.Navigation; import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.ViewLoader;
import javax.inject.Inject; import javax.inject.Inject;
public class SellViewCB extends TradeViewCB { public class SellViewCB extends TradeViewCB {
@Inject @Inject
public SellViewCB(Navigation navigation) { public SellViewCB(ViewLoader viewLoader, Navigation navigation) {
super(navigation); super(viewLoader, navigation);
} }
} }

View file

@ -47,13 +47,11 @@ import org.slf4j.LoggerFactory;
public class TradeViewCB extends CachedViewCB implements TradeNavigator { public class TradeViewCB extends CachedViewCB implements TradeNavigator {
private static final Logger log = LoggerFactory.getLogger(TradeViewCB.class); private static final Logger log = LoggerFactory.getLogger(TradeViewCB.class);
// private final OfferBookInfo offerBookInfo = new OfferBookInfo();
private OfferBookViewCB offerBookViewCB; private OfferBookViewCB offerBookViewCB;
private CreateOfferViewCB createOfferViewCB; private CreateOfferViewCB createOfferViewCB;
private TakeOfferViewCB takeOfferViewCB; private TakeOfferViewCB takeOfferViewCB;
private Node createOfferView; private Node createOfferView;
private Node takeOfferView; private Node takeOfferView;
private final Navigation navigation;
private Navigation.Listener listener; private Navigation.Listener listener;
private Navigation.Item navigationItem; private Navigation.Item navigationItem;
private Direction direction; private Direction direction;
@ -61,14 +59,17 @@ public class TradeViewCB extends CachedViewCB implements TradeNavigator {
private Fiat price; private Fiat price;
private Offer offer; private Offer offer;
private final ViewLoader viewLoader;
private final Navigation navigation;
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
// Constructor // Constructor
/////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////
protected TradeViewCB(Navigation navigation) { protected TradeViewCB(ViewLoader viewLoader, Navigation navigation) {
super(); super();
this.viewLoader = viewLoader;
this.navigation = navigation; this.navigation = navigation;
} }
@ -166,13 +167,12 @@ public class TradeViewCB extends CachedViewCB implements TradeNavigator {
TabPane tabPane = (TabPane) root; TabPane tabPane = (TabPane) root;
if (navigationItem == Navigation.Item.OFFER_BOOK && offerBookViewCB == null) { if (navigationItem == Navigation.Item.OFFER_BOOK && offerBookViewCB == null) {
// Offerbook must not be cached by ViewLoader as we use 2 instances for sell and buy screens. // Offerbook must not be cached by ViewLoader as we use 2 instances for sell and buy screens.
ViewLoader offerBookLoader = new ViewLoader(navigationItem, false); ViewLoader.Item loaded = viewLoader.load(navigationItem.getFxmlUrl(), false);
final Parent view = offerBookLoader.load();
final Tab tab = new Tab(direction == Direction.BUY ? "Buy Bitcoin" : "Sell Bitcoin"); final Tab tab = new Tab(direction == Direction.BUY ? "Buy Bitcoin" : "Sell Bitcoin");
tab.setClosable(false); tab.setClosable(false);
tab.setContent(view); tab.setContent(loaded.view);
tabPane.getTabs().add(tab); tabPane.getTabs().add(tab);
offerBookViewCB = offerBookLoader.getController(); offerBookViewCB = (OfferBookViewCB) loaded.controller;
offerBookViewCB.setParent(this); offerBookViewCB.setParent(this);
offerBookViewCB.setDirection(direction); offerBookViewCB.setDirection(direction);
@ -183,9 +183,9 @@ public class TradeViewCB extends CachedViewCB implements TradeNavigator {
else if (navigationItem == Navigation.Item.CREATE_OFFER && createOfferViewCB == null) { else if (navigationItem == Navigation.Item.CREATE_OFFER && createOfferViewCB == null) {
// CreateOffer and TakeOffer must not be cached by ViewLoader as we cannot use a view multiple times // CreateOffer and TakeOffer must not be cached by ViewLoader as we cannot use a view multiple times
// in different graphs // in different graphs
final ViewLoader loader = new ViewLoader(navigationItem, false); ViewLoader.Item loaded = viewLoader.load(navigationItem.getFxmlUrl(), false);
createOfferView = loader.load(); createOfferView = loaded.view;
createOfferViewCB = loader.getController(); createOfferViewCB = (CreateOfferViewCB) loaded.controller;
createOfferViewCB.setParent(this); createOfferViewCB.setParent(this);
createOfferViewCB.initWithData(direction, amount, price); createOfferViewCB.initWithData(direction, amount, price);
final Tab tab = new Tab("Create offer"); final Tab tab = new Tab("Create offer");
@ -199,9 +199,9 @@ public class TradeViewCB extends CachedViewCB implements TradeNavigator {
offer != null) { offer != null) {
// CreateOffer and TakeOffer must not be cached by ViewLoader as we cannot use a view multiple times // CreateOffer and TakeOffer must not be cached by ViewLoader as we cannot use a view multiple times
// in different graphs // in different graphs
ViewLoader loader = new ViewLoader(Navigation.Item.TAKE_OFFER, false); ViewLoader.Item loaded = viewLoader.load(Navigation.Item.TAKE_OFFER.getFxmlUrl(), false);
takeOfferView = loader.load(); takeOfferView = loaded.view;
takeOfferViewCB = loader.getController(); takeOfferViewCB = (TakeOfferViewCB) loaded.controller;
takeOfferViewCB.setParent(this); takeOfferViewCB.setParent(this);
takeOfferViewCB.initWithData(direction, amount, offer); takeOfferViewCB.initWithData(direction, amount, offer);
final Tab tab = new Tab("Take offer"); final Tab tab = new Tab("Take offer");

View file

@ -19,16 +19,19 @@ package io.bitsquare.app.gui;
import io.bitsquare.BitsquareException; import io.bitsquare.BitsquareException;
import io.bitsquare.app.BitsquareEnvironment; import io.bitsquare.app.BitsquareEnvironment;
import io.bitsquare.gui.GuiceControllerFactory;
import io.bitsquare.gui.Navigation; import io.bitsquare.gui.Navigation;
import io.bitsquare.gui.ViewLoader; import io.bitsquare.gui.ViewLoader;
import com.google.inject.Guice; import com.google.inject.Guice;
import com.google.inject.Injector; import com.google.inject.Injector;
import java.net.MalformedURLException;
import java.net.URL;
import javafx.application.Application; import javafx.application.Application;
import javafx.stage.Stage; import javafx.stage.Stage;
import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.BeforeClass; import org.junit.BeforeClass;
import org.junit.Test; import org.junit.Test;
@ -59,27 +62,24 @@ public class ViewLoaderTests {
Thread.sleep(10); Thread.sleep(10);
} }
private GuiceControllerFactory controllerFactory;
@Before @Before
public void setUp() { public void setUp() {
OptionParser parser = new OptionParser(); OptionParser parser = new OptionParser();
BitsquareEnvironment env = new BitsquareEnvironment(parser.parse(new String[]{})); BitsquareEnvironment env = new BitsquareEnvironment(parser.parse(new String[]{}));
Injector injector = Guice.createInjector(new BitsquareAppModule(env, TestApp.primaryStage)); Injector injector = Guice.createInjector(new BitsquareAppModule(env, TestApp.primaryStage));
ViewLoader.setInjector(injector); controllerFactory = injector.getInstance(GuiceControllerFactory.class);
} controllerFactory.setInjector(injector);
@After
public void tearDown() {
ViewLoader.setInjector(null);
} }
@Test(expected = BitsquareException.class) @Test(expected = BitsquareException.class)
public void loadingBogusFxmlResourceShouldThrow() { public void loadingBogusFxmlResourceShouldThrow() throws MalformedURLException {
new ViewLoader(() -> "a bogus fxml resource", false).load(); new ViewLoader(controllerFactory).load(Navigation.Item.BOGUS.getFxmlUrl(), false);
} }
@Test @Test
public void loadingValidFxmlResourceShouldNotThrow() { public void loadingValidFxmlResourceShouldNotThrow() {
new ViewLoader(Navigation.Item.ACCOUNT, false).load(); new ViewLoader(controllerFactory).load(Navigation.Item.ACCOUNT.getFxmlUrl(), false);
} }
} }