Enabling drag and drop between AppleCommander windows. Internally, uses the AppleSingle format.

This commit is contained in:
a2geek committed 2026-10-07 14:31:14 -05:00
1 parent 6a8db12494
commit 33f2eeae7b
3 files changed
+130 -28

No files matched your search

@@ -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<DiskUsage> 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<DiskUsage> 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",
@@ -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",
@@ -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<Path> copyToAppleSingle(Path directory, FileEntry fileEntry) {
try {
List<Path> 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);