Introduce new system to create the context menus

This commit is contained in:
0xb00bface 2021-06-12 12:23:10 +02:00
parent b32409379c
commit df91a71eb7
10 changed files with 359 additions and 126 deletions

View File

@ -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);
}

View File

@ -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());
}
}

View File

@ -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;

View File

@ -0,0 +1,7 @@
package ctbrec.ui.action;
@FunctionalInterface
public interface TriConsumer<T, U, V> {
void accept(T t, U u, V v);
}

View File

@ -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");

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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) {

View File

@ -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);
}

View File

@ -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();