Tony Yang
AppStarter
GUILauncher
CommandLineApp
no arg
with args
EPUBConvertor
AppState
AppConfig
AppConfig
Image Source: wiki
pathColumn.minWidthProperty().bind(fileList.widthProperty().multiply(0.66));
modeLabel.textProperty().bind(state.getMode().asString());
Bindings.when()
convertButton.textProperty().bind(
Bindings.when(state.getMode().isNotEqualTo(AppMode.CONVERTING))
.then("Convert")
.otherwise("Cancel")
);
with listeners
state.getMode().addListener((observable, oldValue, newValue) -> {
bindProgressLabel(newValue);
bindProgressBar(newValue);
Map<String, Boolean> convertButtonClassMap;
if (newValue != AppMode.CONVERTING) {
convertButtonClassMap = Map.of("primary", true, "negative", false);
} else {
convertButtonClassMap = Map.of("primary", false, "negative", true);
}
toggleClassMap(convertButton, convertButtonClassMap);
Map<String, Boolean> progressbarClassMap;
switch (newValue) {
case DONE -> progressbarClassMap = Map.of("positive", true, "negative", false);
case INTERRUPTED -> progressbarClassMap = Map.of("positive", false, "negative", true);
default -> progressbarClassMap = Map.of("positive", false, "negative", false);
}
toggleClassMap(progressbar, progressbarClassMap);
});
TableColumn.setCellValueFactory()
fileList.setItems(state.getFiles());
statusColumn.setCellValueFactory(data -> data.getValue().getStatus().asString());
statusColumn.setCellFactory(data -> new TableCell<>() {
@Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
setText(item);
if (empty) return;
toggleClassMap(this, Map.of("positive", item.equals("SUCCESS"), "negative", item.equals("FAILED")));
}
});
nameColumn.setCellValueFactory(new PropertyValueFactory<>("filename"));
pathColumn.setCellValueFactory(new PropertyValueFactory<>("path"));
Garbage Collection
binding = Bindings.createStringBinding(
() -> MessageFormat.format("0 / {0}", state.getFiles().getSize()),
Bindings.size(state.getFiles())
);
Reference: stackoverflow
binding = Bindings.createStringBinding(
() -> MessageFormat.format("0 / {0}", state.getFiles().getSize()),
state.getFiles().sizeProperty()
);
vs
private class ConversionTask extends Task<Void> {
int conversionCnt = 0;
List<EPUBFile> files;
public ConversionTask(List<EPUBFile> files) {
this.files = files;
}
@Override
public Void call() throws InterruptedException {
// put initial value so the progress won't be -1/-1
this.updateProgress(0, files.size());
for (EPUBFile file: files) {
convertor.convert(file, state.getConfig());
conversionCnt++;
this.updateProgress(conversionCnt, files.size());
}
return;
}
}
// start task
conversionTask.setOnSucceeded(ev -> {
state.setMode(AppMode.DONE);
showConversionResult(conversionTask.getValue().getLeftValue());
});
conversionThread = new Thread(conversionTask);
conversionThread.setDaemon(true);
conversionThread.start();
// interrupt task
conversionTask.cancel();
// bind to progressbar
progressbar.progressProperty().bind(conversionTask.progressProperty());
Thread.currentThread().isInterrupted()
while (entries.hasMoreElements()) {
// stop conversion if interrupt signal occurs.
if (Thread.currentThread().isInterrupted()) throw new InterruptedException();
entry = entries.nextElement();
// ...
}
vgrow="ALWAYS"
<GridPane fx:id="root" prefHeight="540.0" prefWidth="720.0" vgap="10.0" xmlns="http://javafx.com/javafx/11.0.1" xmlns:fx="http://javafx.com/fxml/1" fx:controller="tech.stoneapp.epub.gui.controller.AppController">
<columnConstraints>
<ColumnConstraints hgrow="ALWAYS" minWidth="10.0" prefWidth="100.0" />
</columnConstraints>
<rowConstraints>
<RowConstraints minHeight="10.0" prefHeight="30.0" />
<RowConstraints minHeight="10.0" prefHeight="30.0" />
<RowConstraints minHeight="10.0" prefHeight="30.0" vgrow="ALWAYS" />
<RowConstraints minHeight="130.0" prefHeight="130.0" vgrow="SOMETIMES" />
</rowConstraints>
<padding>
<Insets bottom="15.0" left="15.0" right="15.0" top="15.0" />
</padding>
<children>
<!-- some node -->
</children>
</GridPane>
DragOver, DragDropped, TransferModes()
fileList.setOnDragOver(ev -> {
if (ev.getDragboard().hasFiles()) {
ev.acceptTransferModes(TransferMode.COPY_OR_MOVE);
}
ev.consume();
});
fileList.setOnDragDropped(ev -> {
if (ev.getDragboard().hasFiles()) {
List<File> files = ev.getDragboard().getFiles();
importEPUB(files);
}
ev.consume();
});
Reference: stackoverflow
Reference: stackoverflow
public class DesktopAPI {
public DesktopAPI() {}
public static boolean showInFolder(File file) {
if (!file.exists()) return false;
String path = file.getAbsolutePath();
OS osPlatform = getOS();
String[] command = null;
switch (osPlatform) {
// magic don't touch
case windows:
// on windows, pass String[] failed.
command = new String[] {String.format("explorer.exe /select,\"%s\"", path)};
break;
case macos:
// on Mac, pass String failed. ????
command = new String[] {"open", "-R", path};
break;
case linux:
// use Desktop.getDesktop() to handle files on linux, for there are too many cases on linux.
Desktop.getDesktop().browse(file.toURI());
default:
return false;
}
// only command for Windows and MacOS
return runCommand(command);
}
}
Related Problems: stackoverflow
public class EPUBConvertor {
private static EPUBConvertor instance;
private EPUBConvertor() {}
public static EPUBConvertor getInstance() {
if (instance == null) instance = new EPUBConvertor();
return instance;
}
}
subclass with private constructor
public class EPUBConvertor {
// signature security
// https://stackoverflow.com/a/18634125/9039813
public static final class EPUBAccessor { private EPUBAccessor() {} }
private static final EPUBAccessor accessor = new EPUBAccessor();
private EPUBConvertor() {}
}
Reference: stackoverflow
subclass with private constructor
public class EPUBFile {
private ObjectProperty<ConvertStatus> status = new SimpleObjectProperty<>(ConvertStatus.PENDING);
public void updateStatus(ConvertStatus status, EPUBConvertor.EPUBAccessor accessor) {
// slap you with NullPointerException
Objects.requireNonNull(accessor);
this.status.setValue(status);
}
}
Reference: stackoverflow
Define jobs in .github/workflows/action.yml
name: Build App to JAR
on:
push:
branch: master
tags:
- 'v*.*.*'
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up JDK
uses: actions/setup-java@v2
with:
java-version: '15'
distribution: 'adopt'
- name: Grant execute permission for gradlew
run: chmod +x gradlew
- name: Build with Gradle
run: ./gradlew jar
- name: Set output
id: vars
run: echo ::set-output name=tag::${GITHUB_REF#refs/*/}
- name: Create Release
uses: "marvinpinto/action-automatic-releases@latest"
with:
repo_token: "${{ secrets.GITHUB_TOKEN }}"
title: "Release ${{ steps.vars.outputs.tag }}"
files: |
build/libs/*.jar
Released under MIT License!