Skip to content

Commit

Permalink
Merge pull request #527 from saalfeldlab/fix/commitTriggers
Browse files Browse the repository at this point in the history
Fix/commit triggers
  • Loading branch information
cmhulbert authored Feb 16, 2024
2 parents d659d66 + 09b85ca commit 8aa6952
Show file tree
Hide file tree
Showing 7 changed files with 113 additions and 109 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/maven-build-all-installer.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ jobs:
build:
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macos-latest, macOS-ARM64]
os: [ubuntu-latest, windows-latest, macos-latest, macos-14]
runs-on: ${{ matrix.os }}
env:
RELEASE_INSTALLERS: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
Expand Down Expand Up @@ -50,13 +50,13 @@ jobs:
java-package: jdk+fx
cache: 'maven'
- name: "Build with Maven"
if: matrix.os != 'macos-latest' && matrix.os != 'macOS-ARM64'
if: matrix.os != 'macos-latest' && matrix.os != 'macos-14'
run: mvn -B clean install -DskipTests -Pbuild-installer "-Dmatrix.os=${{ matrix.os }}" --file pom.xml
- name: "Build with Maven (macOS No Signing)"
env:
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
if: ${{ env.MACOS_CERTIFICATE == null && (matrix.os == 'macos-latest' || matrix.os == 'macOS-ARM64') }}
if: ${{ env.MACOS_CERTIFICATE == null && (matrix.os == 'macos-latest' || matrix.os == 'macos-14') }}
run: mvn -B clean install -DskipTests -Pbuild-installer "-Dmatrix.os=${{ matrix.os }}" --file pom.xml
- name: Update Automatic Release
uses: marvinpinto/action-automatic-releases@latest
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
import net.imglib2.util.ConstantUtils;
import net.imglib2.util.IntervalIndexer;
import net.imglib2.util.Intervals;
import org.janelia.saalfeldlab.fx.UtilityTask;
import paintera.net.imglib2.view.BundleView;
import net.imglib2.view.ExtendedRealRandomAccessibleRealInterval;
import net.imglib2.view.IntervalView;
Expand Down Expand Up @@ -811,95 +812,90 @@ public void persistCanvas(final boolean clearCanvas) throws CannotPersist {
this.isPersistingProperty.set(true);
this.isBusy.set(true);
}
/* Start the task in the background */
final var progressBar = new ProgressBar();
progressBar.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
final ObservableList<String> states = FXCollections.synchronizedObservableList(FXCollections.observableArrayList());
final Consumer<String> nextState = (text) -> InvokeOnJavaFXApplicationThread.invoke(() -> states.add(text));
final Consumer<String> updateState = (update) -> InvokeOnJavaFXApplicationThread.invoke(() -> states.set(states.size() - 1, update));
persistCanvasTask(clearCanvas, progressBar.progressProperty(), nextState, updateState).submit();

final BooleanBinding stillPersisting = Bindings.createBooleanBinding(
() -> this.isPersisting() || progressBar.progressProperty().get() < 1.0,
this.isPersistingProperty, progressBar.progressProperty()
);

/*Show the dialog and wait for committing to finish */
LOG.warn("Creating commit status dialog.");
final Alert isCommittingDialog = PainteraAlerts.alert(Alert.AlertType.INFORMATION, false);
Scene dialogScene = isCommittingDialog.getDialogPane().getScene();
final var prevCursor = Optional.ofNullable(dialogScene.cursorProperty().get()).orElse(javafx.scene.Cursor.DEFAULT);
ObjectBinding<javafx.scene.Cursor> busyCursorBinding = Bindings.createObjectBinding(() -> {
if (isBusy.get()) {
return javafx.scene.Cursor.WAIT;
} else {
return prevCursor;
}
}, isBusyProperty());
dialogScene.cursorProperty().bind(busyCursorBinding);
dialogScene.getWindow().setOnCloseRequest(ev -> {
if (isBusy.get())
ev.consume();
});
isCommittingDialog.setHeaderText("Committing canvas.");
final Node okButton = isCommittingDialog.getDialogPane().lookupButton(ButtonType.OK);
okButton.setDisable(true);
final var content = new VBox();
final var statesText = new TextArea();
statesText.setMouseTransparent(true);
statesText.setEditable(false);
statesText.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
final var textScrollPane = new ScrollPane(statesText);
content.getChildren().add(new HBox(textScrollPane));
content.getChildren().add(new HBox(progressBar));

HBox.setHgrow(statesText, Priority.ALWAYS);
HBox.setHgrow(progressBar, Priority.ALWAYS);
VBox.setVgrow(statesText, Priority.ALWAYS);
VBox.setVgrow(progressBar, Priority.ALWAYS);
InvokeOnJavaFXApplicationThread.invoke(() -> isCommittingDialog.getDialogPane().setContent(content));
states.addListener((ListChangeListener<String>)change -> statesText.setText(String.join("\n", states)));
synchronized (this) {
okButton.disableProperty().bind(stillPersisting);
}

LOG.info("Will show dialog? {}", stillPersisting.get());
if (stillPersisting.get())
isCommittingDialog.showAndWait();
}

private synchronized UtilityTask<?> persistCanvasTask(
final boolean clearCanvas,
final DoubleProperty progress,
final Consumer<String> nextState,
final Consumer<String> updateState
) {


LOG.debug("Merging canvas into background for blocks {}", this.affectedBlocks);
final CachedCellImg<UnsignedLongType, ?> canvas = this.dataCanvases[0];
final long[] affectedBlocks = this.affectedBlocks.toArray();
this.affectedBlocks.clear();
final var progressBar = new ProgressBar();
progressBar.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);

final BooleanBinding proxy = Bindings.createBooleanBinding(() -> this.isPersisting() || progressBar.progressProperty().get() < 1.0,
this.isPersistingProperty, progressBar.progressProperty());

final ObservableList<String> states = FXCollections.observableArrayList();

final Consumer<String> nextState = (next) -> {
synchronized (states) {
states.add(next);
}
};
final Consumer<String> updateState = (update) -> {
synchronized (states) {
states.set(states.size() -1, update);
}
};

final Runnable dialogHandler = () -> {
LOG.warn("Creating commit status dialog.");
final Alert isCommittingDialog = PainteraAlerts.alert(Alert.AlertType.INFORMATION, false);
Scene dialogScene = isCommittingDialog.getDialogPane().getScene();
final var prevCursor = Optional.ofNullable(dialogScene.cursorProperty().get()).orElse(javafx.scene.Cursor.DEFAULT);
ObjectBinding<javafx.scene.Cursor> busyCursorBinding = Bindings.createObjectBinding(() -> {
if (isBusy.get()) {
return javafx.scene.Cursor.WAIT;
} else {
return prevCursor;
}
}, isBusyProperty());
dialogScene.cursorProperty().bind(busyCursorBinding);
dialogScene.getWindow().setOnCloseRequest(ev -> {
if (isBusy.get())
ev.consume();
});
isCommittingDialog.setHeaderText("Committing canvas.");
final Node okButton = isCommittingDialog.getDialogPane().lookupButton(ButtonType.OK);
okButton.setDisable(true);
final var content = new VBox();
final var statesText = new TextArea();
statesText.setMouseTransparent(true);
statesText.setEditable(false);
statesText.setMaxSize(Double.MAX_VALUE, Double.MAX_VALUE);
final var textScrollPane = new ScrollPane(statesText);
content.getChildren().add(new HBox(textScrollPane));
content.getChildren().add(new HBox(progressBar));

HBox.setHgrow(statesText, Priority.ALWAYS);
HBox.setHgrow(progressBar, Priority.ALWAYS);
VBox.setVgrow(statesText, Priority.ALWAYS);
VBox.setVgrow(progressBar, Priority.ALWAYS);
InvokeOnJavaFXApplicationThread.invoke(() -> isCommittingDialog.getDialogPane().setContent(content));
states.addListener((ListChangeListener<? super String>) change ->
InvokeOnJavaFXApplicationThread.invoke(() -> {
synchronized (states) {
statesText.setText(String.join("\n", states));
}
}
)
);
synchronized (this) {
okButton.disableProperty().bind(proxy);
}
LOG.info("Will show dialog? {}", proxy.get());
if (proxy.get())
isCommittingDialog.show();
};
final Consumer<Double> animateProgressBar = progress -> {
if (progressBar.progressProperty().get() > progress) {
progressBar.progressProperty().set(0.0);
final Consumer<Double> animateProgressBar = newProgress -> {
if (progress.get() > newProgress) {
progress.set(0.0);
}
Timeline timeline = new Timeline();
KeyValue keyValue = new KeyValue(progressBar.progressProperty(), progress);
KeyValue keyValue = new KeyValue(progress, newProgress);
KeyFrame keyFrame = new KeyFrame(new Duration(500), keyValue);
timeline.getKeyFrames().add(keyFrame);
InvokeOnJavaFXApplicationThread.invoke(timeline::play);
};
ChangeListener<Number> animateProgressBarListener = (obs, oldv, newv) ->
animateProgressBar.accept(newv.doubleValue());
Tasks.createTask(task -> {
ChangeListener<Number> animateProgressBarListener = (obs, oldv, newv) -> animateProgressBar.accept(newv.doubleValue());
return Tasks.createTask(task -> {
try {
InvokeOnJavaFXApplicationThread.invokeAndWait(dialogHandler);

nextState.accept("Persisting painted labels...");
InvokeOnJavaFXApplicationThread.invoke(() ->
this.persistCanvas.getProgressProperty().addListener(animateProgressBarListener)
Expand Down Expand Up @@ -927,7 +923,7 @@ public void persistCanvas(final boolean clearCanvas) throws CannotPersist {
} else
LOG.info("Not clearing canvas.");

} catch (UnableToPersistCanvas | UnableToUpdateLabelBlockLookup | InterruptedException e) {
} catch (UnableToPersistCanvas | UnableToUpdateLabelBlockLookup e) {
throw new RuntimeException(e);
}
return null;
Expand All @@ -944,7 +940,7 @@ public void persistCanvas(final boolean clearCanvas) throws CannotPersist {
LOG.error("Unable to commit canvas", t.getException());
nextState.accept("Unable to commit canvas: " + t.getException().getMessage());
}
}).submit();
});
}

@Override
Expand Down
23 changes: 13 additions & 10 deletions src/main/kotlin/org/janelia/saalfeldlab/paintera/Paintera.kt
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import org.janelia.saalfeldlab.fx.extensions.nonnull
import org.janelia.saalfeldlab.fx.ui.Exceptions
import org.janelia.saalfeldlab.fx.util.InvokeOnJavaFXApplicationThread
import org.janelia.saalfeldlab.paintera.config.ScreenScalesConfig
import org.janelia.saalfeldlab.paintera.data.mask.MaskedSource
import org.janelia.saalfeldlab.paintera.state.label.ConnectomicsLabelState
import org.janelia.saalfeldlab.paintera.ui.PainteraAlerts
import org.janelia.saalfeldlab.paintera.util.logging.LogUtils
Expand Down Expand Up @@ -219,16 +220,18 @@ class Paintera : Application() {
}

paintera.baseView.sourceInfo().apply {
trackSources().forEach { source ->
(getState(source) as? ConnectomicsLabelState)?.let { state ->
val responseButton = state.promptForCommitIfNecessary(paintera.baseView) { index, name ->
"""
Closing current Paintera project.
Uncommitted changes to the canvas will be lost for source $index: $name if skipped.
Uncommitted changes to the fragment-segment-assigment will be stored in the Paintera project (if any)
but can be committed to the data backend, as well
""".trimIndent()
}
trackSources()
.filterIsInstance(MaskedSource::class.java)
.forEach { source ->
(getState(source) as? ConnectomicsLabelState)?.let { state ->
val responseButton = state.promptForCommitIfNecessary(paintera.baseView) { index, name ->
"""
Closing current Paintera project.
Uncommitted changes to the canvas will be lost for source $index: $name if skipped.
Uncommitted changes to the fragment-segment-assigment will be stored in the Paintera project (if any)
but can be committed to the data backend, as well
""".trimIndent()
}

when (responseButton) {
ButtonType.OK -> Unit
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ class PainteraMainWindow(val gateway: PainteraGateway = PainteraGateway()) {
}
}

private var wasQuit = false
fun setupStage(stage: Stage) {
keyTracker.installInto(stage)
projectDirectory.addListener { pd -> stage.title = if (pd.directory == null) NAME else "$NAME ${pd.directory.absolutePath.homeToTilde()}" }
Expand All @@ -267,24 +268,28 @@ class PainteraMainWindow(val gateway: PainteraGateway = PainteraGateway()) {
Image("/icon-128.png")
)
stage.fullScreenExitKeyProperty().bind(NAMED_COMBINATIONS[PainteraBaseKeys.TOGGLE_FULL_SCREEN]!!.primaryCombinationProperty())
stage.onCloseRequest = EventHandler { if (!askQuit()) it.consume() }
stage.onHiding = EventHandler { quit() }
wasQuit = false
stage.onCloseRequest = EventHandler { if (!doSaveAndQuit()) it.consume() }
stage.onHiding = EventHandler { if (!doSaveAndQuit()) it.consume() }
}

internal fun askAndQuit() {
if (askQuit())
pane.scene.window.hide()
internal fun askSaveAndQuit() : Boolean {
return when {
wasQuit -> false
!isSaveNecessary() -> true
else -> SaveAndQuitDialog.showAndWaitForResponse()
}
}

private fun askQuit(): Boolean {
return askSaveAndQuit()
}
internal fun doSaveAndQuit(): Boolean {

internal fun askSaveAndQuit(): Boolean {
if (!isSaveNecessary()) {
return true
}
return SaveAndQuitDialog.showAndWaitForResponse()
return if (askSaveAndQuit()) {
quit()
wasQuit = true
/* quit() calls platform.exit() and exitProcess, so we never really get here.
* But it's useful to know if `false` so we do this */
true
} else false
}

private fun quit() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class CommitHandler<S : SourceState<*, *>>(private val state: S, private val fra

internal fun makeActionSet(bindings: NamedKeyCombination.CombinationMap, paintera: PainteraBaseView) =
painteraActionSet(LabelSourceStateKeys.COMMIT_DIALOG, MenuActionType.CommitCanvas) {
KEY_PRESSED(bindings, LabelSourceStateKeys.COMMIT_DIALOG) {
KEY_PRESSED ( bindings, LabelSourceStateKeys.COMMIT_DIALOG, keysExclusive = true) {
onAction { showCommitDialog(state, paintera.sourceInfo().indexOf(state.dataSource), true, fragmentSegmentAssignmentState = fragmentProvider()) }
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,22 +215,22 @@ class ConnectomicsLabelState<D : IntegerType<D>, T>(

private val globalActions = listOf(
ActionSet("Connectomics Label State Global Actions") {
KEY_PRESSED(keyBindings, LabelSourceStateKeys.REFRESH_MESHES) {
KEY_PRESSED(keyBindings, LabelSourceStateKeys.REFRESH_MESHES, keysExclusive = true) {
onAction {
refreshMeshes()
LOG.debug("Key event triggered refresh meshes")
}
}
KEY_PRESSED(keyBindings, LabelSourceStateKeys.TOGGLE_NON_SELECTED_LABELS_VISIBILITY) {
KEY_PRESSED(keyBindings, LabelSourceStateKeys.TOGGLE_NON_SELECTED_LABELS_VISIBILITY, keysExclusive = true) {
onAction {
showOnlySelectedInStreamToggle.toggleNonSelectionVisibility()
paintera.baseView.orthogonalViews().requestRepaint()
}
}
KEY_PRESSED(keyBindings, LabelSourceStateKeys.ARGB_STREAM_INCREMENT_SEED) {
KEY_PRESSED ( keyBindings, LabelSourceStateKeys.ARGB_STREAM_INCREMENT_SEED, keysExclusive = true) {
onAction { streamSeedSetter.incrementStreamSeed() }
}
KEY_PRESSED(keyBindings, LabelSourceStateKeys.ARGB_STREAM_DECREMENT_SEED) {
KEY_PRESSED(keyBindings, LabelSourceStateKeys.ARGB_STREAM_DECREMENT_SEED, keysExclusive = true) {
onAction { streamSeedSetter.decrementStreamSeed() }
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ enum class PainteraMenuItems(
TOGGLE_STATUS_BAR_MODE to EventHandler<ActionEvent> { properties.statusBarConfig.cycleModes() },
TOGGLE_SIDE_BAR_MENU_ITEM to EventHandler<ActionEvent> { properties.sideBarConfig.toggleIsVisible() },
TOGGLE_TOOL_BAR_MENU_ITEM to EventHandler<ActionEvent> { properties.toolBarConfig.toggleIsVisible() },
QUIT to EventHandler<ActionEvent> { askAndQuit() },
QUIT to EventHandler<ActionEvent> { doSaveAndQuit() },
CYCLE_FORWARD to EventHandler<ActionEvent> { baseView.sourceInfo().incrementCurrentSourceIndex() },
CYCLE_BACKWARD to EventHandler<ActionEvent> { baseView.sourceInfo().decrementCurrentSourceIndex() },
TOGGLE_VISIBILITY to EventHandler<ActionEvent> {
Expand Down

0 comments on commit 8aa6952

Please sign in to comment.