Introduce new system to create the context menus
This commit is contained in:
parent
b32409379c
commit
df91a71eb7
|
@ -1,11 +1,10 @@
|
|||
package ctbrec.ui;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
|
@ -23,7 +22,7 @@ public class StreamSourceSelectionDialog {
|
|||
private StreamSourceSelectionDialog() {
|
||||
}
|
||||
|
||||
public static void show(Scene parent, Model model, Function<Model, Void> onSuccess, Function<Throwable, Void> onFail) {
|
||||
public static void show(Scene parent, Model model, Consumer<Model> onSuccess, Consumer<Throwable> onFail) {
|
||||
Task<List<StreamSource>> selectStreamSource = new Task<List<StreamSource>>() {
|
||||
@Override
|
||||
protected List<StreamSource> call() throws Exception {
|
||||
|
@ -37,14 +36,14 @@ public class StreamSourceSelectionDialog {
|
|||
List<StreamSource> sources;
|
||||
try {
|
||||
sources = selectStreamSource.get();
|
||||
int selectedIndex = model.getStreamUrlIndex() > -1 ? Math.min(model.getStreamUrlIndex(), sources.size() - 1) : sources.size() - 1;
|
||||
ChoiceDialog<StreamSource> choiceDialog = new ChoiceDialog<>(sources.get(selectedIndex), sources);
|
||||
var selectedIndex = model.getStreamUrlIndex() > -1 ? Math.min(model.getStreamUrlIndex(), sources.size() - 1) : sources.size() - 1;
|
||||
var choiceDialog = new ChoiceDialog<StreamSource>(sources.get(selectedIndex), sources);
|
||||
choiceDialog.setTitle("Stream Quality");
|
||||
choiceDialog.setHeaderText("Select your preferred stream quality");
|
||||
choiceDialog.setHeaderText("Select your preferred stream quality for " + model.getDisplayName());
|
||||
choiceDialog.setResizable(true);
|
||||
Stage stage = (Stage) choiceDialog.getDialogPane().getScene().getWindow();
|
||||
var stage = (Stage) choiceDialog.getDialogPane().getScene().getWindow();
|
||||
stage.getScene().getStylesheets().addAll(parent.getStylesheets());
|
||||
InputStream icon = Dialogs.class.getResourceAsStream("/icon.png");
|
||||
var icon = Dialogs.class.getResourceAsStream("/icon.png");
|
||||
stage.getIcons().add(new Image(icon));
|
||||
Optional<StreamSource> selectedSource = choiceDialog.showAndWait();
|
||||
if (selectedSource.isPresent()) {
|
||||
|
@ -53,16 +52,17 @@ public class StreamSourceSelectionDialog {
|
|||
index = sources.indexOf(selectedSource.get());
|
||||
}
|
||||
model.setStreamUrlIndex(index);
|
||||
onSuccess.apply(model);
|
||||
System.err.println(model + " " + model.getStreamUrlIndex());
|
||||
onSuccess.accept(model);
|
||||
}
|
||||
} catch (ExecutionException e1) {
|
||||
onFail.apply(e1);
|
||||
onFail.accept(e1);
|
||||
} catch (InterruptedException e1) {
|
||||
Thread.currentThread().interrupt();
|
||||
onFail.apply(e1);
|
||||
onFail.accept(e1);
|
||||
}
|
||||
});
|
||||
selectStreamSource.setOnFailed(e -> onFail.apply(selectStreamSource.getException()));
|
||||
selectStreamSource.setOnFailed(e -> onFail.accept(selectStreamSource.getException()));
|
||||
GlobalThreadPool.submit(selectStreamSource);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,15 @@
|
|||
package ctbrec.ui.action;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ctbrec.Model;
|
||||
import ctbrec.ui.DesktopIntegration;
|
||||
import javafx.scene.Node;
|
||||
|
||||
public class OpenUrlAction extends ModelMassEditAction {
|
||||
|
||||
public OpenUrlAction(Node source, List<? extends Model> models) {
|
||||
super(source, models);
|
||||
action = m -> DesktopIntegration.open(m.getUrl());
|
||||
}
|
||||
}
|
|
@ -68,7 +68,7 @@ public class SetStopDateAction {
|
|||
var localDate = LocalDateTime.ofInstant(model.getRecordUntil(), ZoneId.systemDefault());
|
||||
datePicker.setDateTimeValue(localDate);
|
||||
}
|
||||
boolean userClickedOk = Dialogs.showCustomInput(source.getScene(), "Stop Recording at", gridPane);
|
||||
boolean userClickedOk = Dialogs.showCustomInput(source.getScene(), "Stop Recording of " + model.getDisplayName() + " at", gridPane);
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
if (userClickedOk) {
|
||||
SubsequentAction action = pauseButton.isSelected() ? PAUSE : REMOVE;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
package ctbrec.ui.action;
|
||||
|
||||
@FunctionalInterface
|
||||
public interface TriConsumer<T, U, V> {
|
||||
|
||||
void accept(T t, U u, V v);
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package ctbrec.ui.menu;
|
||||
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import ctbrec.Model;
|
||||
|
@ -44,6 +45,7 @@ public class ModelGroupMenuBuilder {
|
|||
Objects.requireNonNull(model, "Model has to be set");
|
||||
Objects.requireNonNull(recorder, "Recorder has to be set");
|
||||
Objects.requireNonNull(source, "Node has to be set");
|
||||
callback = Optional.ofNullable(callback).orElse(m -> {});
|
||||
|
||||
var menu = new Menu("Group");
|
||||
|
||||
|
|
|
@ -0,0 +1,268 @@
|
|||
package ctbrec.ui.menu;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ctbrec.Config;
|
||||
import ctbrec.GlobalThreadPool;
|
||||
import ctbrec.Model;
|
||||
import ctbrec.ModelGroup;
|
||||
import ctbrec.recorder.Recorder;
|
||||
import ctbrec.ui.AutosizeAlert;
|
||||
import ctbrec.ui.SiteUiFactory;
|
||||
import ctbrec.ui.StreamSourceSelectionDialog;
|
||||
import ctbrec.ui.action.AddToGroupAction;
|
||||
import ctbrec.ui.action.PlayAction;
|
||||
import ctbrec.ui.action.SetStopDateAction;
|
||||
import ctbrec.ui.action.StartRecordingAction;
|
||||
import ctbrec.ui.action.StopRecordingAction;
|
||||
import ctbrec.ui.action.TriConsumer;
|
||||
import ctbrec.ui.controls.Dialogs;
|
||||
import ctbrec.ui.tabs.FollowedTab;
|
||||
import javafx.application.Platform;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
|
||||
public class ModelMenuContributor {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(ModelMenuContributor.class);
|
||||
|
||||
private static final String COULDNT_START_STOP_RECORDING = "Couldn't start/stop recording";
|
||||
private static final String ERROR = "Error";
|
||||
|
||||
private Config config;
|
||||
private Recorder recorder;
|
||||
private Node source;
|
||||
private Consumer<Model> startStopCallback;
|
||||
private TriConsumer<Model, Boolean, Boolean> followCallback;
|
||||
|
||||
private ModelMenuContributor(Node source, Config config, Recorder recorder) {
|
||||
this.source = source;
|
||||
this.config = config;
|
||||
this.recorder = recorder;
|
||||
}
|
||||
|
||||
public static ModelMenuContributor newContributor(Node source, Config config, Recorder recorder) {
|
||||
return new ModelMenuContributor(source, config, recorder);
|
||||
}
|
||||
|
||||
public ModelMenuContributor withStartStopCallback(Consumer<Model> callback) {
|
||||
this.startStopCallback = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ModelMenuContributor withFollowCallback(TriConsumer<Model, Boolean, Boolean> callback) {
|
||||
this.followCallback = callback;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void contributeToMenu(List<Model> selectedModels, ContextMenu menu) {
|
||||
startStopCallback = Optional.ofNullable(startStopCallback).orElse(m -> {});
|
||||
followCallback = Optional.ofNullable(followCallback).orElse((m, f, s) -> {});
|
||||
addOpenInPlayer(menu, selectedModels);
|
||||
menu.getItems().add(new SeparatorMenuItem());
|
||||
addStartOrStop(menu, selectedModels);
|
||||
addStartRecordingWithTimeLimit(menu, selectedModels);
|
||||
addStartPaused(menu, selectedModels);
|
||||
addRecordLater(menu, selectedModels);
|
||||
addPauseResume(menu, selectedModels);
|
||||
addGroupMenu(menu, selectedModels);
|
||||
menu.getItems().add(new SeparatorMenuItem());
|
||||
addFollowUnfollow(menu, selectedModels);
|
||||
}
|
||||
|
||||
private void addFollowUnfollow(ContextMenu menu, List<Model> selectedModels) {
|
||||
var site = selectedModels.get(0).getSite();
|
||||
if (site.supportsFollow()) {
|
||||
var follow = new MenuItem("Follow");
|
||||
follow.setOnAction(e -> follow(selectedModels, true));
|
||||
var unfollow = new MenuItem("Unfollow");
|
||||
unfollow.setOnAction(e -> follow(selectedModels, false));
|
||||
|
||||
var followOrUnFollow = (source instanceof FollowedTab) ? unfollow : follow; // TODO source instanceof FollowedTab does not work
|
||||
followOrUnFollow.setDisable(!site.credentialsAvailable());
|
||||
menu.getItems().add(followOrUnFollow);
|
||||
followOrUnFollow.setDisable(!site.credentialsAvailable());
|
||||
}
|
||||
}
|
||||
|
||||
protected void follow(List<Model> selectedModels, boolean follow) {
|
||||
for (Model model : selectedModels) {
|
||||
follow(model, follow);
|
||||
}
|
||||
}
|
||||
|
||||
CompletableFuture<Boolean> follow(Model model, boolean follow) {
|
||||
source.setCursor(Cursor.WAIT);
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
var success = true;
|
||||
try {
|
||||
if (follow) {
|
||||
SiteUiFactory.getUi(model.getSite()).login();
|
||||
boolean followed = model.follow();
|
||||
if (followed) {
|
||||
success = true;
|
||||
} else {
|
||||
Dialogs.showError(source.getScene(), "Couldn't follow model", "", null);
|
||||
success = false;
|
||||
}
|
||||
} else {
|
||||
SiteUiFactory.getUi(model.getSite()).login();
|
||||
boolean unfollowed = model.unfollow();
|
||||
if (unfollowed) {
|
||||
success = true;
|
||||
} else {
|
||||
Dialogs.showError(source.getScene(), "Couldn't unfollow model", "", null);
|
||||
success = false;
|
||||
}
|
||||
}
|
||||
return success;
|
||||
} catch (Exception e1) {
|
||||
LOG.error("Couldn't follow/unfollow model {}", model.getName(), e1);
|
||||
String msg = "I/O error while following/unfollowing model " + model.getName() + ": ";
|
||||
Dialogs.showError(source.getScene(), "Couldn't follow/unfollow model", msg, e1);
|
||||
return false;
|
||||
} finally {
|
||||
final boolean result = success;
|
||||
Platform.runLater(() -> {
|
||||
source.setCursor(Cursor.DEFAULT);
|
||||
followCallback.accept(model, follow, result);
|
||||
});
|
||||
}
|
||||
}, GlobalThreadPool.get());
|
||||
}
|
||||
|
||||
private void addGroupMenu(ContextMenu menu, List<Model> selectedModels) {
|
||||
var model = selectedModels.get(0);
|
||||
var addToGroup = new MenuItem("Add to group");
|
||||
addToGroup.setOnAction(e -> new AddToGroupAction(source, recorder, model).execute());
|
||||
var groupSubMenu = new ModelGroupMenuBuilder().model(model).recorder(recorder).node(source).build();
|
||||
Optional<ModelGroup> modelGroup = recorder.getModelGroup(model);
|
||||
menu.getItems().add(modelGroup.isEmpty() ? addToGroup : groupSubMenu);
|
||||
}
|
||||
|
||||
private void addPauseResume(ContextMenu menu, List<Model> selectedModels) {
|
||||
var first = selectedModels.get(0);
|
||||
if (recorder.isTracked(first)) {
|
||||
var pause = new MenuItem("Pause Recording");
|
||||
pause.setOnAction(e -> pauseResumeAction(selectedModels, true));
|
||||
var resume = new MenuItem("Resume Recording");
|
||||
resume.setOnAction(e -> pauseResumeAction(selectedModels, false));
|
||||
var pauseResume = recorder.isSuspended(first) ? resume : pause;
|
||||
menu.getItems().add(pauseResume);
|
||||
}
|
||||
}
|
||||
|
||||
private void pauseResumeAction(List<Model> selectedModels, boolean pause) {
|
||||
selectedModels.forEach(m -> m.setSuspended(pause));
|
||||
startStopAction(selectedModels, true);
|
||||
}
|
||||
|
||||
private void addRecordLater(ContextMenu menu, List<Model> selectedModels) {
|
||||
var first = selectedModels.get(0);
|
||||
var recordLater = new MenuItem("Record Later");
|
||||
recordLater.setOnAction(e -> recordLater(selectedModels, true));
|
||||
var removeRecordLater = new MenuItem("Forget Model");
|
||||
removeRecordLater.setOnAction(e -> recordLater(selectedModels, false));
|
||||
var addRemoveBookmark = recorder.isMarkedForLaterRecording(first) ? removeRecordLater : recordLater;
|
||||
if (recorder.isTracked(first)) {
|
||||
menu.getItems().add(recordLater);
|
||||
} else {
|
||||
menu.getItems().add(addRemoveBookmark);
|
||||
}
|
||||
}
|
||||
|
||||
private void recordLater(List<Model> selectedModels, boolean recordLater) {
|
||||
selectedModels.forEach(m -> m.setMarkedForLaterRecording(recordLater));
|
||||
startStopAction(selectedModels, recordLater);
|
||||
}
|
||||
|
||||
private void addStartPaused(ContextMenu menu, List<Model> selectedModels) {
|
||||
if (!recorder.isTracked(selectedModels.get(0))) {
|
||||
var addPaused = new MenuItem("Add in paused state");
|
||||
menu.getItems().add(addPaused);
|
||||
addPaused.setOnAction(e -> {
|
||||
for (Model model : selectedModels) {
|
||||
model.setMarkedForLaterRecording(false);
|
||||
model.setSuspended(true);
|
||||
}
|
||||
startStopAction(selectedModels, true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void addStartRecordingWithTimeLimit(ContextMenu menu, List<Model> selectedModels) {
|
||||
var text = recorder.isTracked(selectedModels.get(0)) ? "Record Until" : "Start Recording Until";
|
||||
var start = new MenuItem(text);
|
||||
menu.getItems().add(start);
|
||||
start.setOnAction(e -> {
|
||||
startStopAction(selectedModels, true);
|
||||
selectedModels.forEach(m -> new SetStopDateAction(source, m, recorder).execute());
|
||||
});
|
||||
}
|
||||
|
||||
private void addOpenInPlayer(ContextMenu menu, List<Model> selectedModels) {
|
||||
var openInPlayer = new MenuItem("Open in Player");
|
||||
openInPlayer.setOnAction(e -> selectedModels.forEach(m -> new PlayAction(source, m).execute()));
|
||||
menu.getItems().add(openInPlayer);
|
||||
|
||||
if (config.getSettings().singlePlayer && selectedModels.size() > 1) {
|
||||
openInPlayer.setDisable(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void addStartOrStop(ContextMenu menu, List<Model> selectedModels) {
|
||||
var start = new MenuItem("Start Recording");
|
||||
start.setOnAction(e -> {
|
||||
for (Model model : selectedModels) {
|
||||
model.setMarkedForLaterRecording(false);
|
||||
model.setSuspended(false);
|
||||
}
|
||||
startStopAction(selectedModels, true);
|
||||
});
|
||||
var stop = new MenuItem("Stop Recording");
|
||||
stop.setOnAction(e -> startStopAction(selectedModels, false));
|
||||
var startStop = recorder.isTracked(selectedModels.get(0)) ? stop : start;
|
||||
menu.getItems().add(startStop);
|
||||
}
|
||||
|
||||
private void startStopAction(List<Model> selection, boolean start) {
|
||||
if (start) {
|
||||
boolean selectSource = Config.getInstance().getSettings().chooseStreamQuality;
|
||||
if (selectSource) {
|
||||
for (Model model : selection) {
|
||||
Consumer<Model> onSuccess = modl -> startRecording(List.of(modl));
|
||||
Consumer<Throwable> onFail = throwable -> {
|
||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, source.getScene());
|
||||
alert.setTitle(ERROR);
|
||||
alert.setHeaderText(COULDNT_START_STOP_RECORDING);
|
||||
alert.setContentText("I/O error while starting/stopping the recording: " + throwable.getLocalizedMessage());
|
||||
alert.showAndWait();
|
||||
};
|
||||
StreamSourceSelectionDialog.show(source.getScene(), model, onSuccess, onFail);
|
||||
}
|
||||
} else {
|
||||
startRecording(selection);
|
||||
}
|
||||
} else {
|
||||
stopRecording(selection);
|
||||
}
|
||||
}
|
||||
|
||||
private void startRecording(List<Model> models) {
|
||||
new StartRecordingAction(source, models, recorder).execute(startStopCallback);
|
||||
}
|
||||
|
||||
private void stopRecording(List<Model> models) {
|
||||
new StopRecordingAction(source, models, recorder).execute(startStopCallback);
|
||||
}
|
||||
}
|
|
@ -10,7 +10,7 @@ import java.util.Objects;
|
|||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
|
@ -335,8 +335,12 @@ public class ThumbCell extends StackPane {
|
|||
|
||||
public void setSelected(boolean selected) {
|
||||
selectionProperty.set(selected);
|
||||
selectionOverlay.getStyleClass().add("selection-background");
|
||||
selectionOverlay.setOpacity(selected ? .75 : 0);
|
||||
if (selected) {
|
||||
selectionOverlay.getStyleClass().add("selection-background");
|
||||
} else {
|
||||
selectionOverlay.getStyleClass().remove("selection-background");
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
|
@ -512,17 +516,13 @@ public class ThumbCell extends StackPane {
|
|||
|
||||
boolean selectSource = Config.getInstance().getSettings().chooseStreamQuality;
|
||||
if (selectSource && start) {
|
||||
Function<Model, Void> onSuccess = modl -> {
|
||||
startStopActionAsync(modl, true);
|
||||
return null;
|
||||
};
|
||||
Function<Throwable, Void> onFail = throwable -> {
|
||||
Consumer<Model> onSuccess = modl -> startStopActionAsync(modl, true);
|
||||
Consumer<Throwable> onFail = throwable -> {
|
||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, getScene());
|
||||
alert.setTitle(ERROR);
|
||||
alert.setHeaderText(COULDNT_START_STOP_RECORDING);
|
||||
alert.setContentText("I/O error while starting/stopping the recording: " + throwable.getLocalizedMessage());
|
||||
alert.showAndWait();
|
||||
return null;
|
||||
};
|
||||
StreamSourceSelectionDialog.show(getScene(), model, onSuccess, onFail);
|
||||
} else {
|
||||
|
|
|
@ -35,17 +35,15 @@ import ctbrec.ui.AutosizeAlert;
|
|||
import ctbrec.ui.DesktopIntegration;
|
||||
import ctbrec.ui.SiteUiFactory;
|
||||
import ctbrec.ui.TokenLabel;
|
||||
import ctbrec.ui.action.AddToGroupAction;
|
||||
import ctbrec.ui.action.IgnoreModelsAction;
|
||||
import ctbrec.ui.action.OpenRecordingsDir;
|
||||
import ctbrec.ui.action.SetStopDateAction;
|
||||
import ctbrec.ui.action.TipAction;
|
||||
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
|
||||
import ctbrec.ui.controls.FasterVerticalScrollPaneSkin;
|
||||
import ctbrec.ui.controls.SearchBox;
|
||||
import ctbrec.ui.controls.SearchPopover;
|
||||
import ctbrec.ui.controls.SearchPopoverTreeList;
|
||||
import ctbrec.ui.menu.ModelGroupMenuBuilder;
|
||||
import ctbrec.ui.menu.ModelMenuContributor;
|
||||
import javafx.animation.FadeTransition;
|
||||
import javafx.animation.Interpolator;
|
||||
import javafx.animation.ParallelTransition;
|
||||
|
@ -64,6 +62,7 @@ import javafx.event.ActionEvent;
|
|||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.Parent;
|
||||
import javafx.scene.control.Alert;
|
||||
|
@ -74,7 +73,6 @@ import javafx.scene.control.Label;
|
|||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.control.ProgressIndicator;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.control.SeparatorMenuItem;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TextField;
|
||||
import javafx.scene.control.Tooltip;
|
||||
|
@ -461,42 +459,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
|
||||
private ContextMenu createContextMenu(ThumbCell cell) {
|
||||
var model = cell.getModel();
|
||||
boolean modelIsTrackedByRecorder = recorder.isTracked(model);
|
||||
|
||||
var openInPlayer = new MenuItem("Open in Player");
|
||||
openInPlayer.setOnAction(e -> startPlayer(getSelectedThumbCells(cell)));
|
||||
|
||||
var start = new MenuItem("Start Recording");
|
||||
start.setOnAction(e -> startStopAction(getSelectedThumbCells(cell), true));
|
||||
|
||||
var stop = new MenuItem("Stop Recording");
|
||||
stop.setOnAction(e -> startStopAction(getSelectedThumbCells(cell), false));
|
||||
var startStop = recorder.isTracked(model) ? stop : start;
|
||||
|
||||
var recordUntil = new MenuItem("Start Recording Until");
|
||||
recordUntil.setOnAction(e -> startRecordingWithTimeLimit(getSelectedThumbCells(cell)));
|
||||
var addPaused = new MenuItem("Add in paused state");
|
||||
addPaused.setOnAction(e -> addPaused(getSelectedThumbCells(cell)));
|
||||
var recordLater = new MenuItem("Record Later");
|
||||
recordLater.setOnAction(e -> recordLater(getSelectedThumbCells(cell), true));
|
||||
var removeRecordLater = new MenuItem("Forget Model");
|
||||
removeRecordLater.setOnAction(e -> recordLater(getSelectedThumbCells(cell), false));
|
||||
var addRemoveBookmark = recorder.isMarkedForLaterRecording(model) ? removeRecordLater : recordLater;
|
||||
|
||||
var pause = new MenuItem("Pause Recording");
|
||||
pause.setOnAction(e -> pauseResumeAction(getSelectedThumbCells(cell), true));
|
||||
var resume = new MenuItem("Resume Recording");
|
||||
resume.setOnAction(e -> pauseResumeAction(getSelectedThumbCells(cell), false));
|
||||
var pauseResume = recorder.isSuspended(model) ? resume : pause;
|
||||
|
||||
var addToGroup = new MenuItem("Add to group");
|
||||
addToGroup.setOnAction(e -> new AddToGroupAction(this.getContent(), recorder, model).execute());
|
||||
var groupSubMenu = new ModelGroupMenuBuilder().model(model).recorder(recorder).node(grid).build();
|
||||
|
||||
var follow = new MenuItem("Follow");
|
||||
follow.setOnAction(e -> follow(getSelectedThumbCells(cell), true));
|
||||
var unfollow = new MenuItem("Unfollow");
|
||||
unfollow.setOnAction(e -> follow(getSelectedThumbCells(cell), false));
|
||||
var selectedModels = getSelectedThumbCells(cell).stream().map(ThumbCell::getModel).collect(Collectors.toList());
|
||||
|
||||
var ignore = new MenuItem("Ignore");
|
||||
ignore.setOnAction(e -> ignore(getSelectedThumbCells(cell)));
|
||||
|
@ -511,26 +474,28 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
var openInBrowser = createOpenInBrowser(cell);
|
||||
var sendTip = createTipMenuItem(cell);
|
||||
|
||||
configureItemsForSelection(cell, openInPlayer, copyUrl, sendTip);
|
||||
configureItemsForSelection(cell, copyUrl, sendTip);
|
||||
|
||||
ContextMenu contextMenu = new CustomMouseBehaviorContextMenu();
|
||||
contextMenu.setAutoHide(true);
|
||||
contextMenu.setHideOnEscape(true);
|
||||
contextMenu.setAutoFix(true);
|
||||
contextMenu.getItems().addAll(openInPlayer, new SeparatorMenuItem(), startStop);
|
||||
if (modelIsTrackedByRecorder) {
|
||||
contextMenu.getItems().addAll(pauseResume, recordLater);
|
||||
} else {
|
||||
contextMenu.getItems().addAll(recordUntil, addPaused, addRemoveBookmark);
|
||||
}
|
||||
Optional<ModelGroup> modelGroup = recorder.getModelGroup(model);
|
||||
contextMenu.getItems().add(modelGroup.isEmpty() ? addToGroup : groupSubMenu);
|
||||
contextMenu.getItems().add(new SeparatorMenuItem());
|
||||
if (site.supportsFollow()) {
|
||||
var followOrUnFollow = (this instanceof FollowedTab) ? unfollow : follow;
|
||||
followOrUnFollow.setDisable(!site.credentialsAvailable());
|
||||
contextMenu.getItems().add(followOrUnFollow);
|
||||
}
|
||||
|
||||
ModelMenuContributor.newContributor(getTabPane(), Config.getInstance(), recorder)
|
||||
.withStartStopCallback(m -> {
|
||||
getTabPane().setCursor(Cursor.DEFAULT);
|
||||
getSelectedThumbCells(cell).forEach(ThumbCell::update);
|
||||
})
|
||||
.withFollowCallback( (mdl, fllw, success) -> {
|
||||
if (Boolean.TRUE.equals(fllw) && Boolean.TRUE.equals(success)) {
|
||||
getThumbCell(mdl).ifPresent(this::showAddToFollowedAnimation);
|
||||
}
|
||||
if (Boolean.FALSE.equals(fllw)) {
|
||||
selectedThumbCells.clear();
|
||||
}
|
||||
})
|
||||
.contributeToMenu(selectedModels, contextMenu);
|
||||
|
||||
if (site.supportsTips()) {
|
||||
contextMenu.getItems().add(sendTip);
|
||||
}
|
||||
|
@ -544,18 +509,16 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
return contextMenu;
|
||||
}
|
||||
|
||||
private void recordLater(List<ThumbCell> list, boolean recordLater) {
|
||||
for (ThumbCell cell : list) {
|
||||
cell.recordLater(recordLater);
|
||||
}
|
||||
}
|
||||
|
||||
private void startRecordingWithTimeLimit(List<ThumbCell> list) {
|
||||
for (ThumbCell cell : list) {
|
||||
cell.getModel().setMarkedForLaterRecording(false);
|
||||
cell.startStopAction(true);
|
||||
new SetStopDateAction(cell, cell.getModel(), recorder).execute();
|
||||
private Optional<ThumbCell> getThumbCell(Model model) {
|
||||
for (Node node : grid.getChildren()) {
|
||||
if (node instanceof ThumbCell) {
|
||||
var thumbCell = (ThumbCell) node;
|
||||
if (Objects.equals(thumbCell.getModel(), model)) {
|
||||
return Optional.of(thumbCell);
|
||||
}
|
||||
}
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
private void refresh() {
|
||||
|
@ -570,12 +533,9 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
* check, if other cells are selected, too. in that case, we have to disable menu items, which make sense only for single selections. but only do that, if
|
||||
* the popup has been triggered on a selected cell. otherwise remove the selection and show the normal menu
|
||||
*/
|
||||
private void configureItemsForSelection(ThumbCell cell, MenuItem openInPlayer, MenuItem copyUrl, MenuItem sendTip) {
|
||||
private void configureItemsForSelection(ThumbCell cell, MenuItem copyUrl, MenuItem sendTip) {
|
||||
if (selectedThumbCells.size() > 1 || selectedThumbCells.size() == 1 && selectedThumbCells.get(0) != cell) {
|
||||
if (cell.isSelected()) {
|
||||
if (Config.getInstance().getSettings().singlePlayer) {
|
||||
openInPlayer.setDisable(true);
|
||||
}
|
||||
copyUrl.setDisable(true);
|
||||
sendTip.setDisable(true);
|
||||
} else {
|
||||
|
@ -616,6 +576,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
}
|
||||
|
||||
protected void follow(List<ThumbCell> selection, boolean follow) {
|
||||
|
||||
for (ThumbCell thumbCell : selection) {
|
||||
thumbCell.follow(follow).thenAccept(success -> {
|
||||
if (follow && Boolean.TRUE.equals(success)) {
|
||||
|
@ -691,32 +652,6 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
return 0;
|
||||
}
|
||||
|
||||
private void startStopAction(List<ThumbCell> selection, boolean start) {
|
||||
for (ThumbCell thumbCell : selection) {
|
||||
thumbCell.getModel().setSuspended(false);
|
||||
thumbCell.getModel().setMarkedForLaterRecording(false);
|
||||
thumbCell.startStopAction(start);
|
||||
}
|
||||
}
|
||||
|
||||
private void addPaused(List<ThumbCell> selection) {
|
||||
for (ThumbCell thumbCell : selection) {
|
||||
thumbCell.addInPausedState();
|
||||
}
|
||||
}
|
||||
|
||||
private void pauseResumeAction(List<ThumbCell> selection, boolean pause) {
|
||||
for (ThumbCell thumbCell : selection) {
|
||||
thumbCell.pauseResumeAction(pause);
|
||||
}
|
||||
}
|
||||
|
||||
private void startPlayer(List<ThumbCell> selection) {
|
||||
for (ThumbCell thumbCell : selection) {
|
||||
thumbCell.startPlayer();
|
||||
}
|
||||
}
|
||||
|
||||
private final EventHandler<MouseEvent> mouseClickListener = e -> {
|
||||
ThumbCell cell = (ThumbCell) e.getSource();
|
||||
if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) {
|
||||
|
|
|
@ -17,7 +17,7 @@ import java.util.concurrent.ExecutorService;
|
|||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.locks.ReentrantLock;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
|
@ -32,7 +32,6 @@ import ctbrec.StringUtil;
|
|||
import ctbrec.recorder.Recorder;
|
||||
import ctbrec.sites.Site;
|
||||
import ctbrec.ui.AutosizeAlert;
|
||||
import ctbrec.ui.DesktopIntegration;
|
||||
import ctbrec.ui.JavaFxModel;
|
||||
import ctbrec.ui.PreviewPopupHandler;
|
||||
import ctbrec.ui.StreamSourceSelectionDialog;
|
||||
|
@ -42,6 +41,7 @@ import ctbrec.ui.action.EditNotesAction;
|
|||
import ctbrec.ui.action.FollowAction;
|
||||
import ctbrec.ui.action.IgnoreModelsAction;
|
||||
import ctbrec.ui.action.OpenRecordingsDir;
|
||||
import ctbrec.ui.action.OpenUrlAction;
|
||||
import ctbrec.ui.action.PauseAction;
|
||||
import ctbrec.ui.action.PlayAction;
|
||||
import ctbrec.ui.action.RemoveTimeLimitAction;
|
||||
|
@ -662,7 +662,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
var removeTimeLimit = new MenuItem("Remove Time Limit");
|
||||
removeTimeLimit.setOnAction(e -> removeTimeLimit(selectedModels.get(0)));
|
||||
var openInBrowser = new MenuItem("Open in Browser");
|
||||
openInBrowser.setOnAction(e -> DesktopIntegration.open(selectedModels.get(0).getUrl()));
|
||||
openInBrowser.setOnAction(e -> openInBrowser(selectedModels));
|
||||
var openInPlayer = new MenuItem("Open in Player");
|
||||
openInPlayer.setOnAction(e -> openInPlayer(selectedModels.get(0)));
|
||||
var switchStreamSource = new MenuItem("Switch resolution");
|
||||
|
@ -698,7 +698,6 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
if (selectedModels.size() > 1) {
|
||||
copyUrl.setDisable(true);
|
||||
openInPlayer.setDisable(true);
|
||||
openInBrowser.setDisable(true);
|
||||
switchStreamSource.setDisable(true);
|
||||
notes.setDisable(true);
|
||||
}
|
||||
|
@ -706,6 +705,11 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
return menu;
|
||||
}
|
||||
|
||||
private void openInBrowser(ObservableList<JavaFxModel> selectedModels) {
|
||||
List<Model> models = selectedModels.stream().map(JavaFxModel::getDelegate).collect(Collectors.toList());
|
||||
new OpenUrlAction(getTabPane(), models).execute();
|
||||
}
|
||||
|
||||
private Menu createModelGroupMenu(ObservableList<JavaFxModel> selectedModels) {
|
||||
return new ModelGroupMenuBuilder() //
|
||||
.model(selectedModels.get(0)) //
|
||||
|
@ -760,19 +764,17 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
|||
return;
|
||||
}
|
||||
|
||||
Function<Model, Void> onSuccess = m -> {
|
||||
Consumer<Model> onSuccess = m -> {
|
||||
try {
|
||||
recorder.switchStreamSource(m);
|
||||
} catch (InvalidKeyException | NoSuchAlgorithmException | IllegalStateException | IOException e) {
|
||||
LOG.error(couldntSwitchHeaderText, e);
|
||||
showStreamSwitchErrorDialog(e);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
Function<Throwable, Void> onFail = t -> {
|
||||
Consumer<Throwable> onFail = t -> {
|
||||
LOG.error(couldntSwitchHeaderText, t);
|
||||
showStreamSwitchErrorDialog(t);
|
||||
return null;
|
||||
};
|
||||
StreamSourceSelectionDialog.show(getTabPane().getScene(), fxModel.getDelegate(), onSuccess, onFail);
|
||||
}
|
||||
|
|
|
@ -266,7 +266,11 @@ public class NextGenLocalRecorder implements Recorder {
|
|||
if (existing.isPresent()) {
|
||||
existing.get().setSuspended(model.isSuspended());
|
||||
existing.get().setMarkedForLaterRecording(model.isMarkedForLaterRecording());
|
||||
startRecordingProcess(existing.get());
|
||||
if (model.isMarkedForLaterRecording() && getRecordingProcesses().containsKey(model)) {
|
||||
stopRecording(model);
|
||||
} else {
|
||||
startRecordingProcess(existing.get());
|
||||
}
|
||||
} else {
|
||||
LOG.info("Model {} added", model);
|
||||
recorderLock.lock();
|
||||
|
|
Loading…
Reference in New Issue