Browse Source

use table instead of list

Armin Schrenk 2 tháng trước cách đây
mục cha
commit
ceec4beedd

+ 19 - 1
src/main/java/org/cryptomator/ui/decryptname/CipherAndCleartext.java

@@ -1,7 +1,25 @@
 package org.cryptomator.ui.decryptname;
 
+import javafx.beans.property.ReadOnlyStringWrapper;
+import javafx.beans.value.ObservableValue;
 import java.nio.file.Path;
 
-record CipherAndCleartext(Path ciphertext, String cleartextName) {
+public record CipherAndCleartext(Path ciphertext, String cleartextName) {
+
+	public String getCiphertextFilename() {
+		return ciphertext.getFileName().toString();
+	}
+
+	public ObservableValue<String> ciphertextFilenameProperty() {
+		return new ReadOnlyStringWrapper(getCiphertextFilename());
+	}
+
+	public String getCleartextName() {
+		return cleartextName;
+	}
+
+	public ObservableValue<String> cleartextNameProperty() {
+		return new ReadOnlyStringWrapper(getCleartextName());
+	}
 
 }

+ 0 - 94
src/main/java/org/cryptomator/ui/decryptname/CipherAndCleartextCellController.java

@@ -1,94 +0,0 @@
-package org.cryptomator.ui.decryptname;
-
-import org.cryptomator.common.ObservableUtil;
-import org.cryptomator.ui.common.FxController;
-import org.cryptomator.ui.controls.FontAwesome5Icon;
-import org.jetbrains.annotations.NotNull;
-
-import javax.inject.Inject;
-import javafx.beans.binding.Bindings;
-import javafx.beans.binding.StringBinding;
-import javafx.beans.property.ObjectProperty;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.beans.value.ObservableStringValue;
-import javafx.beans.value.ObservableValue;
-import javafx.fxml.FXML;
-import javafx.scene.control.Tooltip;
-import javafx.scene.layout.HBox;
-import java.util.ResourceBundle;
-
-public class CipherAndCleartextCellController implements FxController {
-
-	private final ObjectProperty<CipherAndCleartext> item = new SimpleObjectProperty<>();
-	private final ObservableValue<String> ciphertext;
-	private final ObservableValue<String> cleartext;
-	private final ObjectProperty<FontAwesome5Icon> icon = new SimpleObjectProperty<>();
-	private final ResourceBundle resourceBundle;
-
-	@FXML
-	public HBox root;
-
-	@Inject
-	public CipherAndCleartextCellController(ResourceBundle resourceBundle) {
-		this.resourceBundle = resourceBundle;
-		this.ciphertext = ObservableUtil.mapWithDefault(item, i -> i.ciphertext().getFileName().toString(), "");
-		this.cleartext = ObservableUtil.mapWithDefault(item, CipherAndCleartext::cleartextName, "");
-	}
-
-	@FXML
-	public void initialize() {
-		icon.bind(Bindings.createObjectBinding(this::selectIcon, root.hoverProperty()));
-	}
-
-	private String selectText() {
-		var cipherAndClear = item.get();
-		if (cipherAndClear != null) {
-			if (root.isHover()) {
-				return cipherAndClear.cleartextName();
-			} else {
-				return cipherAndClear.ciphertext().getFileName().toString();
-			}
-		}
-		return "";
-	}
-
-	private FontAwesome5Icon selectIcon() {
-		if (root.isHover()) {
-			return FontAwesome5Icon.LOCK_OPEN;
-		} else {
-			return FontAwesome5Icon.LOCK;
-		}
-	}
-
-	public void setCipherAndCleartextEntry(@NotNull CipherAndCleartext item) {
-		this.item.set(item);
-		var tooltip = new Tooltip("Click to copy");
-		Tooltip.install(root,tooltip);
-	}
-
-	//observability getter
-	public ObservableValue<String> ciphertextProperty() {
-		return ciphertext;
-	}
-
-	public String getCiphertext() {
-		return ciphertext.getValue();
-	}
-
-	public ObservableValue<String> cleartextProperty() {
-		return cleartext;
-	}
-
-	public String getCleartext() {
-		return cleartext.getValue();
-	}
-
-	public ObservableValue<FontAwesome5Icon> iconProperty() {
-		return icon;
-	}
-
-	public FontAwesome5Icon getIcon() {
-		return icon.getValue();
-	}
-
-}

+ 0 - 64
src/main/java/org/cryptomator/ui/decryptname/CipherAndCleartextCellFactory.java

@@ -1,64 +0,0 @@
-package org.cryptomator.ui.decryptname;
-
-import jakarta.inject.Inject;
-import org.cryptomator.ui.common.FxmlLoaderFactory;
-
-import javafx.fxml.FXMLLoader;
-import javafx.scene.Parent;
-import javafx.scene.control.ContentDisplay;
-import javafx.scene.control.ListCell;
-import javafx.scene.control.ListView;
-import javafx.util.Callback;
-import java.io.IOException;
-import java.io.UncheckedIOException;
-
-@DecryptNameScoped
-public class CipherAndCleartextCellFactory implements Callback<ListView<CipherAndCleartext>, ListCell<CipherAndCleartext>> {
-
-	private static final String FXML_PATH = "/fxml/decryptnames_cipherandcleartextcell.fxml";
-	private final FxmlLoaderFactory fxmlLoaders;
-
-	@Inject
-	public CipherAndCleartextCellFactory(@DecryptNameWindow FxmlLoaderFactory fxmlLoaders) {
-		this.fxmlLoaders = fxmlLoaders;
-	}
-
-
-	@Override
-	public ListCell<CipherAndCleartext> call(ListView<CipherAndCleartext> cipherAndCleartextListView) {
-		try {
-			FXMLLoader fxmlLoader = fxmlLoaders.load(FXML_PATH);
-			return new Cell(fxmlLoader.getRoot(), fxmlLoader.getController());
-		} catch (IOException e) {
-			throw new UncheckedIOException("Failed to load %s.".formatted(FXML_PATH), e);
-		}
-	}
-
-	private static class Cell extends ListCell<CipherAndCleartext> {
-
-		private final Parent root;
-		private final CipherAndCleartextCellController controller;
-
-		public Cell(Parent root, CipherAndCleartextCellController controller) {
-			this.root = root;
-			this.controller = controller;
-		}
-
-		@Override
-		protected void updateItem(CipherAndCleartext item, boolean empty) {
-			super.updateItem(item, empty);
-
-			if (empty || item == null) {
-				setGraphic(null);
-				this.getStyleClass().remove("test-list-cell");
-				setVisible(false);
-			} else {
-				this.getStyleClass().addLast("test-list-cell");
-				setContentDisplay(ContentDisplay.GRAPHIC_ONLY);
-				setGraphic(root);
-				controller.setCipherAndCleartextEntry(item);
-				setVisible(true);
-			}
-		}
-	}
-}

+ 42 - 20
src/main/java/org/cryptomator/ui/decryptname/DecryptFileNamesViewController.java

@@ -1,5 +1,6 @@
 package org.cryptomator.ui.decryptname;
 
+import org.apache.commons.lang3.SystemUtils;
 import org.cryptomator.common.vaults.Vault;
 import org.cryptomator.cryptofs.common.Constants;
 import org.cryptomator.ui.common.FxController;
@@ -18,7 +19,10 @@ import javafx.beans.property.StringProperty;
 import javafx.beans.value.ObservableValue;
 import javafx.collections.FXCollections;
 import javafx.fxml.FXML;
-import javafx.scene.control.ListView;
+import javafx.scene.control.TableColumn;
+import javafx.scene.control.TableView;
+import javafx.scene.control.cell.PropertyValueFactory;
+import javafx.scene.input.TransferMode;
 import javafx.stage.FileChooser;
 import javafx.stage.Stage;
 import java.io.File;
@@ -39,18 +43,20 @@ public class DecryptFileNamesViewController implements FxController {
 	private final BooleanProperty wrongFilesSelected = new SimpleBooleanProperty(false);
 	private final Stage window;
 	private final Vault vault;
-	private final CipherAndCleartextCellFactory cellFactory;
 	private final ResourceBundle resourceBundle;
 	private final List<Path> initialList;
 
 	@FXML
-	public ListView<CipherAndCleartext> decryptedNamesView;
+	public TableColumn<CipherAndCleartext, String> ciphertextColumn;
+	@FXML
+	public TableColumn<CipherAndCleartext, String> cleartextColumn;
+	@FXML
+	public TableView<CipherAndCleartext> cipherToCleartextTable;
 
 	@Inject
-	public DecryptFileNamesViewController(@DecryptNameWindow Stage window, @DecryptNameWindow Vault vault, @DecryptNameWindow List<Path> pathsToDecrypt, CipherAndCleartextCellFactory cellFactory, ResourceBundle resourceBundle) {
+	public DecryptFileNamesViewController(@DecryptNameWindow Stage window, @DecryptNameWindow Vault vault, @DecryptNameWindow List<Path> pathsToDecrypt, ResourceBundle resourceBundle) {
 		this.window = window;
 		this.vault = vault;
-		this.cellFactory = cellFactory;
 		this.resourceBundle = resourceBundle;
 		this.mapping = new SimpleListProperty<>(FXCollections.observableArrayList());
 		this.initialList = pathsToDecrypt;
@@ -58,15 +64,39 @@ public class DecryptFileNamesViewController implements FxController {
 
 	@FXML
 	public void initialize() {
-		decryptedNamesView.setItems(mapping);
-		decryptedNamesView.setCellFactory(cellFactory);
+		cipherToCleartextTable.setItems(mapping);
+		cipherToCleartextTable.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS);
+		cipherToCleartextTable.getSelectionModel().setCellSelectionEnabled(true);
+		cipherToCleartextTable.setOnDragEntered(event -> {
+			if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
+				cipherToCleartextTable.setItems(FXCollections.emptyObservableList());
+			}
+		});
+		cipherToCleartextTable.setOnDragOver(event -> {
+			if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
+				if (SystemUtils.IS_OS_WINDOWS || SystemUtils.IS_OS_MAC) {
+					event.acceptTransferModes(TransferMode.LINK);
+				} else {
+					event.acceptTransferModes(TransferMode.ANY);
+				}
+			}
+		});
+		cipherToCleartextTable.setOnDragDropped(event -> {
+			if (event.getGestureSource() == null && event.getDragboard().hasFiles()) {
+				checkAndDecrypt(event.getDragboard().getFiles().stream().map(File::toPath).toList());
+				cipherToCleartextTable.setItems(mapping);
+			}
+		});
+		cipherToCleartextTable.setOnDragExited(_ -> cipherToCleartextTable.setItems(mapping));
+		ciphertextColumn.setCellValueFactory(new PropertyValueFactory<>("ciphertextFilename"));
+		cleartextColumn.setCellValueFactory(new PropertyValueFactory<>("cleartextName"));
 
 		dropZoneText.setValue("Drop files or click to select");
 		dropZoneIcon.setValue(FontAwesome5Icon.FILE_IMPORT);
 
 		wrongFilesSelected.addListener((_, _, areWrongFiles) -> {
 			if (areWrongFiles) {
-				CompletableFuture.delayedExecutor(3, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
+				CompletableFuture.delayedExecutor(5, TimeUnit.SECONDS, Platform::runLater).execute(() -> {
 					//dropZoneText.setValue(resourceBundle.getString(".."));
 					dropZoneText.setValue("Drop files or click to select");
 					dropZoneIcon.setValue(FontAwesome5Icon.FILE_IMPORT);
@@ -92,25 +122,25 @@ public class DecryptFileNamesViewController implements FxController {
 	}
 
 	private void checkAndDecrypt(List<Path> pathsToDecrypt) {
+		mapping.clear();
 		//Assumption: All files are in the same directory
 		var testPath = pathsToDecrypt.getFirst();
 		if (!testPath.startsWith(vault.getPath())) {
-			setDropZoneError("Selected files do not belong the the vault");
+			setDropZoneError("Selected files do not belong vault %s".formatted(vault.getDisplayName()));
 			return;
 		}
 		if (pathsToDecrypt.size() == 1 && testPath.endsWith(Constants.DIR_ID_BACKUP_FILE_NAME)) {
-			setDropZoneError("%s is a vault internal file with no encrypted filename".formatted(Constants.DIR_ID_BACKUP_FILE_NAME));
+			setDropZoneError("Vault internal files with no decrypt-able name selected");
 			return;
 		}
 
 		try {
 			var newMapping = pathsToDecrypt.stream().filter(p -> !p.endsWith(Constants.DIR_ID_BACKUP_FILE_NAME)).map(this::getCleartextName).toList();
-			mapping.clear();
 			mapping.addAll(newMapping);
 		} catch (UncheckedIOException e) {
 			setDropZoneError("Failed to read selected files");
 		} catch (IllegalArgumentException e) {
-			setDropZoneError("Names of selected files are not encrypted".formatted(Constants.DIR_ID_BACKUP_FILE_NAME));
+			setDropZoneError("Vault internal files with no decrypt-able name selected");
 		}
 	}
 
@@ -147,12 +177,4 @@ public class DecryptFileNamesViewController implements FxController {
 		return dropZoneIcon.get();
 	}
 
-	public ObservableValue<Boolean> decryptedPathsListEmptyProperty() {
-		return mapping.emptyProperty();
-	}
-
-	public boolean isDecryptedPathsListEmpty() {
-		return mapping.isEmpty();
-	}
-
 }

+ 0 - 4
src/main/java/org/cryptomator/ui/decryptname/DecryptNameModule.java

@@ -57,8 +57,4 @@ public abstract class DecryptNameModule {
 	@FxControllerKey(DecryptFileNamesViewController.class)
 	abstract FxController bindDecryptNamesViewController(DecryptFileNamesViewController controller);
 
-	@Binds
-	@IntoMap
-	@FxControllerKey(CipherAndCleartextCellController.class)
-	abstract FxController binCipherAndCleartextCellController(CipherAndCleartextCellController controller);
 }

+ 252 - 30
src/main/resources/css/light_theme.css

@@ -86,6 +86,22 @@
 	-fx-background-color: MAIN_BG;
 	-fx-text-fill: TEXT_FILL;
 	-fx-font-family: 'Open Sans';
+	-fx-control-inner-background: CONTROL_BG_NORMAL;
+	-fx-focus-color: PRIMARY;
+	-fx-accent: PRIMARY;
+	-fx-border: CONTROL_BORDER_NORMAL;
+	-fx-box-border: CONTROL_BORDER_NORMAL;
+	-fx-base: MAIN_BG;
+	-fx-table-header-border-color: GRAY_5;
+    -fx-table-cell-border-color: GRAY_6;
+	-fx-cell-focus-inner-border: GRAY_9;
+	-fx-selection-bar: PRIMARY_L1;
+	-fx-selection-bar-text: TEXT_FILL;
+	-fx-cell-hover-color: CONTROL_BG_HOVER;
+	-fx-text-inner-color: TEXT_FILL;
+	-fx-body-color: GRAY_7;
+	-fx-mark-highlight-color: PRIMARY;
+	-fx-mark-color: BLACK;
 }
 
 /*******************************************************************************
@@ -980,51 +996,257 @@
 	-fx-background-radius: 4px;
 }
 
-.test-style2 {
-	/*-fx-background-color: rgba(88,94,98,0.7), rgba(53,57,59,0.7) ;*/
-	-fx-background-color: CONTROL_BG_NORMAL;
-	-fx-background-insets: 1px;
-	-fx-background-radius: 4px;
-	-fx-border-width: 4px;
-	-fx-border-style: dashed inside;
-	-fx-border-color: CONTROL_BORDER_NORMAL;
-	-fx-border-radius: 4px;
+/*******************************************************************************
+ *                                                                             *
+ * TableView                                                                   *
+ *                                                                             *
+ ******************************************************************************/
+
+.table-view {
+    -fx-background-color: -fx-box-border, -fx-control-inner-background;
+    -fx-background-insets: 0,1;
+
+    /* There is some oddness if padding is in em values rather than pixels,
+       in particular, the left border of the control doesn't show. */
+    -fx-padding: 1; /* 0.083333em; */
 }
 
-.test-style2:hover {
-	/*-fx-background-color: rgba(88,94,98,0.7), rgba(53,57,59,0.7) ;*/
-	-fx-background-color: CONTROL_BG_HOVER;
+/** Draws focus border around tableview */
+.table-view:focused {
+    -fx-background-color: CONTROL_BORDER_FOCUSED,-fx-control-inner-background;
+    -fx-background-insets: 0, 1;
+    -fx-background-radius: 0, 0;
+
+    /* There is some oddness if padding is in em values rather than pixels,
+      in particular, the left border of the control doesn't show. */
+    -fx-padding: 1; /* 0.083333em; */
 }
 
-.test-list-view {
-	-fx-background-color: CONTROL_BG_NORMAL;
+.table-view > .virtual-flow > .scroll-bar:vertical {
+    -fx-background-insets: 0, 0 0 0 1;
+    -fx-padding: -1 -1 -1 0;
 }
 
-.test-list-view:focused .test-list-cell:selected {
-	-fx-background-color: PRIMARY, CONTROL_BG_SELECTED;
-	-fx-background-insets: 0, 0 0 0 3px;
+.table-view > .virtual-flow > .corner {
+    -fx-background-color: -fx-box-border, -fx-base;
+    -fx-background-insets: 0, 1 0 0 1;
 }
-.test-list-view .test-list-cell:hover {
-	-fx-background-color: CONTROL_BG_SELECTED;
+
+/* Each row in the table is a table-row-cell. Inside a table-row-cell is any
+   number of table-cell. */
+.table-row-cell {
+    -fx-background-color: -fx-table-cell-border-color, -fx-control-inner-background;
+    -fx-background-insets: 0, 0 0 1 0;
+    -fx-padding: 0.0em; /* 0 */
+    -fx-text-fill: -fx-text-inner-color;
 }
 
-.test-list-cell:selected {
-	-fx-background-color: CONTROL_BG_SELECTED;
+.table-row-cell:odd {
+    -fx-background-color: -fx-table-cell-border-color, GRAY_9;
+    -fx-background-insets: 0, 0 0 1 0;
 }
 
-.test-list-cell .glyph-icon {
-	-fx-fill: TEXT_FILL_MUTED;
+.table-row-cell:focused {
+    -fx-background-color: -fx-focus-color, -fx-cell-focus-inner-border, -fx-control-inner-background;
+    -fx-background-insets: 0, 1, 2;
 }
 
-.test-list-cell .text {
-	-fx-fill: TEXT_FILL;
-	-fx-font-size: 1.0em;
+.table-row-cell:focused:odd {
+    -fx-background-color: -fx-focus-color, -fx-cell-focus-inner-border, GRAY_9;
+    -fx-background-insets: 0, 1, 2;
 }
 
-.test-list-cell:selected .glyph-icon {
-	-fx-fill: PRIMARY;
+/* When the table-row-cell is selected and focused */
+.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:focused:selected {
+    -fx-background-color: -fx-focus-color, -fx-cell-focus-inner-border, -fx-selection-bar;
+    -fx-background-insets: 0, 1, 2;
+    -fx-background: -fx-accent;
+    -fx-text-fill: -fx-selection-bar-text;
+}
+
+.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected > .table-cell {
+    -fx-text-fill: -fx-selection-bar-text;
+}
+
+.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:selected {
+    -fx-background: -fx-accent;
+    -fx-background-color: -fx-selection-bar;
+    -fx-text-fill: -fx-selection-bar-text;
+}
+
+.table-view:row-selection:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:focused:selected:hover {
+    -fx-background: -fx-accent;
+    -fx-background-color: -fx-focus-color, -fx-cell-focus-inner-border, -fx-selection-bar;
+    -fx-background-insets: 0, 1, 2;
+    -fx-text-fill: -fx-selection-bar-text;
+}
+
+/* When the TableView is _not_ focused, we show alternate selection colors */
+.table-row-cell:filled:selected:focused,
+.table-row-cell:filled:selected,
+    -fx-background-color: lightgray; /* TODO: */
+    -fx-text-fill: -fx-selection-bar-text;
+}
+
+.table-view:row-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:hover {
+    -fx-background-color: -fx-table-cell-border-color, -fx-cell-hover-color;
+    -fx-background-insets: 0, 0 0 1 0;
+    -fx-text-fill: -fx-text-inner-color;
+}
+
+.table-view:row-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled:focused:hover {
+    -fx-background-color: -fx-table-cell-border-color, -fx-focus-color, -fx-cell-focus-inner-border, -fx-cell-hover-color;
+    -fx-background-insets: 0, 0 0 1 0, 1 1 2 1, 2 2 3 2, 3 3 4 3;
+    -fx-text-fill: -fx-text-inner-color;
+}
+
+.table-cell {
+    -fx-padding: 0.166667em; /* 2px, plus border adds 1px */
+    -fx-background-color: transparent;
+    -fx-border-color: transparent -fx-table-cell-border-color transparent transparent;
+    -fx-border-width: 0.083333em; /* 1 */
+    -fx-cell-size: 2.0em; /* 24 */
+    -fx-text-fill: -fx-text-inner-color;
+    -fx-text-overrun: center-ellipsis;
+}
+
+/* When in constrained resize mode, the right-most visible cell should not have
+   a right-border, as it is not possible to get this cleanly out of view without
+   introducing horizontal scrollbars (see JDK-8114045). */
+.table-view:constrained-resize > .virtual-flow > .clipped-container > .sheet > .table-row-cell > .table-cell:last-visible {
+    -fx-border-color: transparent;
+}
+.table-view:constrained-resize > .column-header:last-visible {
+    -fx-border-width: 0.083333em 0.0em 0.083333em 0.083333em, 0.083333em 0.0em 0.083333em 0.083333em;
+}
+.table-view:constrained-resize .filler {
+    -fx-border-color:
+        derive(-fx-base, 80%)
+        linear-gradient(to bottom, derive(-fx-base,80%) 20%, derive(-fx-base,-10%) 90%)
+        derive(-fx-base, 10%)
+        linear-gradient(to bottom, derive(-fx-base,80%) 20%, derive(-fx-base,-10%) 90%),
+        /* Outer border: */
+        transparent -fx-table-header-border-color -fx-table-header-border-color -fx-table-header-border-color;
+    -fx-border-insets: 0 1 1 1, 0 0 0 0;
+}
+
+.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell > .table-cell:focused {
+    -fx-background-color: -fx-focus-color, -fx-cell-focus-inner-border, -fx-control-inner-background;
+    -fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
+}
+
+.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled .table-cell:focused:selected {
+    -fx-background-color: -fx-focus-color, -fx-cell-focus-inner-border, -fx-selection-bar;
+    -fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
+    -fx-background: -fx-accent;
+    -fx-text-fill: -fx-selection-bar-text;
+}
+
+.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:selected,
+.table-view:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:hover:selected {
+    -fx-background: -fx-accent;
+    -fx-background-color: -fx-selection-bar;
+    -fx-text-fill: -fx-selection-bar-text;
+    -fx-background-insets: 0 0 1 0;
+}
+
+.table-view:focused > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:selected:hover {
+    -fx-background: -fx-accent;
+    -fx-background-color: -fx-focus-color, -fx-cell-focus-inner-border, -fx-selection-bar;
+    -fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
+    -fx-text-fill: -fx-selection-bar-text;
+}
+
+/* When the TableView is _not_ focused, we show alternate selection colors */
+.table-row-cell:filled > .table-cell:selected:focused,
+.table-row-cell:filled > .table-cell:selected {
+    -fx-background-color: lightgray;
+   	-fx-text-fill: -fx-selection-bar-text;
+}
+
+
+.table-view:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:hover {
+   -fx-background-color: -fx-cell-hover-color;
+   -fx-text-fill: -fx-text-inner-color;
+   -fx-background-insets: 0 0 1 0;
+}
+
+.table-view:cell-selection > .virtual-flow > .clipped-container > .sheet > .table-row-cell:filled > .table-cell:focused:hover,
+    -fx-background-color: -fx-focus-color, -fx-cell-focus-inner-border, -fx-cell-hover-color;
+    -fx-background-insets: 0 1 0 0, 1 2 1 1, 2 3 2 2;
+    -fx-text-fill: -fx-text-inner-color;
+}
+
+/* The column-resize-line is shown when the user is attempting to resize a column. */
+.table-view .column-resize-line {
+    -fx-background: -fx-accent;
+    -fx-background-color: -fx-selection-bar;
+    -fx-padding: 0.0em 0.0416667em 0.0em 0.0416667em; /* 0 0.571429 0 0.571429 */
+}
+
+/* This is the area behind the column headers. An ideal place to specify background
+   and border colors for the whole area (not individual column-header's). */
+.table-view .column-header-background {
+    -fx-background-color: -fx-body-color;
+    -fx-padding: 0;
+}
+
+/* The column header row is made up of a number of column-header, one for each
+   TableColumn, and a 'filler' area that extends from the right-most column
+   to the edge of the tableview, or up to the 'column control' button. */
+.table-view .column-header {
+    -fx-text-fill: -fx-selection-bar-text;
+
+    /* TODO: for some reason, this doesn't scale. */
+    -fx-font-size: 1.083333em; /* 13pt ;  1 more than the default font */
+    -fx-size: 24;
+    -fx-border-style: solid;
+    -fx-border-color:
+        GRAY_7
+        GRAY_5
+        GRAY_5
+        transparent;
+    -fx-border-insets: 0 0 0 0;
+    -fx-border-width: 0.083333em;
+}
+
+.table-view .filler {
+    -fx-size: 24;
+    -fx-border-style: solid;
+    -fx-border-color:
+        GRAY_7;
+    -fx-border-insets: 0 0 0 0;
+    -fx-border-width: 0.083333em;
+}
+
+.table-view .column-header .sort-order-dots-container {
+    -fx-padding: 2 0 2 0;
+}
+
+.table-view .column-header .sort-order {
+    -fx-font-size: 0.916667em; /* 11pt - 1 less than the default font */
+}
+
+.table-view .column-header .sort-order-dot {
+    -fx-background-color: derive(-fx-mark-highlight-color, 40%), -fx-mark-color;
+    -fx-padding: 0.0625em 0.104em 0.0625em 0.104em;
+}
+
+.table-view .column-header .sort-order-dot.ascending {
+    -fx-background-insets: -1 0 1 0, 0;
+}
+
+.table-view .column-header .sort-order-dot.descending {
+    -fx-background-insets: 1 0 -1 0, 0;
+}
+
+.table-view .column-header .label {
+    -fx-alignment: center;
+	-fx-font-family: 'Fira Code';
 }
 
-.test-list-cell:selected .text {
-	-fx-fill: TEXT_FILL_HIGHLIGHTED;
+/* This is shown when the table has no rows and/or no columns. */
+.table-view .empty-table {
+    -fx-background-color: MAIN_BG;
+    -fx-font-size: 1.166667em; /* 14pt - 2 more than the default font */
 }

+ 24 - 10
src/main/resources/fxml/decryptnames.fxml

@@ -3,8 +3,10 @@
 <?import org.cryptomator.ui.controls.FontAwesome5IconView?>
 <?import javafx.geometry.Insets?>
 <?import javafx.scene.control.Label?>
-<?import javafx.scene.control.ListView?>
 <?import javafx.scene.layout.VBox?>
+<?import javafx.scene.control.TableView?>
+<?import javafx.scene.control.TableColumn?>
+<?import javafx.scene.control.Button?>
 <VBox xmlns="http://javafx.com/javafx"
 	  xmlns:fx="http://javafx.com/fxml"
 	  fx:controller="org.cryptomator.ui.decryptname.DecryptFileNamesViewController"
@@ -15,13 +17,25 @@
 		<Insets topRightBottomLeft="24"/>
 	</padding>
 	<Label text="Decrypt File Name" styleClass="label-large"/>
-	<VBox alignment="CENTER" VBox.vgrow="ALWAYS" styleClass="test-style2" visible="${controller.decryptedPathsListEmpty}" managed="${controller.decryptedPathsListEmpty}" onMouseClicked="#selectFiles">
-		<!-- Drop or click to decrypt names of files -->
-		<Label text="${controller.dropZoneText}" contentDisplay="TOP">
-			<graphic>
-				<FontAwesome5IconView glyph="${controller.dropZoneIcon}" glyphSize="16"/>
-			</graphic>
-		</Label>
-	</VBox>
-	<ListView fx:id="decryptedNamesView" styleClass="test-list-view" VBox.vgrow="ALWAYS" visible="${!controller.decryptedPathsListEmpty}" managed="${!controller.decryptedPathsListEmpty}"/>
+	<TableView fx:id="cipherToCleartextTable">
+		<placeholder>
+			<Button alignment="CENTER" onAction="#selectFiles" text="${controller.dropZoneText}" contentDisplay="TOP"  maxWidth="Infinity" maxHeight="Infinity">
+				<graphic>
+					<FontAwesome5IconView glyph="${controller.dropZoneIcon}" glyphSize="16"/>
+				</graphic>
+			</Button>
+		</placeholder>
+		<columns>
+			<TableColumn fx:id="ciphertextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}">
+				<graphic>
+					<FontAwesome5IconView glyph="LOCK"/>
+				</graphic>
+			</TableColumn>
+			<TableColumn fx:id="cleartextColumn" prefWidth="${cipherToCleartextTable.width * 0.5}">
+				<graphic>
+					<FontAwesome5IconView glyph="LOCK_OPEN"/>
+				</graphic>
+			</TableColumn>
+		</columns>
+	</TableView>
 </VBox>

+ 0 - 23
src/main/resources/fxml/decryptnames_cipherandcleartextcell.fxml

@@ -1,23 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<?import javafx.scene.layout.HBox?>
-<?import org.cryptomator.ui.controls.FontAwesome5IconView?>
-<?import javafx.scene.text.Text?>
-<?import javafx.geometry.Insets?>
-<?import javafx.scene.control.Label?>
-<?import javafx.scene.layout.StackPane?>
-<HBox xmlns="http://javafx.com/javafx"
-	  xmlns:fx="http://javafx.com/fxml"
-	  fx:controller="org.cryptomator.ui.decryptname.CipherAndCleartextCellController"
-	  spacing="6"
-	  fx:id="root"
-		>
-	<padding>
-		<Insets topRightBottomLeft="6"/>
-	</padding>
-	<FontAwesome5IconView glyph="${controller.icon}" glyphSize="16" />
-	<StackPane alignment="CENTER_LEFT">
-		<Label styleClass="label" textOverrun="CENTER_ELLIPSIS" text="${controller.ciphertext}" visible="${!root.hover}"/>
-		<Label styleClass="label" textOverrun="CENTER_ELLIPSIS" text="${controller.cleartext}" visible="${root.hover}"/>
-	</StackPane>
-</HBox>