Add model group context menu

This commit is contained in:
0xb00bface 2021-05-13 20:07:40 +02:00
parent 0c4f37f950
commit 0d0311fbfc
11 changed files with 442 additions and 182 deletions

View File

@ -40,6 +40,10 @@ public class AddToGroupAction {
} }
public void execute() { public void execute() {
execute(() -> {});
}
public void execute(Runnable callback) {
source.setCursor(Cursor.WAIT); source.setCursor(Cursor.WAIT);
try { try {
var dialog = new AddModelGroupDialog(); var dialog = new AddModelGroupDialog();
@ -66,6 +70,7 @@ public class AddToGroupAction {
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException e) { } catch (IOException | InvalidKeyException | NoSuchAlgorithmException e) {
Dialogs.showError(source.getScene(), "Add model to group", "Saving model group failed", e); Dialogs.showError(source.getScene(), "Add model to group", "Saving model group failed", e);
} finally { } finally {
callback.run();
source.setCursor(Cursor.DEFAULT); source.setCursor(Cursor.DEFAULT);
} }
} }

View File

@ -3,6 +3,7 @@ package ctbrec.ui.action;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.function.Consumer;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.ModelGroup; import ctbrec.ModelGroup;
@ -36,6 +37,10 @@ public class EditGroupAction {
} }
public void execute() { public void execute() {
execute(m -> {});
}
public void execute(Consumer<Model> callback) {
source.setCursor(Cursor.WAIT); source.setCursor(Cursor.WAIT);
try { try {
var dialog = new EditModelGroupDialog(model); var dialog = new EditModelGroupDialog(model);
@ -56,6 +61,7 @@ public class EditGroupAction {
} catch (Exception e) { } catch (Exception e) {
Dialogs.showError(source.getScene(), DIALOG_TITLE, "Editing model group failed", e); Dialogs.showError(source.getScene(), DIALOG_TITLE, "Editing model group failed", e);
} finally { } finally {
Optional.ofNullable(callback).ifPresent(c -> c.accept(model));
source.setCursor(Cursor.DEFAULT); source.setCursor(Cursor.DEFAULT);
} }
} }
@ -102,6 +108,7 @@ public class EditGroupAction {
urlList = FXCollections.observableList(modelGroup.getModelUrls()); urlList = FXCollections.observableList(modelGroup.getModelUrls());
urlList.addListener((ListChangeListener<String>) change -> urls = new ArrayList<>(urlList)); urlList.addListener((ListChangeListener<String>) change -> urls = new ArrayList<>(urlList));
urlListView = new ListView<>(urlList); urlListView = new ListView<>(urlList);
urlListView.setPrefWidth(600);
GridPane.setHgrow(urlListView, Priority.ALWAYS); GridPane.setHgrow(urlListView, Priority.ALWAYS);
var row = 0; var row = 0;

View File

@ -1,21 +1,33 @@
package ctbrec.ui.action; package ctbrec.ui.action;
import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.GlobalThreadPool; import ctbrec.GlobalThreadPool;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.ui.controls.Dialogs;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.scene.Cursor; import javafx.scene.Cursor;
import javafx.scene.Node; import javafx.scene.Node;
public class ModelMassEditAction { public class ModelMassEditAction {
private static final Logger LOG = LoggerFactory.getLogger(ModelMassEditAction.class);
protected List<? extends Model> models; protected List<? extends Model> models;
protected Consumer<Model> action; protected Consumer<Model> action;
protected Node source; protected Node source;
protected ModelMassEditAction() {
}
protected ModelMassEditAction(Node source, List<? extends Model> models) { protected ModelMassEditAction(Node source, List<? extends Model> models) {
this.source = source; this.source = source;
this.models = models; this.models = models;
@ -32,14 +44,36 @@ public class ModelMassEditAction {
} }
public void execute(Consumer<Model> callback) { public void execute(Consumer<Model> callback) {
Consumer<Model> cb = Objects.requireNonNull(callback, "Callback is null, call execute() instead");
source.setCursor(Cursor.WAIT);
for (Model model : models) {
GlobalThreadPool.submit(() -> { GlobalThreadPool.submit(() -> {
Platform.runLater(() -> source.setCursor(Cursor.WAIT));
Consumer<Model> cb = Objects.requireNonNull(callback, "Callback is null, call execute() instead");
List<Future<?>> futures = new LinkedList<>();
for (Model model : getModels()) {
futures.add(GlobalThreadPool.submit(() -> {
action.accept(model); action.accept(model);
cb.accept(model); cb.accept(model);
}));
}
Exception ex = null;
for (Future<?> future : futures) {
try {
future.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (ExecutionException e) {
ex = e;
}
}
if (ex != null) {
LOG.error("Error while executing model mass edit", ex);
Dialogs.showError(source.getScene(), "Error", "Error while execution action", ex);
}
Platform.runLater(() -> source.setCursor(Cursor.DEFAULT)); Platform.runLater(() -> source.setCursor(Cursor.DEFAULT));
}); });
} }
@SuppressWarnings("unchecked")
protected List<Model> getModels() {
return (List<Model>) models;
} }
} }

View File

@ -0,0 +1,49 @@
package ctbrec.ui.action;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import ctbrec.Model;
import ctbrec.ModelGroup;
import ctbrec.recorder.Recorder;
import ctbrec.ui.controls.Dialogs;
import javafx.application.Platform;
import javafx.scene.Node;
public class PauseGroupAction extends ModelMassEditAction {
private Recorder recorder;
private Model model;
public PauseGroupAction(Node source, Recorder recorder, Model model) {
super.source = source;
this.recorder = recorder;
this.model = model;
action = m -> {
try {
recorder.suspendRecording(m);
} catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
Platform.runLater(() -> Dialogs.showError(source.getScene(), "Couldn't pause model", "Pausing recording of " + m.getName() + " failed", e));
}
};
}
@Override
protected List<Model> getModels() {
Optional<ModelGroup> optionalGroup = recorder.getModelGroup(model);
if (optionalGroup.isPresent()) {
ModelGroup group = optionalGroup.get();
return recorder.getModels().stream() //
.filter(m -> group.getModelUrls().contains(m.getUrl())) //
.collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
}

View File

@ -0,0 +1,49 @@
package ctbrec.ui.action;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import ctbrec.Model;
import ctbrec.ModelGroup;
import ctbrec.recorder.Recorder;
import ctbrec.ui.controls.Dialogs;
import javafx.application.Platform;
import javafx.scene.Node;
public class ResumeGroupAction extends ModelMassEditAction {
private Recorder recorder;
private Model model;
public ResumeGroupAction(Node source, Recorder recorder, Model model) {
super.source = source;
this.recorder = recorder;
this.model = model;
action = m -> {
try {
recorder.resumeRecording(m);
} catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
Platform.runLater(() -> Dialogs.showError(source.getScene(), "Couldn't resume model", "Resuming recording of " + m.getName() + " failed", e));
}
};
}
@Override
protected List<Model> getModels() {
Optional<ModelGroup> optionalGroup = recorder.getModelGroup(model);
if (optionalGroup.isPresent()) {
ModelGroup group = optionalGroup.get();
return recorder.getModels().stream() //
.filter(m -> group.getModelUrls().contains(m.getUrl())) //
.collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
}

View File

@ -0,0 +1,49 @@
package ctbrec.ui.action;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import ctbrec.Model;
import ctbrec.ModelGroup;
import ctbrec.recorder.Recorder;
import ctbrec.ui.controls.Dialogs;
import javafx.application.Platform;
import javafx.scene.Node;
public class StopGroupAction extends ModelMassEditAction {
private Recorder recorder;
private Model model;
public StopGroupAction(Node source, Recorder recorder, Model model) {
super.source = source;
this.recorder = recorder;
this.model = model;
action = m -> {
try {
recorder.stopRecording(m);
} catch (InvalidKeyException | NoSuchAlgorithmException | IOException e) {
Platform.runLater(() -> Dialogs.showError(source.getScene(), "Couldn't stop model", "Stopping recording of " + m.getName() + " failed", e));
}
};
}
@Override
protected List<Model> getModels() {
Optional<ModelGroup> optionalGroup = recorder.getModelGroup(model);
if (optionalGroup.isPresent()) {
ModelGroup group = optionalGroup.get();
return recorder.getModels().stream() //
.filter(m -> group.getModelUrls().contains(m.getUrl())) //
.collect(Collectors.toList());
} else {
return Collections.emptyList();
}
}
}

View File

@ -0,0 +1,60 @@
package ctbrec.ui.action;
import static ctbrec.ui.controls.Dialogs.*;
import java.io.IOException;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ctbrec.Model;
import ctbrec.event.EventBusHolder;
import ctbrec.ui.SiteUiFactory;
import ctbrec.ui.TipDialog;
import javafx.scene.Cursor;
import javafx.scene.Node;
public class TipAction {
private static final Logger LOG = LoggerFactory.getLogger(TipAction.class);
private Model model;
private Node source;
public TipAction(Model model, Node source) {
this.model = model;
this.source = source;
}
public void execute() {
source.setCursor(Cursor.WAIT);
try {
var site = model.getSite();
var tipDialog = new TipDialog(source.getScene(), site);
tipDialog.showAndWait();
String tipText = tipDialog.getResult();
if (tipText != null) {
var df = new DecimalFormat("0.##");
try {
Number tokens = df.parse(tipText);
SiteUiFactory.getUi(site).login();
model.receiveTip(tokens.doubleValue());
Map<String, Object> event = new HashMap<>();
event.put("event", "tokens.sent");
event.put("amount", tokens.doubleValue());
EventBusHolder.BUS.post(event);
} catch (IOException ex) {
LOG.error("An error occurred while sending tip", ex);
showError(source.getScene(), "Couldn't send tip", "An error occurred while sending tip:", ex);
} catch (Exception ex) {
showError(source.getScene(), "Couldn't send tip", "You entered an invalid amount of tokens", ex);
}
}
} finally {
source.setCursor(Cursor.DEFAULT);
}
}
}

View File

@ -0,0 +1,65 @@
package ctbrec.ui.menu;
import java.util.Objects;
import java.util.function.Consumer;
import ctbrec.Model;
import ctbrec.recorder.Recorder;
import ctbrec.ui.action.EditGroupAction;
import ctbrec.ui.action.PauseGroupAction;
import ctbrec.ui.action.ResumeGroupAction;
import ctbrec.ui.action.StopGroupAction;
import javafx.scene.Node;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem;
public class ModelGroupMenuBuilder {
private Model model;
private Recorder recorder;
private Node source;
private Consumer<Model> callback;
public ModelGroupMenuBuilder model(Model model) {
this.model = Objects.requireNonNull(model, "Model cannot be null");
return this;
}
public ModelGroupMenuBuilder recorder(Recorder recorder) {
this.recorder = Objects.requireNonNull(recorder, "Recorder cannot be null");
return this;
}
public ModelGroupMenuBuilder node(Node source) {
this.source = Objects.requireNonNull(source, "Node cannot be null");
return this;
}
public ModelGroupMenuBuilder callback(Consumer<Model> callback) {
this.callback = callback;
return this;
}
public Menu build() {
Objects.requireNonNull(model, "Model has to be set");
Objects.requireNonNull(recorder, "Recorder has to be set");
Objects.requireNonNull(source, "Node has to be set");
var menu = new Menu("Group");
var editGroup = new MenuItem("Edit group");
editGroup.setOnAction(e -> new EditGroupAction(source, recorder, model).execute(callback));
var resumeAllOfGroup = new MenuItem("Resume all");
resumeAllOfGroup.setOnAction(e -> new ResumeGroupAction(source, recorder, model).execute(callback));
var pauseAllOfGroup = new MenuItem("Pause all");
pauseAllOfGroup.setOnAction(e -> new PauseGroupAction(source, recorder, model).execute(callback));
var stopAllOfGroup = new MenuItem("Remove all");
stopAllOfGroup.setOnAction(e -> new StopGroupAction(source, recorder, model).execute(callback));
menu.getItems().addAll(editGroup, resumeAllOfGroup, pauseAllOfGroup, stopAllOfGroup);
return menu;
}
}

View File

@ -614,7 +614,7 @@ public class ThumbCell extends StackPane {
this.index = index; this.index = index;
} }
private void update() { protected void update() {
model.setSuspended(recorder.isSuspended(model)); model.setSuspended(recorder.isSuspended(model));
model.setMarkedForLaterRecording(recorder.isMarkedForLaterRecording(model)); model.setMarkedForLaterRecording(recorder.isMarkedForLaterRecording(model));
setRecording(recorder.isTracked(model)); setRecording(recorder.isTracked(model));

View File

@ -1,10 +1,7 @@
package ctbrec.ui.tabs; package ctbrec.ui.tabs;
import static ctbrec.ui.controls.Dialogs.*;
import java.io.IOException; import java.io.IOException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
import java.text.DecimalFormat;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
@ -30,7 +27,6 @@ import ctbrec.Config;
import ctbrec.GlobalThreadPool; import ctbrec.GlobalThreadPool;
import ctbrec.Model; import ctbrec.Model;
import ctbrec.ModelGroup; import ctbrec.ModelGroup;
import ctbrec.event.EventBusHolder;
import ctbrec.recorder.Recorder; import ctbrec.recorder.Recorder;
import ctbrec.sites.Site; import ctbrec.sites.Site;
import ctbrec.sites.mfc.MyFreeCamsClient; import ctbrec.sites.mfc.MyFreeCamsClient;
@ -38,18 +34,18 @@ import ctbrec.sites.mfc.MyFreeCamsModel;
import ctbrec.ui.AutosizeAlert; import ctbrec.ui.AutosizeAlert;
import ctbrec.ui.DesktopIntegration; import ctbrec.ui.DesktopIntegration;
import ctbrec.ui.SiteUiFactory; import ctbrec.ui.SiteUiFactory;
import ctbrec.ui.TipDialog;
import ctbrec.ui.TokenLabel; import ctbrec.ui.TokenLabel;
import ctbrec.ui.action.AddToGroupAction; import ctbrec.ui.action.AddToGroupAction;
import ctbrec.ui.action.EditGroupAction;
import ctbrec.ui.action.IgnoreModelsAction; import ctbrec.ui.action.IgnoreModelsAction;
import ctbrec.ui.action.OpenRecordingsDir; import ctbrec.ui.action.OpenRecordingsDir;
import ctbrec.ui.action.SetStopDateAction; import ctbrec.ui.action.SetStopDateAction;
import ctbrec.ui.action.TipAction;
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu; import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
import ctbrec.ui.controls.FasterVerticalScrollPaneSkin; import ctbrec.ui.controls.FasterVerticalScrollPaneSkin;
import ctbrec.ui.controls.SearchBox; import ctbrec.ui.controls.SearchBox;
import ctbrec.ui.controls.SearchPopover; import ctbrec.ui.controls.SearchPopover;
import ctbrec.ui.controls.SearchPopoverTreeList; import ctbrec.ui.controls.SearchPopoverTreeList;
import ctbrec.ui.menu.ModelGroupMenuBuilder;
import javafx.animation.FadeTransition; import javafx.animation.FadeTransition;
import javafx.animation.Interpolator; import javafx.animation.Interpolator;
import javafx.animation.ParallelTransition; import javafx.animation.ParallelTransition;
@ -80,7 +76,6 @@ import javafx.scene.control.ProgressIndicator;
import javafx.scene.control.ScrollPane; import javafx.scene.control.ScrollPane;
import javafx.scene.control.SeparatorMenuItem; import javafx.scene.control.SeparatorMenuItem;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.scene.control.TabPane;
import javafx.scene.control.TextField; import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip; import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView; import javafx.scene.image.ImageView;
@ -96,7 +91,6 @@ import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority; import javafx.scene.layout.Priority;
import javafx.scene.layout.StackPane; import javafx.scene.layout.StackPane;
import javafx.scene.transform.Transform;
import javafx.util.Duration; import javafx.util.Duration;
public class ThumbOverviewTab extends Tab implements TabSelectionListener { public class ThumbOverviewTab extends Tab implements TabSelectionListener {
@ -152,7 +146,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
SearchBox filterInput = new SearchBox(false); SearchBox filterInput = new SearchBox(false);
filterInput.setPromptText("Filter models on this page"); filterInput.setPromptText("Filter models on this page");
filterInput.textProperty().addListener( (observableValue, oldValue, newValue) -> { filterInput.textProperty().addListener((observableValue, oldValue, newValue) -> {
filter = filterInput.getText(); filter = filterInput.getText();
gridLock.lock(); gridLock.lock();
try { try {
@ -173,7 +167,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
searchInput.prefWidth(200); searchInput.prefWidth(200);
searchInput.textProperty().addListener(search()); searchInput.textProperty().addListener(search());
searchInput.addEventHandler(KeyEvent.KEY_PRESSED, evt -> { searchInput.addEventHandler(KeyEvent.KEY_PRESSED, evt -> {
if(evt.getCode() == KeyCode.ESCAPE) { if (evt.getCode() == KeyCode.ESCAPE) {
popover.hide(); popover.hide();
} }
}); });
@ -201,7 +195,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
topBar.getChildren().addAll(tokenBalance, buyTokens); topBar.getChildren().addAll(tokenBalance, buyTokens);
tokenBalance.loadBalance(); tokenBalance.loadBalance();
} }
if(site.supportsSearch()) { if (site.supportsSearch()) {
topBar.getChildren().add(searchInput); topBar.getChildren().add(searchInput);
} }
BorderPane.setMargin(topBar, new Insets(0, 5, 0, 5)); BorderPane.setMargin(topBar, new Insets(0, 5, 0, 5));
@ -232,7 +226,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
HBox thumbSizeSelector = new HBox(5); HBox thumbSizeSelector = new HBox(5);
Label l = new Label("Thumb Size"); Label l = new Label("Thumb Size");
l.setPadding(new Insets(5,0,0,0)); l.setPadding(new Insets(5, 0, 0, 0));
thumbSizeSelector.getChildren().add(l); thumbSizeSelector.getChildren().add(l);
List<Integer> thumbWidths = new ArrayList<>(); List<Integer> thumbWidths = new ArrayList<>();
thumbWidths.add(180); thumbWidths.add(180);
@ -249,7 +243,6 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
thumbSizeSelector.getChildren().add(thumbWidth); thumbSizeSelector.getChildren().add(thumbWidth);
BorderPane.setMargin(thumbSizeSelector, new Insets(5)); BorderPane.setMargin(thumbSizeSelector, new Insets(5));
BorderPane bottomPane = new BorderPane(); BorderPane bottomPane = new BorderPane();
bottomPane.setLeft(pagination); bottomPane.setLeft(pagination);
bottomPane.setRight(thumbSizeSelector); bottomPane.setRight(thumbSizeSelector);
@ -281,7 +274,6 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
changePageTo(page); changePageTo(page);
} }
private void previousPage() { private void previousPage() {
int page = updateService.getPage(); int page = updateService.getPage();
page = Math.max(1, --page); page = Math.max(1, --page);
@ -296,10 +288,10 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
private ChangeListener<? super String> search() { private ChangeListener<? super String> search() {
return (observableValue, oldValue, newValue) -> { return (observableValue, oldValue, newValue) -> {
if(searchTask != null) { if (searchTask != null) {
searchTask.cancel(true); searchTask.cancel(true);
} }
if(newValue.length() < 2) { if (newValue.length() < 2) {
return; return;
} }
searchTask = new ThumbOverviewTabSearchTask(site, popover, popoverTreeList, newValue); searchTask = new ThumbOverviewTabSearchTask(site, popover, popoverTreeList, newValue);
@ -311,7 +303,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
int width = Config.getInstance().getSettings().thumbWidth; int width = Config.getInstance().getSettings().thumbWidth;
thumbWidth.getSelectionModel().select(Integer.valueOf(width)); thumbWidth.getSelectionModel().select(Integer.valueOf(width));
for (Node node : grid.getChildren()) { for (Node node : grid.getChildren()) {
if(node instanceof ThumbCell) { if (node instanceof ThumbCell) {
ThumbCell cell = (ThumbCell) node; ThumbCell cell = (ThumbCell) node;
cell.setThumbWidth(width); cell.setThumbWidth(width);
} }
@ -326,7 +318,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
int page = Integer.parseInt(pageInput.getText()); int page = Integer.parseInt(pageInput.getText());
page = Math.max(1, page); page = Math.max(1, page);
changePageTo(page); changePageTo(page);
} catch(NumberFormatException e) { } catch (NumberFormatException e) {
// noop // noop
} finally { } finally {
pageInput.setText(Integer.toString(updateService.getPage())); pageInput.setText(Integer.toString(updateService.getPage()));
@ -353,7 +345,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
} }
protected void onSuccess() { protected void onSuccess() {
if(updatesSuspended) { if (updatesSuspended) {
return; return;
} }
List<Model> models = filterIgnoredModels(updateService.getValue()); List<Model> models = filterIgnoredModels(updateService.getValue());
@ -362,9 +354,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
private List<Model> filterIgnoredModels(List<Model> models) { private List<Model> filterIgnoredModels(List<Model> models) {
List<String> ignored = Config.getInstance().getSettings().ignoredModels; List<String> ignored = Config.getInstance().getSettings().ignoredModels;
return models.stream() return models.stream().filter(m -> !ignored.contains(m.getUrl())).collect(Collectors.toList());
.filter(m -> !ignored.contains(m.getUrl()))
.collect(Collectors.toList());
} }
protected void updateGrid(List<? extends Model> models) { protected void updateGrid(List<? extends Model> models) {
@ -397,19 +387,20 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
for (Model model : models) { for (Model model : models) {
boolean found = false; boolean found = false;
for (Node node : nodes) { // NOSONAR for (Node node : nodes) { // NOSONAR
if (!(node instanceof ThumbCell)) continue; if (!(node instanceof ThumbCell))
continue;
ThumbCell cell = (ThumbCell) node; ThumbCell cell = (ThumbCell) node;
if(cell.getModel().equals(model)) { if (cell.getModel().equals(model)) {
found = true; found = true;
cell.setModel(model); cell.setModel(model);
if(index != cell.getIndex()) { if (index != cell.getIndex()) {
cell.setIndex(index); cell.setIndex(index);
positionChangedOrNew.add(cell); positionChangedOrNew.add(cell);
} }
break; break;
} }
} }
if(!found) { if (!found) {
ThumbCell newCell = createThumbCell(model, recorder); ThumbCell newCell = createThumbCell(model, recorder);
newCell.setIndex(index); newCell.setIndex(index);
positionChangedOrNew.add(newCell); positionChangedOrNew.add(newCell);
@ -422,7 +413,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
private void rearrangeCells(ObservableList<Node> nodes, List<ThumbCell> positionChangedOrNew) { private void rearrangeCells(ObservableList<Node> nodes, List<ThumbCell> positionChangedOrNew) {
for (ThumbCell thumbCell : positionChangedOrNew) { for (ThumbCell thumbCell : positionChangedOrNew) {
nodes.remove(thumbCell); nodes.remove(thumbCell);
if(thumbCell.getIndex() < nodes.size()) { if (thumbCell.getIndex() < nodes.size()) {
nodes.add(thumbCell.getIndex(), thumbCell); nodes.add(thumbCell.getIndex(), thumbCell);
} else { } else {
nodes.add(thumbCell); nodes.add(thumbCell);
@ -433,7 +424,8 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
private void removeModelsMissingInUpdate(ObservableList<Node> nodes, List<? extends Model> models) { private void removeModelsMissingInUpdate(ObservableList<Node> nodes, List<? extends Model> models) {
for (Iterator<Node> iterator = nodes.iterator(); iterator.hasNext();) { for (Iterator<Node> iterator = nodes.iterator(); iterator.hasNext();) {
Node node = iterator.next(); Node node = iterator.next();
if (!(node instanceof ThumbCell)) continue; if (!(node instanceof ThumbCell))
continue;
ThumbCell cell = (ThumbCell) node; ThumbCell cell = (ThumbCell) node;
if (!models.contains(cell.getModel())) { if (!models.contains(cell.getModel())) {
iterator.remove(); iterator.remove();
@ -453,7 +445,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
event.consume(); event.consume();
}); });
newCell.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> { newCell.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
if(popup != null) { if (popup != null) {
popup.hide(); popup.hide();
popup = null; popup = null;
} }
@ -469,14 +461,6 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
return newCell; return newCell;
} }
// private ContextMenu createContextMenu(ThumbCell cell) {
// return new ModelContextMenu.Builder()
// .model(cell.getModel())
// .node(grid)
// .recorder(recorder)
// .build();
// }
private ContextMenu createContextMenu(ThumbCell cell) { private ContextMenu createContextMenu(ThumbCell cell) {
var model = cell.getModel(); var model = cell.getModel();
boolean modelIsTrackedByRecorder = recorder.isTracked(model); boolean modelIsTrackedByRecorder = recorder.isTracked(model);
@ -507,16 +491,15 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
resume.setOnAction(e -> pauseResumeAction(getSelectedThumbCells(cell), false)); resume.setOnAction(e -> pauseResumeAction(getSelectedThumbCells(cell), false));
var pauseResume = recorder.isSuspended(model) ? resume : pause; 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"); var follow = new MenuItem("Follow");
follow.setOnAction(e -> follow(getSelectedThumbCells(cell), true)); follow.setOnAction(e -> follow(getSelectedThumbCells(cell), true));
var unfollow = new MenuItem("Unfollow"); var unfollow = new MenuItem("Unfollow");
unfollow.setOnAction(e -> follow(getSelectedThumbCells(cell), false)); unfollow.setOnAction(e -> follow(getSelectedThumbCells(cell), false));
var addToGroup = new MenuItem("Add to group");
addToGroup.setOnAction(e -> addToGroup(model));
var editGroup = new MenuItem("Edit group");
editGroup.setOnAction(e -> editGroup(model));
var ignore = new MenuItem("Ignore"); var ignore = new MenuItem("Ignore");
ignore.setOnAction(e -> ignore(getSelectedThumbCells(cell))); ignore.setOnAction(e -> ignore(getSelectedThumbCells(cell)));
@ -542,6 +525,8 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
} else { } else {
contextMenu.getItems().addAll(recordUntil, addPaused, addRemoveBookmark); contextMenu.getItems().addAll(recordUntil, addPaused, addRemoveBookmark);
} }
Optional<ModelGroup> modelGroup = recorder.getModelGroup(model);
contextMenu.getItems().add(modelGroup.isEmpty() ? addToGroup : groupSubMenu);
contextMenu.getItems().add(new SeparatorMenuItem()); contextMenu.getItems().add(new SeparatorMenuItem());
if (site.supportsFollow()) { if (site.supportsFollow()) {
var followOrUnFollow = (this instanceof FollowedTab) ? unfollow : follow; var followOrUnFollow = (this instanceof FollowedTab) ? unfollow : follow;
@ -551,8 +536,6 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
if (site.supportsTips()) { if (site.supportsTips()) {
contextMenu.getItems().add(sendTip); contextMenu.getItems().add(sendTip);
} }
Optional<ModelGroup> modelGroup = recorder.getModelGroup(model);
contextMenu.getItems().add(modelGroup.isEmpty() ? addToGroup : editGroup);
contextMenu.getItems().addAll(copyUrl, openInBrowser, ignore, refresh, openRecDir); contextMenu.getItems().addAll(copyUrl, openInBrowser, ignore, refresh, openRecDir);
if (model instanceof MyFreeCamsModel && Objects.equals(System.getenv("CTBREC_DEV"), "1")) { if (model instanceof MyFreeCamsModel && Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
var debug = new MenuItem("debug"); var debug = new MenuItem("debug");
@ -563,14 +546,6 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
return contextMenu; return contextMenu;
} }
private void editGroup(Model model) {
new EditGroupAction(this.getContent(), recorder, model).execute();
}
private void addToGroup(Model model) {
new AddToGroupAction(this.getContent(), recorder, model).execute();
}
private void recordLater(List<ThumbCell> list, boolean recordLater) { private void recordLater(List<ThumbCell> list, boolean recordLater) {
for (ThumbCell cell : list) { for (ThumbCell cell : list) {
cell.recordLater(recordLater); cell.recordLater(recordLater);
@ -593,14 +568,14 @@ 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 * 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
* selection and show the normal menu * 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 openInPlayer, MenuItem copyUrl, MenuItem sendTip) {
if (selectedThumbCells.size() > 1 || selectedThumbCells.size() == 1 && selectedThumbCells.get(0) != cell) { if (selectedThumbCells.size() > 1 || selectedThumbCells.size() == 1 && selectedThumbCells.get(0) != cell) {
if(cell.isSelected()) { if (cell.isSelected()) {
if(Config.getInstance().getSettings().singlePlayer) { if (Config.getInstance().getSettings().singlePlayer) {
openInPlayer.setDisable(true); openInPlayer.setDisable(true);
} }
copyUrl.setDisable(true); copyUrl.setDisable(true);
@ -612,52 +587,30 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
} }
private MenuItem createOpenInBrowser(ThumbCell cell) { private MenuItem createOpenInBrowser(ThumbCell cell) {
MenuItem openInBrowser = new MenuItem("Open in browser"); var openInBrowser = new MenuItem("Open in browser");
openInBrowser.setOnAction(e -> DesktopIntegration.open(cell.getModel().getUrl())); openInBrowser.setOnAction(e -> DesktopIntegration.open(cell.getModel().getUrl()));
return openInBrowser; return openInBrowser;
} }
private MenuItem createCopyUrlMenuItem(ThumbCell cell) { private MenuItem createCopyUrlMenuItem(ThumbCell cell) {
MenuItem copyUrl = new MenuItem("Copy URL"); var copyUrl = new MenuItem("Copy URL");
copyUrl.setOnAction(e -> { copyUrl.setOnAction(e -> {
final Clipboard clipboard = Clipboard.getSystemClipboard(); final var content = new ClipboardContent();
final ClipboardContent content = new ClipboardContent();
content.putString(cell.getModel().getUrl()); content.putString(cell.getModel().getUrl());
clipboard.setContent(content); Clipboard.getSystemClipboard().setContent(content);
}); });
return copyUrl; return copyUrl;
} }
private MenuItem createTipMenuItem(ThumbCell cell) { private MenuItem createTipMenuItem(ThumbCell cell) {
MenuItem sendTip = new MenuItem("Send Tip"); var sendTip = new MenuItem("Send Tip");
sendTip.setOnAction(e -> { sendTip.setOnAction(e -> new TipAction(cell.getModel(), cell));
TipDialog tipDialog = new TipDialog(getTabPane().getScene(), site);
tipDialog.showAndWait();
String tipText = tipDialog.getResult();
if(tipText != null) {
DecimalFormat df = new DecimalFormat("0.##");
try {
Number tokens = df.parse(tipText);
SiteUiFactory.getUi(site).login();
cell.getModel().receiveTip(tokens.doubleValue());
Map<String, Object> event = new HashMap<>();
event.put("event", "tokens.sent");
event.put("amount", tokens.doubleValue());
EventBusHolder.BUS.post(event);
} catch (IOException ex) {
LOG.error("An error occurred while sending tip", ex);
showError(getTabPane().getScene(), "Couldn't send tip", "An error occurred while sending tip:", ex);
} catch (Exception ex) {
showError(getTabPane().getScene(), "Couldn't send tip", "You entered an invalid amount of tokens", ex);
}
}
});
sendTip.setDisable(!site.credentialsAvailable()); sendTip.setDisable(!site.credentialsAvailable());
return sendTip; return sendTip;
} }
private List<ThumbCell> getSelectedThumbCells(ThumbCell cell) { private List<ThumbCell> getSelectedThumbCells(ThumbCell cell) {
if(selectedThumbCells.isEmpty()) { if (selectedThumbCells.isEmpty()) {
return Collections.singletonList(cell); return Collections.singletonList(cell);
} else { } else {
return selectedThumbCells; return selectedThumbCells;
@ -672,22 +625,19 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
} }
}); });
} }
if(!follow) { if (!follow) {
selectedThumbCells.clear(); selectedThumbCells.clear();
} }
} }
protected void ignore(List<ThumbCell> selection) { protected void ignore(List<ThumbCell> selection) {
Map<Model, ThumbCell> thumbcells = new HashMap<>(); Map<Model, ThumbCell> thumbcells = new HashMap<>();
List<Model> selectedModels = selection.stream() List<Model> selectedModels = selection.stream().map(tc -> {
.map(tc -> {
thumbcells.put(tc.getModel(), tc); thumbcells.put(tc.getModel(), tc);
return tc; return tc;
}) }).map(ThumbCell::getModel).collect(Collectors.toList());
.map(ThumbCell::getModel)
.collect(Collectors.toList());
new IgnoreModelsAction(grid, selectedModels, recorder, false).execute(m -> { new IgnoreModelsAction(grid, selectedModels, recorder, false).execute(m -> {
ThumbCell thumbCell = thumbcells.get(m); var thumbCell = thumbcells.get(m);
grid.getChildren().remove(thumbCell); grid.getChildren().remove(thumbCell);
selectedThumbCells.remove(thumbCell); selectedThumbCells.remove(thumbCell);
}); });
@ -695,45 +645,45 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
private void showAddToFollowedAnimation(ThumbCell thumbCell) { private void showAddToFollowedAnimation(ThumbCell thumbCell) {
Platform.runLater(() -> { Platform.runLater(() -> {
Transform tx = thumbCell.getLocalToParentTransform(); var tx = thumbCell.getLocalToParentTransform();
ImageView iv = new ImageView(); var iv = new ImageView();
iv.setFitWidth(thumbCell.getWidth()); iv.setFitWidth(thumbCell.getWidth());
root.getChildren().add(iv); root.getChildren().add(iv);
StackPane.setAlignment(iv, Pos.TOP_LEFT); StackPane.setAlignment(iv, Pos.TOP_LEFT);
iv.setImage(thumbCell.getImage()); iv.setImage(thumbCell.getImage());
double scrollPaneTopLeft = scrollPane.getVvalue() * (grid.getHeight() - scrollPane.getViewportBounds().getHeight()); double scrollPaneTopLeft = scrollPane.getVvalue() * (grid.getHeight() - scrollPane.getViewportBounds().getHeight());
double offsetInViewPort = tx.getTy() - scrollPaneTopLeft; double offsetInViewPort = tx.getTy() - scrollPaneTopLeft;
int duration = 500; var duration = 500;
TranslateTransition translate = new TranslateTransition(Duration.millis(duration), iv); var translate = new TranslateTransition(Duration.millis(duration), iv);
translate.setFromX(0); translate.setFromX(0);
translate.setFromY(0); translate.setFromY(0);
translate.setByX(-tx.getTx() - 200); translate.setByX(-tx.getTx() - 200);
TabProvider tabProvider = SiteUiFactory.getUi(site).getTabProvider(); var tabProvider = SiteUiFactory.getUi(site).getTabProvider();
Tab followedTab = tabProvider.getFollowedTab(); var followedTab = tabProvider.getFollowedTab();
translate.setByY(-offsetInViewPort + getFollowedTabYPosition(followedTab)); translate.setByY(-offsetInViewPort + getFollowedTabYPosition(followedTab));
StackPane.setMargin(iv, new Insets(offsetInViewPort, 0, 0, tx.getTx())); StackPane.setMargin(iv, new Insets(offsetInViewPort, 0, 0, tx.getTx()));
translate.setInterpolator(Interpolator.EASE_BOTH); translate.setInterpolator(Interpolator.EASE_BOTH);
FadeTransition fade = new FadeTransition(Duration.millis(duration), iv); var fade = new FadeTransition(Duration.millis(duration), iv);
fade.setFromValue(1); fade.setFromValue(1);
fade.setToValue(.3); fade.setToValue(.3);
ScaleTransition scale = new ScaleTransition(Duration.millis(duration), iv); var scale = new ScaleTransition(Duration.millis(duration), iv);
scale.setToX(0.1); scale.setToX(0.1);
scale.setToY(0.1); scale.setToY(0.1);
ParallelTransition pt = new ParallelTransition(translate, scale); var pt = new ParallelTransition(translate, scale);
pt.play(); pt.play();
pt.setOnFinished(evt -> root.getChildren().remove(iv)); pt.setOnFinished(evt -> root.getChildren().remove(iv));
FollowTabBlinkTransition blink = new FollowTabBlinkTransition(followedTab); var blink = new FollowTabBlinkTransition(followedTab);
blink.play(); blink.play();
}); });
} }
private double getFollowedTabYPosition(Tab followedTab) { private double getFollowedTabYPosition(Tab followedTab) {
TabPane tabPane = getTabPane(); var tabPane = getTabPane();
int idx = Math.max(0, tabPane.getTabs().indexOf(followedTab)); int idx = Math.max(0, tabPane.getTabs().indexOf(followedTab));
for (Node node : tabPane.getChildrenUnmodifiable()) { for (Node node : tabPane.getChildrenUnmodifiable()) {
Parent p = (Parent) node; Parent p = (Parent) node;
for (Node child : p.getChildrenUnmodifiable()) { for (Node child : p.getChildrenUnmodifiable()) {
if(child.getStyleClass().contains("headers-region")) { if (child.getStyleClass().contains("headers-region")) {
Parent tabContainer = (Parent) child; Parent tabContainer = (Parent) child;
Node tab = tabContainer.getChildrenUnmodifiable().get(tabContainer.getChildrenUnmodifiable().size() - idx - 1); Node tab = tabContainer.getChildrenUnmodifiable().get(tabContainer.getChildrenUnmodifiable().size() - idx - 1);
return tab.getLayoutX() - 85; return tab.getLayoutX() - 85;
@ -784,14 +734,14 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
}; };
protected void onFail(WorkerStateEvent event) { protected void onFail(WorkerStateEvent event) {
if(updatesSuspended) { if (updatesSuspended) {
return; return;
} }
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, getTabPane().getScene()); Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, getTabPane().getScene());
alert.setTitle("Error"); alert.setTitle("Error");
alert.setHeaderText("Couldn't fetch model list"); alert.setHeaderText("Couldn't fetch model list");
if(event.getSource().getException() != null) { if (event.getSource().getException() != null) {
if(event.getSource().getException() instanceof SocketTimeoutException) { if (event.getSource().getException() instanceof SocketTimeoutException) {
LOG.debug("Fetching model list timed out"); LOG.debug("Fetching model list timed out");
return; return;
} else { } else {
@ -806,8 +756,10 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
void filter() { void filter() {
filteredThumbCells.sort((c1, c2) -> { filteredThumbCells.sort((c1, c2) -> {
if (c1.getIndex() < c2.getIndex()) return -1; if (c1.getIndex() < c2.getIndex())
if (c1.getIndex() > c2.getIndex()) return 1; return -1;
if (c1.getIndex() > c2.getIndex())
return 1;
return c1.getModel().getName().compareTo(c2.getModel().getName()); return c1.getModel().getName().compareTo(c2.getModel().getName());
}); });
@ -835,7 +787,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
for (Iterator<ThumbCell> iterator = filteredThumbCells.iterator(); iterator.hasNext();) { for (Iterator<ThumbCell> iterator = filteredThumbCells.iterator(); iterator.hasNext();) {
ThumbCell thumbCell = iterator.next(); ThumbCell thumbCell = iterator.next();
Model m = thumbCell.getModel(); Model m = thumbCell.getModel();
if(matches(m, filter)) { if (matches(m, filter)) {
iterator.remove(); iterator.remove();
insert(thumbCell); insert(thumbCell);
} }
@ -852,11 +804,11 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
private void moveActiveRecordingsToFront() { private void moveActiveRecordingsToFront() {
List<ThumbCell> thumbsToMove = new ArrayList<>(); List<ThumbCell> thumbsToMove = new ArrayList<>();
ObservableList<Node> thumbs = grid.getChildren(); ObservableList<Node> thumbs = grid.getChildren();
for (int i = thumbs.size()-1; i >= 0; i--) { for (int i = thumbs.size() - 1; i >= 0; i--) {
Node node = thumbs.get(i); Node node = thumbs.get(i);
if(node instanceof ThumbCell) { if (node instanceof ThumbCell) {
ThumbCell thumb = (ThumbCell) thumbs.get(i); ThumbCell thumb = (ThumbCell) thumbs.get(i);
if(recorder.isTracked(thumb.getModel())) { if (recorder.isTracked(thumb.getModel())) {
thumbs.remove(i); thumbs.remove(i);
thumbsToMove.add(0, thumb); thumbsToMove.add(0, thumb);
} }
@ -866,11 +818,11 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
} }
private void insert(ThumbCell thumbCell) { private void insert(ThumbCell thumbCell) {
if(grid.getChildren().contains(thumbCell)) { if (grid.getChildren().contains(thumbCell)) {
return; return;
} }
if(thumbCell.getIndex() < grid.getChildren().size()-1) { if (thumbCell.getIndex() < grid.getChildren().size() - 1) {
grid.getChildren().add(thumbCell.getIndex(), thumbCell); grid.getChildren().add(thumbCell.getIndex(), thumbCell);
} else { } else {
grid.getChildren().add(thumbCell); grid.getChildren().add(thumbCell);
@ -913,7 +865,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
} }
} else { } else {
var negated = false; var negated = false;
if(token.startsWith("!")) { if (token.startsWith("!")) {
negated = true; negated = true;
token = token.substring(1); token = token.substring(1);
} }
@ -990,6 +942,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
} }
private static int threadCounter = 0; private static int threadCounter = 0;
private static ThreadFactory createThreadFactory() { private static ThreadFactory createThreadFactory() {
return r -> { return r -> {
Thread t = new Thread(r); Thread t = new Thread(r);

View File

@ -38,7 +38,6 @@ import ctbrec.ui.PreviewPopupHandler;
import ctbrec.ui.StreamSourceSelectionDialog; import ctbrec.ui.StreamSourceSelectionDialog;
import ctbrec.ui.action.AddToGroupAction; import ctbrec.ui.action.AddToGroupAction;
import ctbrec.ui.action.CheckModelAccountAction; import ctbrec.ui.action.CheckModelAccountAction;
import ctbrec.ui.action.EditGroupAction;
import ctbrec.ui.action.EditNotesAction; import ctbrec.ui.action.EditNotesAction;
import ctbrec.ui.action.FollowAction; import ctbrec.ui.action.FollowAction;
import ctbrec.ui.action.IgnoreModelsAction; import ctbrec.ui.action.IgnoreModelsAction;
@ -57,6 +56,7 @@ import ctbrec.ui.controls.Dialogs;
import ctbrec.ui.controls.SearchBox; import ctbrec.ui.controls.SearchBox;
import ctbrec.ui.controls.autocomplete.AutoFillTextField; import ctbrec.ui.controls.autocomplete.AutoFillTextField;
import ctbrec.ui.controls.autocomplete.ObservableListSuggester; import ctbrec.ui.controls.autocomplete.ObservableListSuggester;
import ctbrec.ui.menu.ModelGroupMenuBuilder;
import ctbrec.ui.tabs.TabSelectionListener; import ctbrec.ui.tabs.TabSelectionListener;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.beans.property.SimpleObjectProperty; import javafx.beans.property.SimpleObjectProperty;
@ -75,6 +75,7 @@ import javafx.scene.control.Alert;
import javafx.scene.control.Button; import javafx.scene.control.Button;
import javafx.scene.control.ContextMenu; import javafx.scene.control.ContextMenu;
import javafx.scene.control.Label; import javafx.scene.control.Label;
import javafx.scene.control.Menu;
import javafx.scene.control.MenuItem; import javafx.scene.control.MenuItem;
import javafx.scene.control.ScrollPane; import javafx.scene.control.ScrollPane;
import javafx.scene.control.SelectionMode; import javafx.scene.control.SelectionMode;
@ -154,7 +155,6 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
scrollPane.setFitToWidth(true); scrollPane.setFitToWidth(true);
BorderPane.setMargin(scrollPane, new Insets(5)); BorderPane.setMargin(scrollPane, new Insets(5));
table.setEditable(true); table.setEditable(true);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
var previewPopupHandler = new PreviewPopupHandler(table); var previewPopupHandler = new PreviewPopupHandler(table);
@ -168,7 +168,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
preview.setCellValueFactory(cdf -> new SimpleStringProperty("")); preview.setCellValueFactory(cdf -> new SimpleStringProperty(""));
preview.setEditable(false); preview.setEditable(false);
preview.setId("preview"); preview.setId("preview");
if(!Config.getInstance().getSettings().livePreviews) { if (!Config.getInstance().getSettings().livePreviews) {
preview.setVisible(false); preview.setVisible(false);
} }
TableColumn<JavaFxModel, ModelName> name = new TableColumn<>("Model"); TableColumn<JavaFxModel, ModelName> name = new TableColumn<>("Model");
@ -286,8 +286,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
HBox.setHgrow(model, Priority.ALWAYS); HBox.setHgrow(model, Priority.ALWAYS);
model.setPromptText("e.g. MyFreeCams:ModelName or an URL like https://chaturbate.com/modelname/"); model.setPromptText("e.g. MyFreeCams:ModelName or an URL like https://chaturbate.com/modelname/");
model.onActionHandler(this::addModel); model.onActionHandler(this::addModel);
model.setTooltip(new Tooltip("To add a model enter SiteName:ModelName\n" + model.setTooltip(new Tooltip("To add a model enter SiteName:ModelName\n" + "press ENTER to confirm a suggested site name"));
"press ENTER to confirm a suggested site name"));
BorderPane.setMargin(addModelBox, new Insets(5)); BorderPane.setMargin(addModelBox, new Insets(5));
addModelButton.setOnAction(this::addModel); addModelButton.setOnAction(this::addModel);
addModelButton.setPadding(new Insets(5)); addModelButton.setPadding(new Insets(5));
@ -303,8 +302,8 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
checkModelAccountExistance.setPadding(new Insets(5)); checkModelAccountExistance.setPadding(new Insets(5));
checkModelAccountExistance.setTooltip(new Tooltip("Go over all model URLs and check, if the account still exists")); checkModelAccountExistance.setTooltip(new Tooltip("Go over all model URLs and check, if the account still exists"));
HBox.setMargin(checkModelAccountExistance, new Insets(0, 0, 0, 20)); HBox.setMargin(checkModelAccountExistance, new Insets(0, 0, 0, 20));
checkModelAccountExistance.setOnAction(evt -> new CheckModelAccountAction(checkModelAccountExistance, recorder) checkModelAccountExistance
.execute(Predicate.not(Model::isMarkedForLaterRecording))); .setOnAction(evt -> new CheckModelAccountAction(checkModelAccountExistance, recorder).execute(Predicate.not(Model::isMarkedForLaterRecording)));
var filterContainer = new HBox(); var filterContainer = new HBox();
filterContainer.setSpacing(0); filterContainer.setSpacing(0);
@ -317,7 +316,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
filter.minWidth(100); filter.minWidth(100);
filter.prefWidth(150); filter.prefWidth(150);
filter.setPromptText("Filter"); filter.setPromptText("Filter");
filter.textProperty().addListener( (observableValue, oldValue, newValue) -> { filter.textProperty().addListener((observableValue, oldValue, newValue) -> {
String q = filter.getText(); String q = filter.getText();
lock.lock(); lock.lock();
try { try {
@ -529,11 +528,11 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
private ChangeListener<Boolean> createPauseListener(JavaFxModel updatedModel) { private ChangeListener<Boolean> createPauseListener(JavaFxModel updatedModel) {
return (obs, oldV, newV) -> { return (obs, oldV, newV) -> {
if (newV.booleanValue()) { if (newV.booleanValue()) {
if(!recorder.isSuspended(updatedModel)) { if (!recorder.isSuspended(updatedModel)) {
pauseRecording(Collections.singletonList(updatedModel)); pauseRecording(Collections.singletonList(updatedModel));
} }
} else { } else {
if(recorder.isSuspended(updatedModel)) { if (recorder.isSuspended(updatedModel)) {
resumeRecording(Collections.singletonList(updatedModel)); resumeRecording(Collections.singletonList(updatedModel));
} }
} }
@ -591,26 +590,21 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
LOG.trace("Updating recorded models"); LOG.trace("Updating recorded models");
List<Recording> recordings = recorder.getRecordings(); List<Recording> recordings = recorder.getRecordings();
List<Model> onlineModels = recorder.getOnlineModels(); List<Model> onlineModels = recorder.getOnlineModels();
return recorder.getModels() return recorder.getModels().stream().filter(Predicate.not(Model::isMarkedForLaterRecording)).map(JavaFxModel::new).peek(fxm -> { // NOSONAR
.stream()
.filter(Predicate.not(Model::isMarkedForLaterRecording))
.map(JavaFxModel::new)
.peek(fxm -> { // NOSONAR
for (Recording recording : recordings) { for (Recording recording : recordings) {
if(recording.getStatus() == RECORDING && Objects.equals(recording.getModel(), fxm)){ if (recording.getStatus() == RECORDING && Objects.equals(recording.getModel(), fxm)) {
fxm.setRecordingProperty(true); fxm.setRecordingProperty(true);
break; break;
} }
} }
for (Model onlineModel : onlineModels) { for (Model onlineModel : onlineModels) {
if(Objects.equals(onlineModel, fxm)) { if (Objects.equals(onlineModel, fxm)) {
fxm.setOnlineProperty(true); fxm.setOnlineProperty(true);
break; break;
} }
} }
}) }).collect(Collectors.toList());
.collect(Collectors.toList());
} }
}; };
} }
@ -684,9 +678,8 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
openRecDir.setOnAction(e -> new OpenRecordingsDir(table, selectedModels.get(0)).execute()); openRecDir.setOnAction(e -> new OpenRecordingsDir(table, selectedModels.get(0)).execute());
var addToGroup = new MenuItem("Add to group"); var addToGroup = new MenuItem("Add to group");
addToGroup.setOnAction(e -> addToGroup(selectedModels.get(0))); addToGroup.setOnAction(e -> new AddToGroupAction(this.getContent(), recorder, selectedModels.get(0)).execute(() -> table.refresh()));
var editGroup = new MenuItem("Edit group"); var groupSubMenu = createModelGroupMenu(selectedModels);
editGroup.setOnAction(e -> editGroup(selectedModels.get(0)));
ContextMenu menu = new CustomMouseBehaviorContextMenu(stop, recordLater); ContextMenu menu = new CustomMouseBehaviorContextMenu(stop, recordLater);
if (selectedModels.size() == 1) { if (selectedModels.size() == 1) {
@ -699,7 +692,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
menu.getItems().addAll(resumeRecording, pauseRecording); menu.getItems().addAll(resumeRecording, pauseRecording);
} }
Optional<ModelGroup> modelGroup = recorder.getModelGroup(selectedModels.get(0)); Optional<ModelGroup> modelGroup = recorder.getModelGroup(selectedModels.get(0));
menu.getItems().add(modelGroup.isEmpty() ? addToGroup : editGroup); menu.getItems().add(modelGroup.isEmpty() ? addToGroup : groupSubMenu);
menu.getItems().addAll(copyUrl, openInPlayer, openInBrowser, openRecDir, switchStreamSource, follow, notes, ignore); menu.getItems().addAll(copyUrl, openInPlayer, openInBrowser, openRecDir, switchStreamSource, follow, notes, ignore);
if (selectedModels.size() > 1) { if (selectedModels.size() > 1) {
@ -713,14 +706,13 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
return menu; return menu;
} }
private void addToGroup(Model model) { private Menu createModelGroupMenu(ObservableList<JavaFxModel> selectedModels) {
new AddToGroupAction(this.getContent(), recorder, model).execute(); return new ModelGroupMenuBuilder() //
table.refresh(); .model(selectedModels.get(0)) //
} .recorder(recorder) //
.node(table) //
private void editGroup(Model model) { .callback(m -> table.refresh()) //
new EditGroupAction(this.getContent(), recorder, model).execute(); .build();
table.refresh();
} }
private void setStopDate(JavaFxModel model) { private void setStopDate(JavaFxModel model) {
@ -818,13 +810,10 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
private void recordLater(List<JavaFxModel> selectedModels) { private void recordLater(List<JavaFxModel> selectedModels) {
boolean confirmed = stopAction(selectedModels); boolean confirmed = stopAction(selectedModels);
if (confirmed) { if (confirmed) {
List<Model> models = selectedModels.stream() List<Model> models = selectedModels.stream().map(JavaFxModel::getDelegate).map(m -> {
.map(JavaFxModel::getDelegate)
.map(m -> {
m.setMarkedForLaterRecording(true); m.setMarkedForLaterRecording(true);
return m; return m;
}) }).collect(Collectors.toList());
.collect(Collectors.toList());
new StartRecordingAction(table, models, recorder).execute(); new StartRecordingAction(table, models, recorder).execute();
} }
} }
@ -878,10 +867,10 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
private void restoreColumnOrder() { private void restoreColumnOrder() {
String[] columnIds = Config.getInstance().getSettings().recordedModelsColumnIds; String[] columnIds = Config.getInstance().getSettings().recordedModelsColumnIds;
ObservableList<TableColumn<JavaFxModel,?>> columns = table.getColumns(); ObservableList<TableColumn<JavaFxModel, ?>> columns = table.getColumns();
for (var i = 0; i < columnIds.length; i++) { for (var i = 0; i < columnIds.length; i++) {
for (var j = 0; j < table.getColumns().size(); j++) { for (var j = 0; j < table.getColumns().size(); j++) {
if(Objects.equals(columnIds[i], columns.get(j).getId())) { if (Objects.equals(columnIds[i], columns.get(j).getId())) {
TableColumn<JavaFxModel, ?> col = columns.get(j); TableColumn<JavaFxModel, ?> col = columns.get(j);
columns.remove(j); // NOSONAR columns.remove(j); // NOSONAR
columns.add(i, col); columns.add(i, col);
@ -912,7 +901,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> { cell.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) { if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
JavaFxModel selectedModel = table.getSelectionModel().getSelectedItem(); JavaFxModel selectedModel = table.getSelectionModel().getSelectedItem();
if(selectedModel != null) { if (selectedModel != null) {
new PlayAction(table, selectedModel).execute(); new PlayAction(table, selectedModel).execute();
} }
} }
@ -929,11 +918,11 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
TableCell<JavaFxModel, Number> tableCell = callback.call(param); TableCell<JavaFxModel, Number> tableCell = callback.call(param);
tableCell.setOnScroll(event -> { tableCell.setOnScroll(event -> {
if(event.isControlDown()) { if (event.isControlDown()) {
event.consume(); event.consume();
JavaFxModel m = tableCell.getTableRow().getItem(); JavaFxModel m = tableCell.getTableRow().getItem();
int prio = m.getPriority(); int prio = m.getPriority();
if(event.getDeltaY() < 0) { if (event.getDeltaY() < 0) {
prio--; prio--;
} else { } else {
prio++; prio++;