+ * return getClass().getResource("/myView.fxml");
+ *
+ *
+ * @return FXML resource URL
+ */
+ protected abstract URL getFxmlResourceUrl();
+
+ /**
+ * @return Localization bundle for the FXML labels or null.
+ */
+ protected abstract ResourceBundle getFxmlResourceBundle();
+
+ @Override
+ public final void initialize(URL location, ResourceBundle resources) {
+ this.rootUrl = location;
+ this.resourceBundle = resources;
+ this.initialize();
+ }
+
+ protected void initialize() {
+ }
+
+ /**
+ * Creates a FXML loader used in {@link #loadFxml()}. This method can be overwritten for further loader customization.
+ *
+ * @return Configured loader ready to load.
+ */
+ protected FXMLLoader createFxmlLoader() {
+ final URL fxmlUrl = getFxmlResourceUrl();
+ final ResourceBundle rb = getFxmlResourceBundle();
+ final FXMLLoader loader = new FXMLLoader(fxmlUrl, rb);
+ loader.setController(this);
+ return loader;
+ }
+
+ /**
+ * Loads the view presented by this controller from the FXML file return by {@link #getFxmlResourceUrl()}. This method can only be invoked once.
+ *
+ * @return Parent view element.
+ */
+ protected final synchronized Parent loadFxml() {
+ if (fxmlRoot == null) {
+ final FXMLLoader loader = createFxmlLoader();
+ try {
+ fxmlRoot = loader.load();
+ } catch (IOException e) {
+ throw new IllegalStateException("Could not load FXML file from location: " + loader.getLocation(), e);
+ }
+ }
+ return fxmlRoot;
+ }
+
+ /**
+ * Creates a new scene with the root node from the FXML file and applies it to the given stage.
+ */
+ public void initStage(Stage stage) {
+ final Parent root = loadFxml();
+ stage.setScene(new Scene(root));
+ stage.sizeToScene();
+ }
+
+ /**
+ * @return Creates a new stage and calls {@link #initStage(Stage)}.
+ */
+ public Stage createStage() {
+ final Stage stage = new Stage();
+ initStage(stage);
+ return stage;
+ }
+
+}
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
index aa603a71f..97c6d8426 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/ChangePasswordController.java
@@ -10,15 +10,8 @@ import java.nio.file.StandardCopyOption;
import java.nio.file.StandardOpenOption;
import java.util.ResourceBundle;
-import javafx.application.Application;
-import javafx.application.Platform;
-import javafx.beans.value.ObservableValue;
-import javafx.event.ActionEvent;
-import javafx.fxml.FXML;
-import javafx.fxml.Initializable;
-import javafx.scene.control.Button;
-import javafx.scene.control.Hyperlink;
-import javafx.scene.text.Text;
+import javax.inject.Inject;
+import javax.inject.Singleton;
import org.cryptomator.crypto.exceptions.UnsupportedKeyLengthException;
import org.cryptomator.crypto.exceptions.UnsupportedVaultException;
@@ -28,13 +21,20 @@ import org.cryptomator.ui.model.Vault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-import com.google.inject.Inject;
+import javafx.application.Application;
+import javafx.application.Platform;
+import javafx.beans.value.ObservableValue;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.Hyperlink;
+import javafx.scene.text.Text;
-public class ChangePasswordController implements Initializable {
+@Singleton
+public class ChangePasswordController extends AbstractFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(ChangePasswordController.class);
- private ResourceBundle rb;
private ChangePasswordListener listener;
private Vault vault;
@@ -65,9 +65,17 @@ public class ChangePasswordController implements Initializable {
}
@Override
- public void initialize(URL location, ResourceBundle rb) {
- this.rb = rb;
+ protected URL getFxmlResourceUrl() {
+ return getClass().getResource("/fxml/change_password.fxml");
+ }
+ @Override
+ protected ResourceBundle getFxmlResourceBundle() {
+ return ResourceBundle.getBundle("localization");
+ }
+
+ @Override
+ public void initialize() {
oldPasswordField.textProperty().addListener(this::passwordFieldsDidChange);
newPasswordField.textProperty().addListener(this::passwordFieldsDidChange);
retypePasswordField.textProperty().addListener(this::passwordFieldsDidChange);
@@ -109,19 +117,19 @@ public class ChangePasswordController implements Initializable {
vault.getCryptor().decryptMasterKey(masterKeyInputStream, oldPassword);
Files.copy(masterKeyPath, masterKeyBackupPath, StandardCopyOption.REPLACE_EXISTING);
} catch (IOException ex) {
- messageText.setText(rb.getString("changePassword.errorMessage.decryptionFailed"));
+ messageText.setText(resourceBundle.getString("changePassword.errorMessage.decryptionFailed"));
LOG.error("Decryption failed for technical reasons.", ex);
newPasswordField.swipe();
retypePasswordField.swipe();
return;
} catch (WrongPasswordException e) {
- messageText.setText(rb.getString("changePassword.errorMessage.wrongPassword"));
+ messageText.setText(resourceBundle.getString("changePassword.errorMessage.wrongPassword"));
newPasswordField.swipe();
retypePasswordField.swipe();
Platform.runLater(oldPasswordField::requestFocus);
return;
} catch (UnsupportedKeyLengthException ex) {
- messageText.setText(rb.getString("changePassword.errorMessage.unsupportedKeyLengthInstallJCE"));
+ messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedKeyLengthInstallJCE"));
LOG.warn("Unsupported Key-Length. Please install Oracle Java Cryptography Extension (JCE).", ex);
newPasswordField.swipe();
retypePasswordField.swipe();
@@ -129,9 +137,9 @@ public class ChangePasswordController implements Initializable {
} catch (UnsupportedVaultException e) {
downloadsPageLink.setVisible(true);
if (e.isVaultOlderThanSoftware()) {
- messageText.setText(rb.getString("changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
+ messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.vaultOlderThanSoftware") + " ");
} else if (e.isSoftwareOlderThanVault()) {
- messageText.setText(rb.getString("changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
+ messageText.setText(resourceBundle.getString("changePassword.errorMessage.unsupportedVersion.softwareOlderThanVault") + " ");
}
newPasswordField.swipe();
retypePasswordField.swipe();
@@ -146,7 +154,7 @@ public class ChangePasswordController implements Initializable {
final CharSequence newPassword = newPasswordField.getCharacters();
try (final OutputStream masterKeyOutputStream = Files.newOutputStream(masterKeyPath, StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.SYNC)) {
vault.getCryptor().encryptMasterKey(masterKeyOutputStream, newPassword);
- messageText.setText(rb.getString("changePassword.infoMessage.success"));
+ messageText.setText(resourceBundle.getString("changePassword.infoMessage.success"));
Platform.runLater(this::didChangePassword);
// At this point the backup is still using the old password.
// It will be changed as soon as the user unlocks the vault the next time.
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java
index a7fa2a0a4..ff1488e1e 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/InitializeController.java
@@ -19,23 +19,25 @@ import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.ResourceBundle;
-import javafx.beans.value.ObservableValue;
-import javafx.event.ActionEvent;
-import javafx.fxml.FXML;
-import javafx.fxml.Initializable;
-import javafx.scene.control.Button;
-import javafx.scene.control.Label;
+import javax.inject.Inject;
+import javax.inject.Singleton;
import org.cryptomator.ui.controls.SecPasswordField;
import org.cryptomator.ui.model.Vault;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-public class InitializeController implements Initializable {
+import javafx.beans.value.ObservableValue;
+import javafx.event.ActionEvent;
+import javafx.fxml.FXML;
+import javafx.scene.control.Button;
+import javafx.scene.control.Label;
+
+@Singleton
+public class InitializeController extends AbstractFXMLViewController {
private static final Logger LOG = LoggerFactory.getLogger(InitializeController.class);
- private ResourceBundle localization;
private Vault vault;
private InitializationListener listener;
@@ -51,9 +53,22 @@ public class InitializeController implements Initializable {
@FXML
private Label messageLabel;
+ @Inject
+ public InitializeController() {
+ }
+
@Override
- public void initialize(URL url, ResourceBundle rb) {
- this.localization = rb;
+ protected URL getFxmlResourceUrl() {
+ return getClass().getResource("/fxml/initialize.fxml");
+ }
+
+ @Override
+ protected ResourceBundle getFxmlResourceBundle() {
+ return ResourceBundle.getBundle("localization");
+ }
+
+ @Override
+ public void initialize() {
passwordField.textProperty().addListener(this::passwordFieldsDidChange);
retypePasswordField.textProperty().addListener(this::passwordFieldsDidChange);
}
@@ -88,9 +103,9 @@ public class InitializeController implements Initializable {
listener.didInitialize(this);
}
} catch (FileAlreadyExistsException ex) {
- messageLabel.setText(localization.getString("initialize.messageLabel.alreadyInitialized"));
+ messageLabel.setText(resourceBundle.getString("initialize.messageLabel.alreadyInitialized"));
} catch (InvalidPathException ex) {
- messageLabel.setText(localization.getString("initialize.messageLabel.invalidPath"));
+ messageLabel.setText(resourceBundle.getString("initialize.messageLabel.invalidPath"));
} catch (IOException ex) {
LOG.error("I/O Exception", ex);
} finally {
diff --git a/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java b/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java
index 22fa8ffa5..39ba7cb32 100644
--- a/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java
+++ b/main/ui/src/main/java/org/cryptomator/ui/controllers/MacWarningsController.java
@@ -4,6 +4,10 @@ import java.net.URL;
import java.util.ResourceBundle;
import java.util.stream.Collectors;
+import javax.inject.Inject;
+
+import org.cryptomator.ui.model.Vault;
+
import javafx.application.Application;
import javafx.beans.Observable;
import javafx.beans.property.BooleanProperty;
@@ -18,18 +22,13 @@ import javafx.collections.ListChangeListener.Change;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
-import javafx.fxml.Initializable;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.cell.CheckBoxListCell;
import javafx.stage.Stage;
import javafx.util.StringConverter;
-import javax.inject.Inject;
-
-import org.cryptomator.ui.model.Vault;
-
-public class MacWarningsController implements Initializable {
+public class MacWarningsController extends AbstractFXMLViewController {
@FXML
private ListView
* Tries to bring open-close symmetry in contexts where the resource outlives
@@ -57,8 +59,10 @@ public class DeferredCloser implements AutoCloseable {
private static final Logger LOG = LoggerFactory.getLogger(MainController.class);
+ @VisibleForTesting
final Map