Cleaned up view models, tried to get pasting to work.

This commit is contained in:
Andrew Lalis 2021-05-31 13:24:32 +02:00
parent f8b8740ea8
commit f7b54dc182
15 changed files with 164 additions and 79 deletions

View File

@ -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">
<defs
id="defs2" />
<sodipodi:namedview
@ -28,9 +28,9 @@
borderopacity="1.0"
inkscape:pageopacity="0.0"
inkscape:pageshadow="2"
inkscape:zoom="31.678384"
inkscape:cx="9.6523068"
inkscape:cy="7.5940447"
inkscape:zoom="44.8"
inkscape:cx="5.0060445"
inkscape:cy="7.4646004"
inkscape:document-units="px"
inkscape:current-layer="layer1"
showgrid="false"
@ -39,7 +39,9 @@
inkscape:window-x="-8"
inkscape:window-y="-8"
inkscape:window-maximized="1"
units="px" />
units="px"
showguides="true"
inkscape:guide-bbox="true" />
<metadata
id="metadata5">
<rdf:RDF>
@ -48,7 +50,7 @@
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
@ -58,21 +60,19 @@
id="layer1"
transform="translate(0,-292.76667)">
<path
sodipodi:type="star"
style="fill:#f072ff;fill-opacity:1;stroke:none;stroke-width:0.07803907;stroke-linecap:round;stroke-linejoin:round;stroke-miterlimit:4;stroke-dasharray:none;stroke-dashoffset:0;stroke-opacity:1;paint-order:markers fill stroke"
id="path817"
sodipodi:sides="9"
sodipodi:cx="2.1166666"
sodipodi:cy="294.93815"
sodipodi:r1="1.8178079"
sodipodi:r2="1.2179312"
sodipodi:arg1="0.52359878"
sodipodi:arg2="0.87266463"
inkscape:flatsided="false"
inkscape:rounded="0"
inkscape:randomized="0"
d="m 3.6909344,295.84704 -0.7913968,0.0241 -0.1611441,0.77519 -0.6217269,-0.49025 -0.621727,0.49025 -0.1611441,-0.77519 -0.79139678,-0.0241 0.37483979,-0.69741 -0.59076328,-0.52715 0.73543197,-0.2933 -0.11370504,-0.78356 0.75190744,0.24804 0.416557,-0.67333 0.4165569,0.67333 0.7519075,-0.24804 -0.1137051,0.78356 0.735432,0.2933 -0.5907633,0.52715 z"
inkscape:transform-center-x="0.06028767"
inkscape:transform-center-y="0.030212626" />
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" />
<path
inkscape:connector-curvature="0"
id="path826"
d="m 3.2342308,295.01546 -1.1175641,1.93568 -1.11756415,-1.93568 z"
style="fill:#cf00f8;fill-opacity:1;stroke:none;stroke-width:0.26458332px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1" />
<path
style="fill:#00f8c5;fill-opacity:1;stroke:none;stroke-width:0.14845224px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 2.1114031,294.00949 0.5073106,0.87868 -0.501731,0.86902 -0.5023631,-0.87011 z"
id="path828"
inkscape:connector-curvature="0" />
</g>
</svg>

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -80,6 +80,11 @@
<artifactId>javafx-fxml</artifactId>
<version>${javafx.version}</version>
</dependency>
<dependency>
<groupId>com.1stleg</groupId>
<artifactId>jnativehook</artifactId>
<version>2.1.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.junit.jupiter/junit-jupiter-engine -->
<dependency>
<groupId>org.junit.jupiter</groupId>

View File

@ -1,6 +1,7 @@
module crystalkeep {
requires javafx.fxml;
requires javafx.controls;
requires jnativehook;
opens nl.andrewlalis.crystalkeep.control;
opens nl.andrewlalis.crystalkeep;

View File

@ -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<TreeItem<CrystalItem>> {
private static final Map<Class<? extends Shard>, 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<Tree
shardDetailContainer.getChildren().clear();
if (newValue instanceof ShardTreeItem) {
var node = (ShardTreeItem) newValue;
var paneClass = shardPanesMap.get(node.getShard().getClass());
try {
var vm = paneClass.getDeclaredConstructor(node.getShard().getClass()).newInstance(node.getShard());
shardDetailContainer.getChildren().add(vm.getContentPane());
} catch (Exception e) {
e.printStackTrace();
}
ViewModels.get(node.getShard()).ifPresent(vm -> shardDetailContainer.getChildren().add(vm.getContentPane()));
}
}
}

View File

@ -93,9 +93,4 @@ public class Cluster implements Comparable<Cluster>, CrystalItem {
}
return sb.toString();
}
@Override
public String getIconPath() {
return "/nl/andrewlalis/crystalkeep/ui/images/cluster_node_icon.png";
}
}

View File

@ -6,5 +6,4 @@ package nl.andrewlalis.crystalkeep.model;
*/
public interface CrystalItem {
String getName();
String getIconPath();
}

View File

@ -64,9 +64,4 @@ public abstract class Shard implements Comparable<Shard>, 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";
}
}

View File

@ -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<LoginCredentialsShard> {
@Override

View File

@ -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<TextShard> {
@Override
public byte[] serialize(TextShard shard) throws IOException {

View File

@ -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<ImageSpec, Image> images = new ConcurrentHashMap<>();
public static Optional<Image> 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);
}
}
}

View File

@ -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<CrystalItem> {
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<CrystalItem> {
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);

View File

@ -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<LoginCredentialsShard> {
public LoginCredentialsViewModel(LoginCredentialsShard shard) {
@ -61,12 +64,21 @@ public class LoginCredentialsViewModel extends ShardViewModel<LoginCredentialsSh
content.putString(shard.getPassword());
Clipboard.getSystemClipboard().setContent(content);
});
var typePasswordButton = new Button("Auto Type");
typePasswordButton.setOnAction(event -> {
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;

View File

@ -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 <T> The type of shard.
*/
public abstract class ShardViewModel<T extends Shard> {
protected final T shard;
@ -42,4 +48,8 @@ public abstract class ShardViewModel<T extends Shard> {
}
protected abstract Node getContent(T shard);
public String getIconPath() {
return "/nl/andrewlalis/crystalkeep/ui/images/shard_node_icon.png";
}
}

View File

@ -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<?>, Class<?>> shardViewModels = new HashMap<>();
static {
shardViewModels.put(TextShard.class, TextShardViewModel.class);
shardViewModels.put(LoginCredentialsShard.class, LoginCredentialsViewModel.class);
}
public static Optional<ShardViewModel<?>> 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();
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 803 B

After

Width:  |  Height:  |  Size: 7.2 KiB