From 33f2eeae7bf79d72368f218cc6de20afdb3c512f Mon Sep 17 00:00:00 2001 From: a2geek Date: Wed, 7 Oct 2026 14:31:14 -0500 Subject: [PATCH] Enabling drag and drop between AppleCommander windows. Internally, uses the AppleSingle format. --- .../applecommander/javafx/DiskUsageView.java | 45 +++++---- .../org/applecommander/javafx/FileView.java | 91 ++++++++++++++++++- .../javafx/settings/ExportOption.java | 22 +++-- 3 files changed, 130 insertions(+), 28 deletions(-) diff --git a/app/gui-javafx/src/main/java/org/applecommander/javafx/DiskUsageView.java b/app/gui-javafx/src/main/java/org/applecommander/javafx/DiskUsageView.java index 558d620b..003f6781 100644 --- a/app/gui-javafx/src/main/java/org/applecommander/javafx/DiskUsageView.java +++ b/app/gui-javafx/src/main/java/org/applecommander/javafx/DiskUsageView.java @@ -37,6 +37,7 @@ import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import javafx.scene.paint.Color; import javafx.scene.shape.Rectangle; +import org.applecommander.filestore.FileStore; import org.applecommander.usage.BlockUsage; import org.applecommander.usage.DiskUsage; import org.applecommander.usage.DiskUsage.UsageType; @@ -79,24 +80,14 @@ public class DiskUsageView extends StackPane { // Chart view pieChartView = new PieChart(); pieChartView.setTitle("Disk Usage"); - fileStoreWindow.fileStoreSelection().selectedItemProperty().addListener((observable, oldValue, newValue) -> { - if (newValue != null) { - Optional opt = newValue.get(DiskUsage.class); - if (opt.isEmpty()) { - // nothing to view - return; - } - DiskUsage usage = opt.get(); - // Somewhat arbitrary, but the grid view really lags in HDD's; even with the drawing deferred. - largeDisk.set(usage.getTotal() > 2000); - if (!largeDisk.get()) { - updateGridView(usage); - viewMode.set(Mode.GRID_VIEW); - } - else { - viewMode.set(Mode.CHART_VIEW); - } - updateChartView(usage); + + // Refresh on updates + fileStoreWindow.fileStoreSelection().selectedItemProperty().addListener((_, _, newFileStore) -> { + updateDisplay(newFileStore); + }); + fileStoreWindow.hasChangedProperty().addListener((_, _, hasChanged) -> { + if (hasChanged) { + updateDisplay(fileStoreWindow.fileStoreSelection().getSelectedItem()); } }); @@ -120,6 +111,24 @@ public class DiskUsageView extends StackPane { viewMode.set(Mode.GRID_VIEW); } + public void updateDisplay(FileStore fileStore) { + if (fileStore == null) return; + Optional opt = fileStore.get(DiskUsage.class); + if (opt.isEmpty()) { + return; + } + DiskUsage usage = opt.get(); + largeDisk.set(usage.getTotal() > 2000); + if (!largeDisk.get()) { + updateGridView(usage); + viewMode.set(Mode.GRID_VIEW); + } + else { + viewMode.set(Mode.CHART_VIEW); + } + updateChartView(usage); + } + public void bindScene(Scene scene) { // Function keys for view modes applyShortcutToButton(scene, usageGridToggle, "Grid View", diff --git a/app/gui-javafx/src/main/java/org/applecommander/javafx/FileView.java b/app/gui-javafx/src/main/java/org/applecommander/javafx/FileView.java index bc13aa85..8c802547 100644 --- a/app/gui-javafx/src/main/java/org/applecommander/javafx/FileView.java +++ b/app/gui-javafx/src/main/java/org/applecommander/javafx/FileView.java @@ -33,25 +33,35 @@ import javafx.scene.input.*; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.VBox; +import org.applecommander.applesingle.AppleSingle; +import org.applecommander.applesingle.FileDatesInfo; +import org.applecommander.applesingle.ProdosFileInfo; import org.applecommander.capability.Capability; import org.applecommander.filestore.*; import org.applecommander.javafx.settings.ExportOption; import org.applecommander.javafx.settings.Settings; import org.applecommander.source.Source; import org.applecommander.source.Sources; +import org.applecommander.util.DataBuffer; +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; +import java.nio.ByteBuffer; import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Optional; import static org.applecommander.javafx.FxUtils.*; public class FileView extends BorderPane { + // See https://en.wikipedia.org/wiki/AppleSingle_and_AppleDouble_formats + public static final DataFormat APPLESINGLE_MIME = new DataFormat("application/applefile"); + private final FileStoreWindow fileStoreWindow; private final HBox breadcrumbBar; private final ToggleButton nativeToolButton; @@ -157,9 +167,12 @@ public class FileView extends BorderPane { selectedDirectory.set(directoryPath.isEmpty() ? null : directoryPath.getLast()); }); - selectedDirectory.addListener((_, _, newValue) -> { + selectedDirectory.addListener((_, _, _) -> { populateDiskRows(); }); + fileStoreWindow.hasChangedProperty().addListener((_, _, _) -> { + populateDiskRows(); + }); } public void createNewFileTable() { @@ -202,6 +215,7 @@ public class FileView extends BorderPane { fileTable.setOnDragDetected(event -> { if (!fileTable.getSelectionModel().getSelectedItems().isEmpty()) { try { + // If we are dragging OUT of AppleCommander, we need to create working files: ExportOption exportOption = Settings.getExportOption(); Path tempDir = Files.createTempDirectory("AppleCommander-drag-"); tempDir.toFile().deleteOnExit(); @@ -216,6 +230,12 @@ public class FileView extends BorderPane { } ClipboardContent content = new ClipboardContent(); content.putFiles(files); + // Internal drag format is AppleSingle, and one file at a time: + AppleSingle appleSingle = ExportOption.createAppleSingle(fileTable.getSelectionModel().getSelectedItem()); + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + appleSingle.save(baos); + content.put(APPLESINGLE_MIME, ByteBuffer.wrap(baos.toByteArray())); + // Now we're ready, so allow it to proceed: Dragboard db = fileTable.startDragAndDrop(TransferMode.COPY_OR_MOVE); db.setContent(content); } catch (IOException e) { @@ -224,10 +244,79 @@ public class FileView extends BorderPane { } event.consume(); }); + fileTable.setOnDragOver(event -> { + FileStore fileStore = fileStoreWindow.fileStoreSelection().getSelectedItem(); + Directory directory = selectedDirectory.get(); + Dragboard db = event.getDragboard(); + boolean internalDrag = db.hasContent(APPLESINGLE_MIME); + boolean externalDrag = db.hasFiles(); + boolean canCreateFiles = fileStore != null && fileStore.can(Capability.CREATE_FILES) && directory instanceof WritableDirectory; + if ((internalDrag || externalDrag) && canCreateFiles) { + event.acceptTransferModes(TransferMode.COPY_OR_MOVE); + } + event.consume(); + }); + fileTable.setOnDragDropped(event -> { + Dragboard db = event.getDragboard(); + boolean success = false; + try { + if (db.hasContent(APPLESINGLE_MIME)) { + // Give preference to internal dragging. + ByteBuffer data = (ByteBuffer) db.getContent(APPLESINGLE_MIME); + AppleSingle appleSingle = AppleSingle.read(data.array()); + ProdosAttributes prodosAttributes = createProdosAttributes(appleSingle); + createFile(prodosAttributes, appleSingle.getDataFork(), appleSingle.getResourceFork()); + success = true; + } else if (db.hasFiles()) { + // From the file system. + System.out.println("DROPPED"); + System.out.println(db.getFiles()); + success = true; + } + } catch (IOException e) { + FxUtils.showErrorDialog("Unable to drop files", e); + } + event.setDropCompleted(success); + event.consume(); + }); managedProperty().bind(fileTable.visibleProperty()); } + public ProdosAttributes createProdosAttributes(AppleSingle appleSingle) { + ProdosAttributes.Builder builder = ProdosAttributes.builder().name("NEWFILE").BIN(0x0000); + if (appleSingle.getRealName() != null) { + builder.name(appleSingle.getRealName()); + } + if (appleSingle.getFileDatesInfo() != null) { + FileDatesInfo fileDatesInfo = appleSingle.getFileDatesInfo(); + builder.creation(Date.from(fileDatesInfo.getCreationInstant())); + builder.modification(Date.from(fileDatesInfo.getModificationInstant())); + } + if (appleSingle.getProdosFileInfo() != null) { + ProdosFileInfo prodosFileInfo = appleSingle.getProdosFileInfo(); + builder.auxType(prodosFileInfo.getAuxType()); + builder.fileType(prodosFileInfo.getFileType()); + builder.locked((prodosFileInfo.getAccess() & 0xe3) == 0xe3); + } + return builder.build(); + } + + public void createFile(ProdosAttributes prodosAttributes, byte[] dataFork, byte[] resourceFork) { + Directory directory = selectedDirectory.get(); + if (directory instanceof WritableDirectory writableDirectory) { + WritableFileEntry fileEntry = writableDirectory.createFrom(prodosAttributes); + fileEntry.setDataFork(DataBuffer.wrap(dataFork)); + if (resourceFork != null) { + fileEntry.setResourceFork(DataBuffer.wrap(resourceFork)); + } + fileStoreWindow.hasChangedProperty().set(true); + } + else { + throw new RuntimeException("This is not a writable directory"); + } + } + public void bindScene(Scene scene) { // Function keys for view modes applyShortcutToButton(scene, nativeToolButton, "Native View", diff --git a/app/gui-javafx/src/main/java/org/applecommander/javafx/settings/ExportOption.java b/app/gui-javafx/src/main/java/org/applecommander/javafx/settings/ExportOption.java index 8f4ce458..07e8fec7 100644 --- a/app/gui-javafx/src/main/java/org/applecommander/javafx/settings/ExportOption.java +++ b/app/gui-javafx/src/main/java/org/applecommander/javafx/settings/ExportOption.java @@ -84,20 +84,24 @@ public enum ExportOption { } } + public static AppleSingle createAppleSingle(FileEntry fileEntry) { + ProdosAttributes attributes = fileEntry.getProdosAttributes(); + AppleSingle.Builder builder = AppleSingle.builder() + .realName(fileEntry.getName()) + .access(attributes.access()) + .auxType(attributes.auxType()) + .fileType(attributes.fileType()) + .dataFork(fileEntry.getDataFork().asBytes()); + fileEntry.getResourceFork().ifPresent(resourceFork -> builder.resourceFork(resourceFork.asBytes())); + return builder.build(); + } + public static List copyToAppleSingle(Path directory, FileEntry fileEntry) { try { List paths = new ArrayList<>(); Path destination = directory.resolve(fileEntry.getName() + ".as"); paths.add(destination); - ProdosAttributes attributes = fileEntry.getProdosAttributes(); - AppleSingle.Builder builder = AppleSingle.builder() - .realName(fileEntry.getName()) - .access(attributes.access()) - .auxType(attributes.auxType()) - .fileType(attributes.fileType()) - .dataFork(fileEntry.getDataFork().asBytes()); - fileEntry.getResourceFork().ifPresent(resourceFork -> builder.resourceFork(resourceFork.asBytes())); - builder.build().save(destination); + createAppleSingle(fileEntry).save(destination); return paths; } catch (IOException ex) { throw new UncheckedIOException(ex);