diff --git a/designs/cluster_node_icon.svg b/designs/cluster_node_icon.svg
index 6dbe761..c07d77b 100644
--- a/designs/cluster_node_icon.svg
+++ b/designs/cluster_node_icon.svg
@@ -16,9 +16,9 @@
id="svg8"
inkscape:version="0.92.4 (5da689c313, 2019-01-14)"
sodipodi:docname="cluster_node_icon.svg"
- inkscape:export-filename="A:\Programming\GitHub-andrewlalis\CrystalKeep\src\main\resources\ui\images\cluster_node_icon.png"
- inkscape:export-xdpi="192"
- inkscape:export-ydpi="192">
+ inkscape:export-filename="A:\Programming\GitHub-andrewlalis\CrystalKeep\src\main\resources\nl\andrewlalis\crystalkeep\ui\images\cluster_node_icon.png"
+ inkscape:export-xdpi="1536"
+ inkscape:export-ydpi="1536">
+ units="px"
+ showguides="true"
+ inkscape:guide-bbox="true" />
@@ -48,7 +50,7 @@
image/svg+xml
-
+
@@ -58,21 +60,19 @@
id="layer1"
transform="translate(0,-292.76667)">
+ style="fill:#cf00f8;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
+ d="m 0.99910255,294.74397 1.11756415,-1.93568 1.1175641,1.93568 z"
+ id="path824"
+ inkscape:connector-curvature="0" />
+
+
diff --git a/pom.xml b/pom.xml
index 75c9717..f376f07 100644
--- a/pom.xml
+++ b/pom.xml
@@ -80,6 +80,11 @@
javafx-fxml
${javafx.version}
+
+ com.1stleg
+ jnativehook
+ 2.1.0
+
org.junit.jupiter
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index c469dfc..10f6a58 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -1,6 +1,7 @@
module crystalkeep {
requires javafx.fxml;
requires javafx.controls;
+ requires jnativehook;
opens nl.andrewlalis.crystalkeep.control;
opens nl.andrewlalis.crystalkeep;
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/control/ClusterTreeViewItemSelectionListener.java b/src/main/java/nl/andrewlalis/crystalkeep/control/ClusterTreeViewItemSelectionListener.java
index fdcc1d0..318716a 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/control/ClusterTreeViewItemSelectionListener.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/control/ClusterTreeViewItemSelectionListener.java
@@ -5,24 +5,10 @@ import javafx.beans.value.ObservableValue;
import javafx.scene.control.TreeItem;
import javafx.scene.layout.VBox;
import nl.andrewlalis.crystalkeep.model.CrystalItem;
-import nl.andrewlalis.crystalkeep.model.Shard;
-import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
-import nl.andrewlalis.crystalkeep.model.shards.TextShard;
import nl.andrewlalis.crystalkeep.view.ShardTreeItem;
-import nl.andrewlalis.crystalkeep.view.shard_details.LoginCredentialsViewModel;
-import nl.andrewlalis.crystalkeep.view.shard_details.ShardViewModel;
-import nl.andrewlalis.crystalkeep.view.shard_details.TextShardViewModel;
-
-import java.util.HashMap;
-import java.util.Map;
+import nl.andrewlalis.crystalkeep.view.shard_details.ViewModels;
public class ClusterTreeViewItemSelectionListener implements ChangeListener> {
- private static final Map, Class extends ShardViewModel extends Shard>>> shardPanesMap = new HashMap<>();
- static {
- shardPanesMap.put(TextShard.class, TextShardViewModel.class);
- shardPanesMap.put(LoginCredentialsShard.class, LoginCredentialsViewModel.class);
- }
-
private final VBox shardDetailContainer;
public ClusterTreeViewItemSelectionListener(VBox shardDetailContainer) {
@@ -34,13 +20,7 @@ public class ClusterTreeViewItemSelectionListener implements ChangeListener shardDetailContainer.getChildren().add(vm.getContentPane()));
}
}
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/Cluster.java b/src/main/java/nl/andrewlalis/crystalkeep/model/Cluster.java
index 02b721c..b994b57 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/Cluster.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/Cluster.java
@@ -93,9 +93,4 @@ public class Cluster implements Comparable, CrystalItem {
}
return sb.toString();
}
-
- @Override
- public String getIconPath() {
- return "/nl/andrewlalis/crystalkeep/ui/images/cluster_node_icon.png";
- }
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/CrystalItem.java b/src/main/java/nl/andrewlalis/crystalkeep/model/CrystalItem.java
index 3fcaaaa..6f4c213 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/CrystalItem.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/CrystalItem.java
@@ -6,5 +6,4 @@ package nl.andrewlalis.crystalkeep.model;
*/
public interface CrystalItem {
String getName();
- String getIconPath();
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java b/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java
index da4d004..3149a22 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/Shard.java
@@ -64,9 +64,4 @@ public abstract class Shard implements Comparable, CrystalItem {
public String toString() {
return "Shard: name=\"" + this.name + "\", type=" + this.type + ", createdAt=" + this.createdAt;
}
-
- @Override
- public String getIconPath() {
- return "/nl/andrewlalis/crystalkeep/ui/images/shard_node_icon.png";
- }
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java
index cc4ce07..7d7ba75 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/LoginCredentialsShard.java
@@ -44,11 +44,6 @@ public class LoginCredentialsShard extends Shard {
return super.toString() + ", username=\"" + this.username + "\", password=\"" + this.password + "\"";
}
- @Override
- public String getIconPath() {
- return "/nl/andrewlalis/crystalkeep/ui/images/login_credentials_shard_node_icon.png";
- }
-
public static class Serializer implements ShardSerializer {
@Override
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java
index 729d94c..328d132 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/model/shards/TextShard.java
@@ -34,11 +34,6 @@ public class TextShard extends Shard {
return super.toString() + ", text=\"" + this.text + "\"";
}
- @Override
- public String getIconPath() {
- return "/nl/andrewlalis/crystalkeep/ui/images/text_shard_node_icon.png";
- }
-
public static class Serializer implements ShardSerializer {
@Override
public byte[] serialize(TextShard shard) throws IOException {
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/util/ImageCache.java b/src/main/java/nl/andrewlalis/crystalkeep/util/ImageCache.java
new file mode 100644
index 0000000..fbe64ec
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/crystalkeep/util/ImageCache.java
@@ -0,0 +1,54 @@
+package nl.andrewlalis.crystalkeep.util;
+
+import javafx.scene.image.Image;
+
+import java.io.InputStream;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * This cache stores pre-loaded images that are used in the application, so that
+ * they only need to be loaded once.
+ */
+public class ImageCache {
+ private static final Map images = new ConcurrentHashMap<>();
+
+ public static Optional get(String path, double width, double height) {
+ ImageSpec spec = new ImageSpec(path, width, height);
+ Image img = images.get(spec);
+ if (img == null) {
+ InputStream is = ImageCache.class.getResourceAsStream(path);
+ if (is == null) return Optional.empty();
+ img = new Image(is, spec.width, spec.height, true, true);
+ images.put(spec, img);
+ }
+ return Optional.of(img);
+ }
+
+ private static class ImageSpec {
+ private final String path;
+ private final double width;
+ private final double height;
+
+ public ImageSpec(String path, double width, double height) {
+ this.path = path;
+ this.width = width;
+ this.height = height;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ ImageSpec imageSpec = (ImageSpec) o;
+ return Double.compare(imageSpec.width, width) == 0 && Double.compare(imageSpec.height, height) == 0 && path.equals(imageSpec.path);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(path, width, height);
+ }
+ }
+}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java b/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java
index dd083df..32bad78 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/CrystalItemTreeCell.java
@@ -3,7 +3,6 @@ package nl.andrewlalis.crystalkeep.view;
import javafx.scene.control.ContextMenu;
import javafx.scene.control.MenuItem;
import javafx.scene.control.TreeCell;
-import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import nl.andrewlalis.crystalkeep.control.AddClusterHandler;
import nl.andrewlalis.crystalkeep.control.AddShardHandler;
@@ -11,10 +10,13 @@ import nl.andrewlalis.crystalkeep.control.DeleteItemHandler;
import nl.andrewlalis.crystalkeep.model.Cluster;
import nl.andrewlalis.crystalkeep.model.CrystalItem;
import nl.andrewlalis.crystalkeep.model.Model;
-
-import java.io.InputStream;
+import nl.andrewlalis.crystalkeep.model.Shard;
+import nl.andrewlalis.crystalkeep.util.ImageCache;
+import nl.andrewlalis.crystalkeep.view.shard_details.ViewModels;
public class CrystalItemTreeCell extends TreeCell {
+ private static final String CLUSTER_ICON = "/nl/andrewlalis/crystalkeep/ui/images/cluster_node_icon.png";
+
private final Model model;
public CrystalItemTreeCell(Model model) {
@@ -36,19 +38,27 @@ public class CrystalItemTreeCell extends TreeCell {
addClusterItem.setOnAction(new AddClusterHandler(cluster, model));
menu.getItems().addAll(addShardItem, addClusterItem);
}
+
if (this.getTreeItem().getParent() != null && this.getTreeItem().getParent() instanceof ClusterTreeItem) {
var deleteItem = new MenuItem("Delete");
deleteItem.setOnAction(new DeleteItemHandler(this.getTreeItem(), this.model));
menu.getItems().add(deleteItem);
}
this.setText(item.getName());
- InputStream is = getClass().getResourceAsStream(item.getIconPath());
- if (is != null) {
- ImageView icon = new ImageView(new Image(is));
+
+ String iconPath = CLUSTER_ICON;
+ if (item instanceof Shard) {
+ var vm = ViewModels.get((Shard) item);
+ if (vm.isPresent()) {
+ iconPath = vm.get().getIconPath();
+ }
+ }
+ ImageCache.get(iconPath, 16, 16).ifPresent(img -> {
+ ImageView icon = new ImageView(img);
icon.setFitHeight(16);
icon.setPreserveRatio(true);
this.setGraphic(icon);
- }
+ });
this.setContextMenu(menu);
} else {
this.setText(null);
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/LoginCredentialsViewModel.java b/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/LoginCredentialsViewModel.java
index 887c0d4..8a0a97f 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/LoginCredentialsViewModel.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/LoginCredentialsViewModel.java
@@ -9,6 +9,9 @@ import javafx.scene.layout.GridPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Pane;
import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
+import org.jnativehook.GlobalScreen;
+import org.jnativehook.keyboard.NativeKeyAdapter;
+import org.jnativehook.keyboard.NativeKeyEvent;
public class LoginCredentialsViewModel extends ShardViewModel {
public LoginCredentialsViewModel(LoginCredentialsShard shard) {
@@ -61,12 +64,21 @@ public class LoginCredentialsViewModel extends ShardViewModel {
- System.out.println("Not yet implemented.");
+
+ var copyBothButton = new Button("Copy Username and Password");
+ copyBothButton.setOnAction(event -> {
+ ClipboardContent content = new ClipboardContent();
+ content.putString(shard.getUsername());
+ final Clipboard c = Clipboard.getSystemClipboard();
+ c.setContent(content);
+ var t = new Thread(() -> {
+ while (c.getString().equals(shard.getUsername())) {
+ System.out.println("User hasn't pasted yet");
+ }
+ });
});
- typePasswordButton.setDisable(true);
- passwordActionsPane.getChildren().addAll(showPasswordCheckbox, copyPasswordButton, typePasswordButton);
+
+ passwordActionsPane.getChildren().addAll(showPasswordCheckbox, copyPasswordButton);
gp.add(passwordActionsPane, 1, 2);
return gp;
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ShardViewModel.java b/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ShardViewModel.java
index 99a85e5..7c7e194 100644
--- a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ShardViewModel.java
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ShardViewModel.java
@@ -12,6 +12,12 @@ import nl.andrewlalis.crystalkeep.model.Shard;
import java.time.format.DateTimeFormatter;
+/**
+ * The view model for a type of shard. A shard type's view model defines how the
+ * shard is displayed in the application, including the actual UI contents, and
+ * icon.
+ * @param The type of shard.
+ */
public abstract class ShardViewModel {
protected final T shard;
@@ -42,4 +48,8 @@ public abstract class ShardViewModel {
}
protected abstract Node getContent(T shard);
+
+ public String getIconPath() {
+ return "/nl/andrewlalis/crystalkeep/ui/images/shard_node_icon.png";
+ }
}
diff --git a/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ViewModels.java b/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ViewModels.java
new file mode 100644
index 0000000..09f4581
--- /dev/null
+++ b/src/main/java/nl/andrewlalis/crystalkeep/view/shard_details/ViewModels.java
@@ -0,0 +1,34 @@
+package nl.andrewlalis.crystalkeep.view.shard_details;
+
+import nl.andrewlalis.crystalkeep.model.Shard;
+import nl.andrewlalis.crystalkeep.model.shards.LoginCredentialsShard;
+import nl.andrewlalis.crystalkeep.model.shards.TextShard;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * Utility class that provides the ability to look up and obtain a new view
+ * model for a particular type of shard.
+ */
+public class ViewModels {
+ private static final Map, Class>> shardViewModels = new HashMap<>();
+ static {
+ shardViewModels.put(TextShard.class, TextShardViewModel.class);
+ shardViewModels.put(LoginCredentialsShard.class, LoginCredentialsViewModel.class);
+ }
+
+ public static Optional> get(Shard shard) {
+ try {
+ Class> viewModelClass = shardViewModels.get(shard.getClass());
+ if (viewModelClass != null) {
+ var viewModel = (ShardViewModel>) viewModelClass.getDeclaredConstructor(shard.getClass()).newInstance(shard);
+ return Optional.of(viewModel);
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ return Optional.empty();
+ }
+}
diff --git a/src/main/resources/nl/andrewlalis/crystalkeep/ui/images/cluster_node_icon.png b/src/main/resources/nl/andrewlalis/crystalkeep/ui/images/cluster_node_icon.png
index 93e7c27..4d6bede 100644
Binary files a/src/main/resources/nl/andrewlalis/crystalkeep/ui/images/cluster_node_icon.png and b/src/main/resources/nl/andrewlalis/crystalkeep/ui/images/cluster_node_icon.png differ