Introduce common base class for recorded models tabs
This commit is contained in:
parent
82c51bab40
commit
016b5dc7f1
|
@ -2,6 +2,7 @@
|
||||||
========================
|
========================
|
||||||
* Added portrait column to Recording tab. The image to show can be selected in
|
* Added portrait column to Recording tab. The image to show can be selected in
|
||||||
the context menu. This feature is a client-side only feature.
|
the context menu. This feature is a client-side only feature.
|
||||||
|
* Added button to configure, which columns should be shown on the Recording tab
|
||||||
* Added data transfer detection to HLS downloads, so that downloads don't
|
* Added data transfer detection to HLS downloads, so that downloads don't
|
||||||
get stuck in recording state. Recordings will stop now, if now segment was
|
get stuck in recording state. Recordings will stop now, if now segment was
|
||||||
downloaded for 30 seconds.
|
downloaded for 30 seconds.
|
||||||
|
|
|
@ -5,6 +5,7 @@ import java.awt.Image;
|
||||||
import java.awt.image.BufferedImage;
|
import java.awt.image.BufferedImage;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
@ -42,7 +43,8 @@ public class SetPortraitAction {
|
||||||
|
|
||||||
public void execute() {
|
public void execute() {
|
||||||
source.setCursor(Cursor.WAIT);
|
source.setCursor(Cursor.WAIT);
|
||||||
String portraitId = Config.getInstance().getSettings().modelPortraits.getOrDefault(model.getUrl(), UUID.randomUUID().toString());
|
String portraitId = Config.getInstance().getSettings().modelPortraits.getOrDefault(model.getUrl(),
|
||||||
|
UUID.nameUUIDFromBytes(model.getUrl().getBytes(StandardCharsets.UTF_8)).toString());
|
||||||
|
|
||||||
GridPane pane = new GridPane();
|
GridPane pane = new GridPane();
|
||||||
Label l = new Label("Select a portrait image. Leave empty to remove a portrait again.");
|
Label l = new Label("Select a portrait image. Leave empty to remove a portrait again.");
|
||||||
|
|
|
@ -56,7 +56,7 @@ import ctbrec.ui.controls.DateTimeCellFactory;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
import ctbrec.ui.controls.Toast;
|
import ctbrec.ui.controls.Toast;
|
||||||
import ctbrec.ui.menu.ModelMenuContributor;
|
import ctbrec.ui.menu.ModelMenuContributor;
|
||||||
import ctbrec.ui.tabs.recorded.RecordedModelsTab.ModelName;
|
import ctbrec.ui.tabs.recorded.ModelName;
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
import javafx.collections.FXCollections;
|
import javafx.collections.FXCollections;
|
||||||
|
|
|
@ -0,0 +1,601 @@
|
||||||
|
package ctbrec.ui.tabs.recorded;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.common.cache.CacheBuilder;
|
||||||
|
import com.google.common.cache.CacheLoader;
|
||||||
|
import com.google.common.cache.LoadingCache;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.StringUtil;
|
||||||
|
import ctbrec.recorder.Recorder;
|
||||||
|
import ctbrec.sites.Site;
|
||||||
|
import ctbrec.ui.AutosizeAlert;
|
||||||
|
import ctbrec.ui.JavaFxModel;
|
||||||
|
import ctbrec.ui.PreviewPopupHandler;
|
||||||
|
import ctbrec.ui.action.PlayAction;
|
||||||
|
import ctbrec.ui.action.SetPortraitAction;
|
||||||
|
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
|
||||||
|
import ctbrec.ui.controls.Dialogs;
|
||||||
|
import ctbrec.ui.controls.SearchBox;
|
||||||
|
import ctbrec.ui.controls.autocomplete.AutoFillTextField;
|
||||||
|
import ctbrec.ui.controls.autocomplete.ObservableListSuggester;
|
||||||
|
import ctbrec.ui.menu.ModelMenuContributor;
|
||||||
|
import ctbrec.ui.tabs.TabSelectionListener;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.beans.property.StringPropertyBase;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.event.ActionEvent;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.geometry.Point2D;
|
||||||
|
import javafx.geometry.Pos;
|
||||||
|
import javafx.scene.Cursor;
|
||||||
|
import javafx.scene.control.Alert;
|
||||||
|
import javafx.scene.control.Button;
|
||||||
|
import javafx.scene.control.CheckMenuItem;
|
||||||
|
import javafx.scene.control.ContextMenu;
|
||||||
|
import javafx.scene.control.Label;
|
||||||
|
import javafx.scene.control.ScrollPane;
|
||||||
|
import javafx.scene.control.SelectionMode;
|
||||||
|
import javafx.scene.control.Tab;
|
||||||
|
import javafx.scene.control.TableCell;
|
||||||
|
import javafx.scene.control.TableColumn;
|
||||||
|
import javafx.scene.control.TableColumn.SortType;
|
||||||
|
import javafx.scene.control.TableRow;
|
||||||
|
import javafx.scene.control.TableView;
|
||||||
|
import javafx.scene.control.TextField;
|
||||||
|
import javafx.scene.control.Tooltip;
|
||||||
|
import javafx.scene.control.cell.PropertyValueFactory;
|
||||||
|
import javafx.scene.image.Image;
|
||||||
|
import javafx.scene.input.ContextMenuEvent;
|
||||||
|
import javafx.scene.input.KeyCode;
|
||||||
|
import javafx.scene.input.KeyEvent;
|
||||||
|
import javafx.scene.input.MouseButton;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
|
import javafx.scene.layout.BorderPane;
|
||||||
|
import javafx.scene.layout.FlowPane;
|
||||||
|
import javafx.scene.layout.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
|
||||||
|
public abstract class AbstractRecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(AbstractRecordedModelsTab.class);
|
||||||
|
|
||||||
|
protected ReentrantLock lock = new ReentrantLock();
|
||||||
|
protected ObservableList<JavaFxModel> observableModels = FXCollections.observableArrayList();
|
||||||
|
protected ObservableList<JavaFxModel> filteredModels = FXCollections.observableArrayList();
|
||||||
|
|
||||||
|
protected TableView<JavaFxModel> table = new TableView<>();
|
||||||
|
protected List<TableColumn<JavaFxModel, ?>> columns = new ArrayList<>();
|
||||||
|
protected LoadingCache<Model, Image> portraitCache = CacheBuilder.newBuilder()
|
||||||
|
.expireAfterAccess(1, TimeUnit.DAYS)
|
||||||
|
.maximumSize(1000)
|
||||||
|
.build(CacheLoader.from(AbstractRecordedModelsTab::loadModelPortrait));
|
||||||
|
|
||||||
|
protected AutoFillTextField modelInputField;
|
||||||
|
protected List<Site> sites;
|
||||||
|
protected Recorder recorder;
|
||||||
|
|
||||||
|
protected HBox addModelBox = new HBox(5);
|
||||||
|
protected HBox filterContainer = new HBox(5);
|
||||||
|
protected Label modelLabel = new Label("Model");
|
||||||
|
protected Button addModelButton = new Button("Record");
|
||||||
|
protected Button checkModelAccountExistance = new Button("Check URLs");
|
||||||
|
protected TextField filter;
|
||||||
|
|
||||||
|
protected FlowPane grid = new FlowPane();
|
||||||
|
protected ScrollPane scrollPane = new ScrollPane();
|
||||||
|
protected ContextMenu popup;
|
||||||
|
|
||||||
|
AbstractRecordedModelsTab(String text) {
|
||||||
|
super(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void createGui() {
|
||||||
|
grid.setPadding(new Insets(5));
|
||||||
|
grid.setHgap(5);
|
||||||
|
grid.setVgap(5);
|
||||||
|
|
||||||
|
scrollPane.setContent(grid);
|
||||||
|
scrollPane.setFitToHeight(true);
|
||||||
|
scrollPane.setFitToWidth(true);
|
||||||
|
BorderPane.setMargin(scrollPane, new Insets(5));
|
||||||
|
|
||||||
|
table.setEditable(true);
|
||||||
|
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||||
|
var previewPopupHandler = new PreviewPopupHandler(table);
|
||||||
|
table.setRowFactory(tableview -> {
|
||||||
|
TableRow<JavaFxModel> row = new TableRow<>();
|
||||||
|
row.addEventHandler(MouseEvent.ANY, previewPopupHandler);
|
||||||
|
return row;
|
||||||
|
});
|
||||||
|
table.setItems(observableModels);
|
||||||
|
table.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
|
||||||
|
popup = createContextMenu();
|
||||||
|
if (popup != null) {
|
||||||
|
popup.show(table, event.getScreenX(), event.getScreenY());
|
||||||
|
}
|
||||||
|
event.consume();
|
||||||
|
});
|
||||||
|
table.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
|
||||||
|
if (popup != null) {
|
||||||
|
popup.hide();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
table.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
|
||||||
|
List<JavaFxModel> selectedModels = table.getSelectionModel().getSelectedItems();
|
||||||
|
if (event.getCode() == KeyCode.DELETE) {
|
||||||
|
stopAction(selectedModels);
|
||||||
|
} else {
|
||||||
|
jumpToNextModel(event.getCode());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
scrollPane.setContent(table);
|
||||||
|
|
||||||
|
checkModelAccountExistance.setPadding(new Insets(5));
|
||||||
|
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));
|
||||||
|
|
||||||
|
modelLabel.setPadding(new Insets(5, 0, 0, 0));
|
||||||
|
ObservableList<String> suggestions = FXCollections.observableArrayList();
|
||||||
|
sites.forEach(site -> suggestions.add(site.getClass().getSimpleName()));
|
||||||
|
modelInputField = new AutoFillTextField(new ObservableListSuggester(suggestions));
|
||||||
|
modelInputField.minWidth(150);
|
||||||
|
modelInputField.prefWidth(600);
|
||||||
|
HBox.setHgrow(modelInputField, Priority.ALWAYS);
|
||||||
|
modelInputField.setPromptText("e.g. MyFreeCams:ModelName or an URL like https://chaturbate.com/modelname/");
|
||||||
|
modelInputField.onActionHandler(this::addModel);
|
||||||
|
modelInputField.setTooltip(new Tooltip("To add a model enter SiteName:ModelName\n" + "press ENTER to confirm a suggested site name"));
|
||||||
|
BorderPane.setMargin(addModelBox, new Insets(5));
|
||||||
|
addModelButton.setOnAction(this::addModel);
|
||||||
|
addModelButton.setPadding(new Insets(5));
|
||||||
|
addModelBox.getChildren().addAll(modelLabel, modelInputField, addModelButton, checkModelAccountExistance);
|
||||||
|
|
||||||
|
filterContainer.setPadding(new Insets(0));
|
||||||
|
filterContainer.setAlignment(Pos.CENTER_RIGHT);
|
||||||
|
filterContainer.minWidth(100);
|
||||||
|
filterContainer.prefWidth(150);
|
||||||
|
HBox.setHgrow(filterContainer, Priority.ALWAYS);
|
||||||
|
filter = new SearchBox(false);
|
||||||
|
filter.minWidth(100);
|
||||||
|
filter.prefWidth(150);
|
||||||
|
filter.setPromptText("Filter");
|
||||||
|
filter.textProperty().addListener((observableValue, oldValue, newValue) -> {
|
||||||
|
String q = filter.getText();
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
filter(q);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
filter.getStyleClass().remove("search-box-icon");
|
||||||
|
|
||||||
|
var columnSelection = new Button("⚙");
|
||||||
|
columnSelection.setOnAction(this::showColumnSelection);
|
||||||
|
columnSelection.setTooltip(new Tooltip("Select columns"));
|
||||||
|
columnSelection.prefHeightProperty().bind(filter.prefHeightProperty());
|
||||||
|
columnSelection.prefWidthProperty().bind(columnSelection.prefHeightProperty());
|
||||||
|
|
||||||
|
filterContainer.getChildren().addAll(columnSelection, filter);
|
||||||
|
addModelBox.getChildren().add(filterContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addPreviewColumn(int columnIdx) {
|
||||||
|
TableColumn<JavaFxModel, String> preview = addTableColumn("preview", "🎥", columnIdx, 35);
|
||||||
|
preview.setCellValueFactory(cdf -> new SimpleStringProperty(" ▶ "));
|
||||||
|
preview.setEditable(false);
|
||||||
|
if (!Config.getInstance().getSettings().livePreviews) {
|
||||||
|
preview.setVisible(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addPortraitColumn(int columnIdx) {
|
||||||
|
TableColumn<JavaFxModel, Image> portrait = addTableColumn("portrait", "Portrait", columnIdx, 80);
|
||||||
|
portrait.setCellValueFactory(param -> {
|
||||||
|
Model mdl = param.getValue().getDelegate();
|
||||||
|
Image image = null;
|
||||||
|
try {
|
||||||
|
image = portraitCache.get(mdl);
|
||||||
|
} catch (ExecutionException e) {
|
||||||
|
LOG.error("Error while loading portrait from cache for {}", mdl, e);
|
||||||
|
}
|
||||||
|
return new SimpleObjectProperty<Image>(image);
|
||||||
|
});
|
||||||
|
portrait.setCellFactory(param -> new ImageTableCell());
|
||||||
|
portrait.setEditable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addModelColumn(int columnIdx) {
|
||||||
|
TableColumn<JavaFxModel, ModelName> name = addTableColumn("name", "Model", columnIdx, 200);
|
||||||
|
name.setCellValueFactory(param -> {
|
||||||
|
var modelName = new ModelName(param.getValue(), recorder);
|
||||||
|
return new SimpleObjectProperty<>(modelName);
|
||||||
|
});
|
||||||
|
name.setCellFactory(param -> new ModelNameTableCell(recorder));
|
||||||
|
name.setEditable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addUrlColumn(int columnIdx) {
|
||||||
|
TableColumn<JavaFxModel, String> url = addTableColumn("url", "URL", columnIdx, 400);
|
||||||
|
url.setCellValueFactory(new PropertyValueFactory<>("url"));
|
||||||
|
url.setCellFactory(new ClickableCellFactory<>());
|
||||||
|
url.setEditable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addNotesColumn(int columnIdx) {
|
||||||
|
TableColumn<JavaFxModel, String> notes = addTableColumn("notes", "Notes", columnIdx, 400);
|
||||||
|
notes.setCellValueFactory(cdf -> {
|
||||||
|
JavaFxModel m = cdf.getValue();
|
||||||
|
return new StringPropertyBase() {
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "Model Notes";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Object getBean() {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String get() {
|
||||||
|
String modelNotes = Config.getInstance().getModelNotes(m);
|
||||||
|
return modelNotes;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
notes.setEditable(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract void stopAction(List<JavaFxModel> selectedModels);
|
||||||
|
|
||||||
|
protected <T> TableColumn<JavaFxModel, T> addTableColumn(String id, String text, int index, int width) {
|
||||||
|
TableColumn<JavaFxModel, T> tc = new TableColumn<>(text);
|
||||||
|
tc.setId(id);
|
||||||
|
tc.setText(text);
|
||||||
|
tc.setUserData(index);
|
||||||
|
tc.setPrefWidth(width);
|
||||||
|
columns.add(tc);
|
||||||
|
addTableColumnIfEnabled(tc);
|
||||||
|
return tc;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addTableColumnIfEnabled(TableColumn<JavaFxModel, ?> tc) {
|
||||||
|
if(isColumnEnabled(tc)) {
|
||||||
|
table.getColumns().add(tc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected ContextMenu createContextMenu() {
|
||||||
|
List<Model> selectedModels = table.getSelectionModel().getSelectedItems().stream().map(JavaFxModel::getDelegate).collect(Collectors.toList());
|
||||||
|
if (selectedModels.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContextMenu menu = new CustomMouseBehaviorContextMenu();
|
||||||
|
|
||||||
|
ModelMenuContributor.newContributor(getTabPane(), Config.getInstance(), recorder) //
|
||||||
|
.withStartStopCallback(m -> getTabPane().setCursor(Cursor.DEFAULT)) //
|
||||||
|
.removeModelAfterIgnore(true) //
|
||||||
|
.withPortraitCallback(m -> {
|
||||||
|
portraitCache.invalidate(m);
|
||||||
|
table.refresh();
|
||||||
|
})
|
||||||
|
.afterwards(table::refresh) //
|
||||||
|
.contributeToMenu(selectedModels, menu);
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addModel(ActionEvent e) {
|
||||||
|
String input = modelInputField.getText().trim();
|
||||||
|
if (StringUtil.isBlank(input)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (input.startsWith("http")) {
|
||||||
|
addModelByUrl(input);
|
||||||
|
} else {
|
||||||
|
addModelByName(input);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void addModelByUrl(String url) {
|
||||||
|
for (Site site : sites) {
|
||||||
|
var newModel = site.createModelFromUrl(url);
|
||||||
|
if (newModel != null) {
|
||||||
|
try {
|
||||||
|
newModel.setMarkedForLaterRecording(getMarkModelsForLaterRecording());
|
||||||
|
recorder.addModel(newModel);
|
||||||
|
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
||||||
|
Dialogs.showError(getTabPane().getScene(), "Couldn't add model", "The model " + newModel.getName() + " could not be added: ", e1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Dialogs.showError(getTabPane().getScene(), "Unknown URL format",
|
||||||
|
"The URL you entered has an unknown format or the function does not support this site, yet", null);
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract boolean getMarkModelsForLaterRecording();
|
||||||
|
|
||||||
|
protected void addModelByName(String siteModelCombo) {
|
||||||
|
String[] parts = siteModelCombo.trim().split(":");
|
||||||
|
if (parts.length != 2) {
|
||||||
|
Dialogs.showError(getTabPane().getScene(), "Wrong input format", "Use something like \"MyFreeCams:ModelName\"", null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String siteName = parts[0];
|
||||||
|
String modelName = parts[1];
|
||||||
|
for (Site site : sites) {
|
||||||
|
if (Objects.equals(siteName.toLowerCase(), site.getClass().getSimpleName().toLowerCase())) {
|
||||||
|
try {
|
||||||
|
var m = site.createModel(modelName);
|
||||||
|
m.setMarkedForLaterRecording(getMarkModelsForLaterRecording());
|
||||||
|
recorder.addModel(m);
|
||||||
|
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
||||||
|
Dialogs.showError(getTabPane().getScene(), "Couldn't add model", "The model " + modelName + " could not be added:", e1);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, getTabPane().getScene());
|
||||||
|
alert.setTitle("Unknown site");
|
||||||
|
alert.setHeaderText("Couldn't add model");
|
||||||
|
alert.setContentText("The site you entered is unknown");
|
||||||
|
alert.showAndWait();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void jumpToNextModel(KeyCode code) {
|
||||||
|
if (!table.getItems().isEmpty() && (code.isLetterKey() || code.isDigitKey())) {
|
||||||
|
// determine where to start looking for the next model
|
||||||
|
var startAt = getJumpToStartIndex();
|
||||||
|
|
||||||
|
String c = code.getChar().toLowerCase();
|
||||||
|
int i = startAt;
|
||||||
|
do {
|
||||||
|
JavaFxModel current = table.getItems().get(i);
|
||||||
|
if (current.getName().toLowerCase().replaceAll("[^0-9a-z]", "").startsWith(c)) {
|
||||||
|
table.getSelectionModel().clearAndSelect(i);
|
||||||
|
table.scrollTo(i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
i++;
|
||||||
|
if (i >= table.getItems().size()) {
|
||||||
|
i = 0;
|
||||||
|
}
|
||||||
|
} while (i != startAt);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected int getJumpToStartIndex() {
|
||||||
|
var startAt = 0;
|
||||||
|
if (table.getSelectionModel().getSelectedIndex() >= 0) {
|
||||||
|
startAt = table.getSelectionModel().getSelectedIndex() + 1;
|
||||||
|
if (startAt >= table.getItems().size()) {
|
||||||
|
startAt = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return startAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void filter(String filter) {
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
if (StringUtil.isBlank(filter)) {
|
||||||
|
observableModels.addAll(filteredModels);
|
||||||
|
filteredModels.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String[] tokens = filter.split(" ");
|
||||||
|
observableModels.addAll(filteredModels);
|
||||||
|
filteredModels.clear();
|
||||||
|
for (var i = 0; i < table.getItems().size(); i++) {
|
||||||
|
var sb = new StringBuilder();
|
||||||
|
for (TableColumn<JavaFxModel, ?> tc : table.getColumns()) {
|
||||||
|
Object cellData = tc.getCellData(i);
|
||||||
|
if (cellData != null) {
|
||||||
|
var content = cellData.toString();
|
||||||
|
sb.append(content).append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
var searchText = sb.toString();
|
||||||
|
|
||||||
|
var tokensMissing = false;
|
||||||
|
for (String token : tokens) {
|
||||||
|
if (!searchText.toLowerCase().contains(token.toLowerCase())) {
|
||||||
|
tokensMissing = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (tokensMissing) {
|
||||||
|
JavaFxModel filteredModel = table.getItems().get(i);
|
||||||
|
filteredModels.add(filteredModel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
observableModels.removeAll(filteredModels);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract String getSortColumn();
|
||||||
|
abstract void setSortColumn(String column);
|
||||||
|
abstract String getSortType();
|
||||||
|
abstract void setSortType(String sortType);
|
||||||
|
abstract String[] getColumnIds();
|
||||||
|
abstract void setColumnIds(String[] ids);
|
||||||
|
abstract double[] getColumnWidths();
|
||||||
|
abstract void setColumnWidths(double[] widths);
|
||||||
|
abstract List<String> getDisabledColumns();
|
||||||
|
|
||||||
|
public void saveState() {
|
||||||
|
if (!table.getSortOrder().isEmpty()) {
|
||||||
|
TableColumn<JavaFxModel, ?> col = table.getSortOrder().get(0);
|
||||||
|
setSortColumn(col.getText());
|
||||||
|
setSortType(col.getSortType().toString());
|
||||||
|
}
|
||||||
|
int tableColumns = table.getColumns().size();
|
||||||
|
var columnWidths = new double[tableColumns];
|
||||||
|
var columnIds = new String[tableColumns];
|
||||||
|
for (var i = 0; i < columnWidths.length; i++) {
|
||||||
|
columnWidths[i] = table.getColumns().get(i).getWidth();
|
||||||
|
columnIds[i] = table.getColumns().get(i).getId();
|
||||||
|
}
|
||||||
|
setColumnWidths(columnWidths);
|
||||||
|
setColumnIds(columnIds);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void restoreState() {
|
||||||
|
restoreColumnOrder();
|
||||||
|
restoreColumnWidths();
|
||||||
|
restoreSorting();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void restoreSorting() {
|
||||||
|
String sortCol = getSortColumn();
|
||||||
|
if (StringUtil.isNotBlank(sortCol)) {
|
||||||
|
for (TableColumn<JavaFxModel, ?> col : table.getColumns()) {
|
||||||
|
if (Objects.equals(sortCol, col.getText())) {
|
||||||
|
col.setSortType(SortType.valueOf(getSortType()));
|
||||||
|
table.getSortOrder().clear();
|
||||||
|
table.getSortOrder().add(col);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreColumnOrder() {
|
||||||
|
String[] columnIds = getColumnIds();
|
||||||
|
ObservableList<TableColumn<JavaFxModel,?>> tableColumns = table.getColumns();
|
||||||
|
for (var i = 0; i < columnIds.length; i++) {
|
||||||
|
for (var j = 0; j < table.getColumns().size(); j++) {
|
||||||
|
if(Objects.equals(columnIds[i], tableColumns.get(j).getId())) {
|
||||||
|
TableColumn<JavaFxModel, ?> col = tableColumns.get(j);
|
||||||
|
tableColumns.remove(j); // NOSONAR
|
||||||
|
tableColumns.add(Math.min(i, tableColumns.size()), col);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void restoreColumnWidths() {
|
||||||
|
double[] columnWidths = getColumnWidths();
|
||||||
|
if (columnWidths != null && columnWidths.length == table.getColumns().size()) {
|
||||||
|
for (var i = 0; i < columnWidths.length; i++) {
|
||||||
|
table.getColumns().get(i).setPrefWidth(columnWidths[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected class ClickableCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
|
||||||
|
@Override
|
||||||
|
public TableCell<S, T> call(TableColumn<S, T> param) {
|
||||||
|
TableCell<S, T> cell = new TableCell<>() {
|
||||||
|
@Override
|
||||||
|
protected void updateItem(Object item, boolean empty) {
|
||||||
|
setText(empty ? "" : Objects.toString(item));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
|
||||||
|
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
||||||
|
JavaFxModel selectedModel = table.getSelectionModel().getSelectedItem();
|
||||||
|
if (selectedModel != null) {
|
||||||
|
new PlayAction(table, selectedModel).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static Image loadModelPortrait(Model model) {
|
||||||
|
String portraitId = Config.getInstance().getSettings().modelPortraits.get(model.getUrl());
|
||||||
|
if (StringUtil.isNotBlank(portraitId)) {
|
||||||
|
File configDir = Config.getInstance().getConfigDir();
|
||||||
|
File portraitDir = new File(configDir, "portraits");
|
||||||
|
File portraitFile = new File(portraitDir, portraitId + '.' + SetPortraitAction.FORMAT);
|
||||||
|
try {
|
||||||
|
return new Image(new FileInputStream(portraitFile));
|
||||||
|
} catch (FileNotFoundException e) {
|
||||||
|
LOG.error("Couldn't load portrait file {}", portraitFile, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new Image(AbstractRecordedModelsTab.class.getResourceAsStream("/silhouette_256.png"));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void showColumnSelection(ActionEvent evt) {
|
||||||
|
ContextMenu menu = new CustomMouseBehaviorContextMenu();
|
||||||
|
for (TableColumn<JavaFxModel, ?> tc : columns) {
|
||||||
|
var item = new CheckMenuItem(tc.getText());
|
||||||
|
item.setSelected(isColumnEnabled(tc));
|
||||||
|
menu.getItems().add(item);
|
||||||
|
item.setOnAction(e -> {
|
||||||
|
try {
|
||||||
|
if (item.isSelected()) {
|
||||||
|
getDisabledColumns().remove(tc.getText());
|
||||||
|
boolean added = false;
|
||||||
|
for (int i = table.getColumns().size() - 1; i >= 0; i--) {
|
||||||
|
TableColumn<JavaFxModel, ?> other = table.getColumns().get(i);
|
||||||
|
if (!other.isVisible()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
int idx = (int) tc.getUserData();
|
||||||
|
int otherIdx = (int) other.getUserData();
|
||||||
|
if (otherIdx < idx) {
|
||||||
|
table.getColumns().add(i + 1, tc);
|
||||||
|
added = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!added) {
|
||||||
|
table.getColumns().add(0, tc);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
getDisabledColumns().add(tc.getText());
|
||||||
|
table.getColumns().remove(tc);
|
||||||
|
}
|
||||||
|
} catch (Exception ex) {
|
||||||
|
LOG.error("Couldn't activate column {}", tc, ex);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Button src = (Button) evt.getSource();
|
||||||
|
Point2D location = src.localToScreen(src.getTranslateX(), src.getTranslateY());
|
||||||
|
menu.show(getTabPane().getScene().getWindow(), location.getX(), location.getY() + src.getHeight() + 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isColumnEnabled(TableColumn<JavaFxModel, ?> tc) {
|
||||||
|
return !getDisabledColumns().contains(tc.getText());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package ctbrec.ui.tabs.recorded;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.ModelGroup;
|
||||||
|
import ctbrec.recorder.Recorder;
|
||||||
|
|
||||||
|
public class ModelName {
|
||||||
|
private Model mdl;
|
||||||
|
private Recorder rec;
|
||||||
|
|
||||||
|
public ModelName(Model model, Recorder recorder) {
|
||||||
|
mdl = model;
|
||||||
|
rec = recorder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
Optional<ModelGroup> modelGroup = rec.getModelGroup(mdl);
|
||||||
|
String s;
|
||||||
|
if (modelGroup.isPresent()) {
|
||||||
|
s = modelGroup.get().getName() + " (aka " + mdl.getDisplayName() + ')';
|
||||||
|
} else {
|
||||||
|
return mdl.toString();
|
||||||
|
}
|
||||||
|
return s;
|
||||||
|
}
|
||||||
|
}
|
|
@ -7,7 +7,6 @@ import java.util.stream.Collectors;
|
||||||
|
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.ui.tabs.recorded.RecordedModelsTab.ModelName;
|
|
||||||
import javafx.scene.image.ImageView;
|
import javafx.scene.image.ImageView;
|
||||||
|
|
||||||
public class ModelNameTableCell extends IconTableCell<ModelName> {
|
public class ModelNameTableCell extends IconTableCell<ModelName> {
|
||||||
|
|
|
@ -5,11 +5,9 @@ import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -17,83 +15,25 @@ import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.StringUtil;
|
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
import ctbrec.ui.AutosizeAlert;
|
|
||||||
import ctbrec.ui.JavaFxModel;
|
import ctbrec.ui.JavaFxModel;
|
||||||
import ctbrec.ui.PreviewPopupHandler;
|
|
||||||
import ctbrec.ui.action.CheckModelAccountAction;
|
import ctbrec.ui.action.CheckModelAccountAction;
|
||||||
import ctbrec.ui.action.PlayAction;
|
|
||||||
import ctbrec.ui.action.StopRecordingAction;
|
import ctbrec.ui.action.StopRecordingAction;
|
||||||
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
|
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
import ctbrec.ui.controls.SearchBox;
|
|
||||||
import ctbrec.ui.controls.autocomplete.AutoFillTextField;
|
|
||||||
import ctbrec.ui.controls.autocomplete.ObservableListSuggester;
|
|
||||||
import ctbrec.ui.menu.ModelMenuContributor;
|
|
||||||
import ctbrec.ui.tabs.TabSelectionListener;
|
import ctbrec.ui.tabs.TabSelectionListener;
|
||||||
import ctbrec.ui.tabs.recorded.RecordedModelsTab.ModelName;
|
|
||||||
import javafx.application.Platform;
|
import javafx.application.Platform;
|
||||||
import javafx.beans.property.SimpleObjectProperty;
|
|
||||||
import javafx.beans.property.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringPropertyBase;
|
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.concurrent.ScheduledService;
|
import javafx.concurrent.ScheduledService;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.concurrent.WorkerStateEvent;
|
import javafx.concurrent.WorkerStateEvent;
|
||||||
import javafx.event.ActionEvent;
|
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
|
||||||
import javafx.scene.Cursor;
|
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.Button;
|
|
||||||
import javafx.scene.control.ContextMenu;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.ScrollPane;
|
|
||||||
import javafx.scene.control.SelectionMode;
|
|
||||||
import javafx.scene.control.Tab;
|
|
||||||
import javafx.scene.control.TableCell;
|
|
||||||
import javafx.scene.control.TableColumn;
|
|
||||||
import javafx.scene.control.TableColumn.SortType;
|
|
||||||
import javafx.scene.control.TableRow;
|
|
||||||
import javafx.scene.control.TableView;
|
|
||||||
import javafx.scene.control.TextField;
|
|
||||||
import javafx.scene.control.Tooltip;
|
|
||||||
import javafx.scene.control.cell.PropertyValueFactory;
|
|
||||||
import javafx.scene.input.ContextMenuEvent;
|
|
||||||
import javafx.scene.input.KeyCode;
|
|
||||||
import javafx.scene.input.KeyEvent;
|
|
||||||
import javafx.scene.input.MouseButton;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.FlowPane;
|
|
||||||
import javafx.scene.layout.HBox;
|
|
||||||
import javafx.scene.layout.Priority;
|
|
||||||
import javafx.util.Callback;
|
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
|
||||||
public class RecordLaterTab extends Tab implements TabSelectionListener {
|
public class RecordLaterTab extends AbstractRecordedModelsTab implements TabSelectionListener {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(RecordLaterTab.class);
|
private static final Logger LOG = LoggerFactory.getLogger(RecordLaterTab.class);
|
||||||
|
|
||||||
private ReentrantLock lock = new ReentrantLock();
|
|
||||||
private ScheduledService<List<JavaFxModel>> updateService;
|
private ScheduledService<List<JavaFxModel>> updateService;
|
||||||
private Recorder recorder;
|
|
||||||
private List<Site> sites;
|
|
||||||
|
|
||||||
FlowPane grid = new FlowPane();
|
|
||||||
ScrollPane scrollPane = new ScrollPane();
|
|
||||||
TableView<JavaFxModel> table = new TableView<>();
|
|
||||||
ObservableList<JavaFxModel> observableModels = FXCollections.observableArrayList();
|
|
||||||
ObservableList<JavaFxModel> filteredModels = FXCollections.observableArrayList();
|
|
||||||
ContextMenu popup;
|
|
||||||
|
|
||||||
Label modelLabel = new Label("Model");
|
|
||||||
AutoFillTextField model;
|
|
||||||
Button addModelButton = new Button("Record");
|
|
||||||
Button checkModelAccountExistance = new Button("Check URLs");
|
|
||||||
TextField filter;
|
|
||||||
|
|
||||||
public RecordLaterTab(String title, Recorder recorder, List<Site> sites) {
|
public RecordLaterTab(String title, Recorder recorder, List<Site> sites) {
|
||||||
super(title);
|
super(title);
|
||||||
|
@ -104,141 +44,16 @@ public class RecordLaterTab extends Tab implements TabSelectionListener {
|
||||||
initializeUpdateService();
|
initializeUpdateService();
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@Override
|
||||||
private void createGui() {
|
protected void createGui() {
|
||||||
grid.setPadding(new Insets(5));
|
super.createGui();
|
||||||
grid.setHgap(5);
|
|
||||||
grid.setVgap(5);
|
|
||||||
|
|
||||||
scrollPane.setContent(grid);
|
int columnIdx = 0;
|
||||||
scrollPane.setFitToHeight(true);
|
addPreviewColumn(columnIdx++);
|
||||||
scrollPane.setFitToWidth(true);
|
addPortraitColumn(columnIdx++);
|
||||||
BorderPane.setMargin(scrollPane, new Insets(5));
|
addModelColumn(columnIdx++);
|
||||||
|
addUrlColumn(columnIdx++);
|
||||||
table.setEditable(true);
|
addNotesColumn(columnIdx);
|
||||||
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
|
||||||
var previewPopupHandler = new PreviewPopupHandler(table);
|
|
||||||
table.setRowFactory(tableview -> {
|
|
||||||
TableRow<JavaFxModel> row = new TableRow<>();
|
|
||||||
row.addEventHandler(MouseEvent.ANY, previewPopupHandler);
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
TableColumn<JavaFxModel, String> preview = new TableColumn<>("🎥");
|
|
||||||
preview.setPrefWidth(35);
|
|
||||||
preview.setCellValueFactory(cdf -> new SimpleStringProperty(" ▶ "));
|
|
||||||
preview.setEditable(false);
|
|
||||||
preview.setId("preview");
|
|
||||||
if (!Config.getInstance().getSettings().livePreviews) {
|
|
||||||
preview.setVisible(false);
|
|
||||||
}
|
|
||||||
TableColumn<JavaFxModel, ModelName> name = new TableColumn<>("Model");
|
|
||||||
name.setPrefWidth(200);
|
|
||||||
name.setCellValueFactory(param -> {
|
|
||||||
var modelName = new ModelName(param.getValue(), recorder);
|
|
||||||
return new SimpleObjectProperty<>(modelName);
|
|
||||||
});
|
|
||||||
name.setCellFactory(param -> new ModelNameTableCell(recorder));
|
|
||||||
name.setEditable(false);
|
|
||||||
name.setId("name");
|
|
||||||
TableColumn<JavaFxModel, String> url = new TableColumn<>("URL");
|
|
||||||
url.setCellValueFactory(new PropertyValueFactory<>("url"));
|
|
||||||
url.setCellFactory(new ClickableCellFactory<>());
|
|
||||||
url.setPrefWidth(400);
|
|
||||||
url.setEditable(false);
|
|
||||||
url.setId("url");
|
|
||||||
TableColumn<JavaFxModel, String> notes = new TableColumn<>("Notes");
|
|
||||||
notes.setCellValueFactory(cdf -> {
|
|
||||||
JavaFxModel m = cdf.getValue();
|
|
||||||
return new StringPropertyBase() {
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "Model Notes";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Object getBean() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String get() {
|
|
||||||
String modelNotes = Config.getInstance().getModelNotes(m);
|
|
||||||
return modelNotes;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
notes.setPrefWidth(400);
|
|
||||||
notes.setEditable(false);
|
|
||||||
notes.setId("notes");
|
|
||||||
table.getColumns().addAll(preview, name, url, notes);
|
|
||||||
table.setItems(observableModels);
|
|
||||||
table.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
|
|
||||||
popup = createContextMenu();
|
|
||||||
if (popup != null) {
|
|
||||||
popup.show(table, event.getScreenX(), event.getScreenY());
|
|
||||||
}
|
|
||||||
event.consume();
|
|
||||||
});
|
|
||||||
table.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
|
|
||||||
if (popup != null) {
|
|
||||||
popup.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
table.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
|
|
||||||
List<JavaFxModel> selectedModels = table.getSelectionModel().getSelectedItems();
|
|
||||||
if (event.getCode() == KeyCode.DELETE) {
|
|
||||||
stopAction(selectedModels);
|
|
||||||
} else {
|
|
||||||
jumpToNextModel(event.getCode());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
scrollPane.setContent(table);
|
|
||||||
|
|
||||||
var addModelBox = new HBox(5);
|
|
||||||
modelLabel.setPadding(new Insets(5, 0, 0, 0));
|
|
||||||
ObservableList<String> suggestions = FXCollections.observableArrayList();
|
|
||||||
sites.forEach(site -> suggestions.add(site.getClass().getSimpleName()));
|
|
||||||
model = new AutoFillTextField(new ObservableListSuggester(suggestions));
|
|
||||||
model.minWidth(150);
|
|
||||||
model.prefWidth(600);
|
|
||||||
HBox.setHgrow(model, Priority.ALWAYS);
|
|
||||||
model.setPromptText("e.g. MyFreeCams:ModelName or an URL like https://chaturbate.com/modelname/");
|
|
||||||
model.onActionHandler(this::addModel);
|
|
||||||
model.setTooltip(new Tooltip("To add a model enter SiteName:ModelName\n" + "press ENTER to confirm a suggested site name"));
|
|
||||||
BorderPane.setMargin(addModelBox, new Insets(5));
|
|
||||||
addModelButton.setOnAction(this::addModel);
|
|
||||||
addModelButton.setPadding(new Insets(5));
|
|
||||||
addModelBox.getChildren().addAll(modelLabel, model, addModelButton, checkModelAccountExistance);
|
|
||||||
checkModelAccountExistance.setPadding(new Insets(5));
|
|
||||||
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));
|
|
||||||
checkModelAccountExistance.setOnAction(evt -> new CheckModelAccountAction(checkModelAccountExistance, recorder)
|
|
||||||
.execute(Model::isMarkedForLaterRecording));
|
|
||||||
|
|
||||||
var filterContainer = new HBox();
|
|
||||||
filterContainer.setSpacing(0);
|
|
||||||
filterContainer.setPadding(new Insets(0));
|
|
||||||
filterContainer.setAlignment(Pos.CENTER_RIGHT);
|
|
||||||
filterContainer.minWidth(100);
|
|
||||||
filterContainer.prefWidth(150);
|
|
||||||
HBox.setHgrow(filterContainer, Priority.ALWAYS);
|
|
||||||
filter = new SearchBox(false);
|
|
||||||
filter.minWidth(100);
|
|
||||||
filter.prefWidth(150);
|
|
||||||
filter.setPromptText("Filter");
|
|
||||||
filter.textProperty().addListener((observableValue, oldValue, newValue) -> {
|
|
||||||
String q = filter.getText();
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
filter(q);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
filter.getStyleClass().remove("search-box-icon");
|
|
||||||
filterContainer.getChildren().add(filter);
|
|
||||||
addModelBox.getChildren().add(filterContainer);
|
|
||||||
|
|
||||||
var root = new BorderPane();
|
var root = new BorderPane();
|
||||||
root.setPadding(new Insets(5));
|
root.setPadding(new Insets(5));
|
||||||
|
@ -246,98 +61,12 @@ public class RecordLaterTab extends Tab implements TabSelectionListener {
|
||||||
root.setCenter(scrollPane);
|
root.setCenter(scrollPane);
|
||||||
setContent(root);
|
setContent(root);
|
||||||
|
|
||||||
|
checkModelAccountExistance.setOnAction(evt -> new CheckModelAccountAction(checkModelAccountExistance, recorder)
|
||||||
|
.execute(Model::isMarkedForLaterRecording));
|
||||||
|
|
||||||
restoreState();
|
restoreState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void jumpToNextModel(KeyCode code) {
|
|
||||||
if (!table.getItems().isEmpty() && (code.isLetterKey() || code.isDigitKey())) {
|
|
||||||
// determine where to start looking for the next model
|
|
||||||
var startAt = 0;
|
|
||||||
if (table.getSelectionModel().getSelectedIndex() >= 0) {
|
|
||||||
startAt = table.getSelectionModel().getSelectedIndex() + 1;
|
|
||||||
if (startAt >= table.getItems().size()) {
|
|
||||||
startAt = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String c = code.getChar().toLowerCase();
|
|
||||||
int i = startAt;
|
|
||||||
do {
|
|
||||||
JavaFxModel current = table.getItems().get(i);
|
|
||||||
if (current.getName().toLowerCase().replaceAll("[^0-9a-z]", "").startsWith(c)) {
|
|
||||||
table.getSelectionModel().clearAndSelect(i);
|
|
||||||
table.scrollTo(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
if (i >= table.getItems().size()) {
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
} while (i != startAt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addModel(ActionEvent e) {
|
|
||||||
String input = model.getText().trim();
|
|
||||||
if (StringUtil.isBlank(input)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.startsWith("http")) {
|
|
||||||
addModelByUrl(input);
|
|
||||||
} else {
|
|
||||||
addModelByName(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addModelByUrl(String url) {
|
|
||||||
for (Site site : sites) {
|
|
||||||
var newModel = site.createModelFromUrl(url);
|
|
||||||
if (newModel != null) {
|
|
||||||
try {
|
|
||||||
newModel.setMarkedForLaterRecording(true);
|
|
||||||
recorder.addModel(newModel);
|
|
||||||
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
|
||||||
Dialogs.showError(getTabPane().getScene(), "Couldn't add model", "The model " + newModel.getName() + " could not be added: ", e1);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialogs.showError(getTabPane().getScene(), "Unknown URL format",
|
|
||||||
"The URL you entered has an unknown format or the function does not support this site, yet", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addModelByName(String siteModelCombo) {
|
|
||||||
String[] parts = siteModelCombo.trim().split(":");
|
|
||||||
if (parts.length != 2) {
|
|
||||||
Dialogs.showError(getTabPane().getScene(), "Wrong input format", "Use something like \"MyFreeCams:ModelName\"", null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String siteName = parts[0];
|
|
||||||
String modelName = parts[1];
|
|
||||||
for (Site site : sites) {
|
|
||||||
if (Objects.equals(siteName.toLowerCase(), site.getClass().getSimpleName().toLowerCase())) {
|
|
||||||
try {
|
|
||||||
var m = site.createModel(modelName);
|
|
||||||
m.setMarkedForLaterRecording(true);
|
|
||||||
recorder.addModel(m);
|
|
||||||
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
|
||||||
Dialogs.showError(getTabPane().getScene(), "Couldn't add model", "The model " + modelName + " could not be added:", e1);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, getTabPane().getScene());
|
|
||||||
alert.setTitle("Unknown site");
|
|
||||||
alert.setHeaderText("Couldn't add model");
|
|
||||||
alert.setContentText("The site you entered is unknown");
|
|
||||||
alert.showAndWait();
|
|
||||||
}
|
|
||||||
|
|
||||||
void initializeUpdateService() {
|
void initializeUpdateService() {
|
||||||
updateService = createUpdateService();
|
updateService = createUpdateService();
|
||||||
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(2)));
|
updateService.setPeriod(new Duration(TimeUnit.SECONDS.toMillis(2)));
|
||||||
|
@ -388,47 +117,6 @@ public class RecordLaterTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void filter(String filter) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
if (StringUtil.isBlank(filter)) {
|
|
||||||
observableModels.addAll(filteredModels);
|
|
||||||
filteredModels.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] tokens = filter.split(" ");
|
|
||||||
observableModels.addAll(filteredModels);
|
|
||||||
filteredModels.clear();
|
|
||||||
for (var i = 0; i < table.getItems().size(); i++) {
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
for (TableColumn<JavaFxModel, ?> tc : table.getColumns()) {
|
|
||||||
Object cellData = tc.getCellData(i);
|
|
||||||
if (cellData != null) {
|
|
||||||
var content = cellData.toString();
|
|
||||||
sb.append(content).append(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var searchText = sb.toString();
|
|
||||||
|
|
||||||
var tokensMissing = false;
|
|
||||||
for (String token : tokens) {
|
|
||||||
if (!searchText.toLowerCase().contains(token.toLowerCase())) {
|
|
||||||
tokensMissing = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tokensMissing) {
|
|
||||||
JavaFxModel filteredModel = table.getItems().get(i);
|
|
||||||
filteredModels.add(filteredModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
observableModels.removeAll(filteredModels);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ScheduledService<List<JavaFxModel>> createUpdateService() {
|
private ScheduledService<List<JavaFxModel>> createUpdateService() {
|
||||||
ScheduledService<List<JavaFxModel>> modelUpdateService = new ScheduledService<List<JavaFxModel>>() {
|
ScheduledService<List<JavaFxModel>> modelUpdateService = new ScheduledService<List<JavaFxModel>>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -467,23 +155,8 @@ public class RecordLaterTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContextMenu createContextMenu() {
|
@Override
|
||||||
List<Model> selectedModels = table.getSelectionModel().getSelectedItems().stream().map(JavaFxModel::getDelegate).collect(Collectors.toList());
|
void stopAction(List<JavaFxModel> selectedModels) {
|
||||||
if (selectedModels.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContextMenu menu = new CustomMouseBehaviorContextMenu();
|
|
||||||
|
|
||||||
ModelMenuContributor.newContributor(getTabPane(), Config.getInstance(), recorder) //
|
|
||||||
.withStartStopCallback(m -> getTabPane().setCursor(Cursor.DEFAULT)) //
|
|
||||||
.afterwards(table::refresh)
|
|
||||||
.contributeToMenu(selectedModels, menu);
|
|
||||||
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void stopAction(List<JavaFxModel> selectedModels) {
|
|
||||||
var confirmed = true;
|
var confirmed = true;
|
||||||
if (Config.getInstance().getSettings().confirmationForDangerousActions) {
|
if (Config.getInstance().getSettings().confirmationForDangerousActions) {
|
||||||
int n = selectedModels.size();
|
int n = selectedModels.size();
|
||||||
|
@ -500,85 +173,53 @@ public class RecordLaterTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveState() {
|
@Override
|
||||||
if (!table.getSortOrder().isEmpty()) {
|
String getSortColumn() {
|
||||||
TableColumn<JavaFxModel, ?> col = table.getSortOrder().get(0);
|
return Config.getInstance().getSettings().recordLaterSortColumn;
|
||||||
Config.getInstance().getSettings().recordLaterSortColumn = col.getText();
|
}
|
||||||
Config.getInstance().getSettings().recordLaterSortType = col.getSortType().toString();
|
|
||||||
}
|
@Override
|
||||||
int columns = table.getColumns().size();
|
void setSortColumn(String column) {
|
||||||
var columnWidths = new double[columns];
|
Config.getInstance().getSettings().recordLaterSortColumn = column;
|
||||||
var columnIds = new String[columns];
|
}
|
||||||
for (var i = 0; i < columnWidths.length; i++) {
|
|
||||||
columnWidths[i] = table.getColumns().get(i).getWidth();
|
@Override
|
||||||
columnIds[i] = table.getColumns().get(i).getId();
|
String getSortType() {
|
||||||
}
|
return Config.getInstance().getSettings().recordLaterSortType;
|
||||||
Config.getInstance().getSettings().recordLaterColumnWidths = columnWidths;
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setSortType(String sortType) {
|
||||||
|
Config.getInstance().getSettings().recordLaterSortType = sortType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String[] getColumnIds() {
|
||||||
|
return Config.getInstance().getSettings().recordLaterColumnIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setColumnIds(String[] columnIds) {
|
||||||
Config.getInstance().getSettings().recordLaterColumnIds = columnIds;
|
Config.getInstance().getSettings().recordLaterColumnIds = columnIds;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restoreState() {
|
@Override
|
||||||
restoreColumnOrder();
|
double[] getColumnWidths() {
|
||||||
restoreColumnWidths();
|
return Config.getInstance().getSettings().recordLaterColumnWidths;
|
||||||
restoreSorting();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restoreSorting() {
|
@Override
|
||||||
String sortCol = Config.getInstance().getSettings().recordLaterSortColumn;
|
void setColumnWidths(double[] widths) {
|
||||||
if (StringUtil.isNotBlank(sortCol)) {
|
Config.getInstance().getSettings().recordLaterColumnWidths = widths;
|
||||||
for (TableColumn<JavaFxModel, ?> col : table.getColumns()) {
|
|
||||||
if (Objects.equals(sortCol, col.getText())) {
|
|
||||||
col.setSortType(SortType.valueOf(Config.getInstance().getSettings().recordLaterSortType));
|
|
||||||
table.getSortOrder().clear();
|
|
||||||
table.getSortOrder().add(col);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restoreColumnOrder() {
|
@Override
|
||||||
String[] columnIds = Config.getInstance().getSettings().recordLaterColumnIds;
|
List<String> getDisabledColumns() {
|
||||||
ObservableList<TableColumn<JavaFxModel,?>> columns = table.getColumns();
|
return Config.getInstance().getSettings().recordLaterDisabledColumns;
|
||||||
for (var i = 0; i < columnIds.length; i++) {
|
|
||||||
for (var j = 0; j < table.getColumns().size(); j++) {
|
|
||||||
if(Objects.equals(columnIds[i], columns.get(j).getId())) {
|
|
||||||
TableColumn<JavaFxModel, ?> col = columns.get(j);
|
|
||||||
columns.remove(j); // NOSONAR
|
|
||||||
columns.add(i, col);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restoreColumnWidths() {
|
@Override
|
||||||
double[] columnWidths = Config.getInstance().getSettings().recordLaterColumnWidths;
|
boolean getMarkModelsForLaterRecording() {
|
||||||
if (columnWidths != null && columnWidths.length == table.getColumns().size()) {
|
return true;
|
||||||
for (var i = 0; i < columnWidths.length; i++) {
|
|
||||||
table.getColumns().get(i).setPrefWidth(columnWidths[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ClickableCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
|
|
||||||
@Override
|
|
||||||
public TableCell<S, T> call(TableColumn<S, T> param) {
|
|
||||||
TableCell<S, T> cell = new TableCell<>() {
|
|
||||||
@Override
|
|
||||||
protected void updateItem(Object item, boolean empty) {
|
|
||||||
setText(empty ? "" : Objects.toString(item));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
|
|
||||||
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
|
||||||
JavaFxModel selectedModel = table.getSelectionModel().getSelectedItem();
|
|
||||||
if (selectedModel != null) {
|
|
||||||
new PlayAction(table, selectedModel).execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,141 +2,70 @@ package ctbrec.ui.tabs.recorded;
|
||||||
|
|
||||||
import static ctbrec.Recording.State.*;
|
import static ctbrec.Recording.State.*;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
|
||||||
import java.io.FileNotFoundException;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.InvalidKeyException;
|
import java.security.InvalidKeyException;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.concurrent.ExecutionException;
|
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.locks.ReentrantLock;
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import com.google.common.cache.CacheBuilder;
|
|
||||||
import com.google.common.cache.CacheLoader;
|
|
||||||
import com.google.common.cache.LoadingCache;
|
|
||||||
|
|
||||||
import ctbrec.Config;
|
import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.ModelGroup;
|
|
||||||
import ctbrec.Recording;
|
import ctbrec.Recording;
|
||||||
import ctbrec.StringUtil;
|
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
import ctbrec.ui.AutosizeAlert;
|
|
||||||
import ctbrec.ui.JavaFxModel;
|
import ctbrec.ui.JavaFxModel;
|
||||||
import ctbrec.ui.PreviewPopupHandler;
|
|
||||||
import ctbrec.ui.action.CheckModelAccountAction;
|
import ctbrec.ui.action.CheckModelAccountAction;
|
||||||
import ctbrec.ui.action.PauseAction;
|
import ctbrec.ui.action.PauseAction;
|
||||||
import ctbrec.ui.action.PlayAction;
|
|
||||||
import ctbrec.ui.action.ResumeAction;
|
import ctbrec.ui.action.ResumeAction;
|
||||||
import ctbrec.ui.action.SetPortraitAction;
|
|
||||||
import ctbrec.ui.action.StopRecordingAction;
|
import ctbrec.ui.action.StopRecordingAction;
|
||||||
import ctbrec.ui.action.ToggleRecordingAction;
|
import ctbrec.ui.action.ToggleRecordingAction;
|
||||||
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
|
|
||||||
import ctbrec.ui.controls.DateTimeCellFactory;
|
import ctbrec.ui.controls.DateTimeCellFactory;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
import ctbrec.ui.controls.SearchBox;
|
|
||||||
import ctbrec.ui.controls.autocomplete.AutoFillTextField;
|
|
||||||
import ctbrec.ui.controls.autocomplete.ObservableListSuggester;
|
|
||||||
import ctbrec.ui.menu.ModelMenuContributor;
|
|
||||||
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.SimpleStringProperty;
|
|
||||||
import javafx.beans.property.StringPropertyBase;
|
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.collections.FXCollections;
|
|
||||||
import javafx.collections.ObservableList;
|
|
||||||
import javafx.concurrent.ScheduledService;
|
import javafx.concurrent.ScheduledService;
|
||||||
import javafx.concurrent.Task;
|
import javafx.concurrent.Task;
|
||||||
import javafx.concurrent.WorkerStateEvent;
|
import javafx.concurrent.WorkerStateEvent;
|
||||||
import javafx.event.ActionEvent;
|
import javafx.event.ActionEvent;
|
||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Point2D;
|
|
||||||
import javafx.geometry.Pos;
|
|
||||||
import javafx.scene.Cursor;
|
|
||||||
import javafx.scene.control.Alert;
|
|
||||||
import javafx.scene.control.Button;
|
import javafx.scene.control.Button;
|
||||||
import javafx.scene.control.CheckMenuItem;
|
|
||||||
import javafx.scene.control.ContextMenu;
|
|
||||||
import javafx.scene.control.Label;
|
|
||||||
import javafx.scene.control.ScrollPane;
|
|
||||||
import javafx.scene.control.SelectionMode;
|
|
||||||
import javafx.scene.control.Tab;
|
|
||||||
import javafx.scene.control.TableCell;
|
import javafx.scene.control.TableCell;
|
||||||
import javafx.scene.control.TableColumn;
|
import javafx.scene.control.TableColumn;
|
||||||
import javafx.scene.control.TableColumn.CellEditEvent;
|
import javafx.scene.control.TableColumn.CellEditEvent;
|
||||||
import javafx.scene.control.TableColumn.SortType;
|
|
||||||
import javafx.scene.control.TableRow;
|
|
||||||
import javafx.scene.control.TableView;
|
|
||||||
import javafx.scene.control.TextField;
|
|
||||||
import javafx.scene.control.ToggleButton;
|
import javafx.scene.control.ToggleButton;
|
||||||
import javafx.scene.control.Tooltip;
|
|
||||||
import javafx.scene.control.cell.CheckBoxTableCell;
|
import javafx.scene.control.cell.CheckBoxTableCell;
|
||||||
import javafx.scene.control.cell.PropertyValueFactory;
|
|
||||||
import javafx.scene.control.cell.TextFieldTableCell;
|
import javafx.scene.control.cell.TextFieldTableCell;
|
||||||
import javafx.scene.image.Image;
|
|
||||||
import javafx.scene.input.ContextMenuEvent;
|
|
||||||
import javafx.scene.input.KeyCode;
|
|
||||||
import javafx.scene.input.KeyEvent;
|
|
||||||
import javafx.scene.input.MouseButton;
|
|
||||||
import javafx.scene.input.MouseEvent;
|
|
||||||
import javafx.scene.layout.BorderPane;
|
import javafx.scene.layout.BorderPane;
|
||||||
import javafx.scene.layout.FlowPane;
|
|
||||||
import javafx.scene.layout.HBox;
|
import javafx.scene.layout.HBox;
|
||||||
import javafx.scene.layout.Priority;
|
|
||||||
import javafx.util.Callback;
|
import javafx.util.Callback;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
import javafx.util.StringConverter;
|
import javafx.util.StringConverter;
|
||||||
import javafx.util.converter.NumberStringConverter;
|
import javafx.util.converter.NumberStringConverter;
|
||||||
|
|
||||||
public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
public class RecordedModelsTab extends AbstractRecordedModelsTab implements TabSelectionListener {
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(RecordedModelsTab.class);
|
private static final Logger LOG = LoggerFactory.getLogger(RecordedModelsTab.class);
|
||||||
|
|
||||||
private static final String STYLE_ALIGN_CENTER = "-fx-alignment: CENTER;";
|
private static final String STYLE_ALIGN_CENTER = "-fx-alignment: CENTER;";
|
||||||
|
|
||||||
private ReentrantLock lock = new ReentrantLock();
|
|
||||||
private ScheduledService<List<JavaFxModel>> updateService;
|
private ScheduledService<List<JavaFxModel>> updateService;
|
||||||
private Recorder recorder;
|
|
||||||
private List<Site> sites;
|
|
||||||
private volatile boolean cellEditing = false;
|
private volatile boolean cellEditing = false;
|
||||||
|
|
||||||
FlowPane grid = new FlowPane();
|
|
||||||
ScrollPane scrollPane = new ScrollPane();
|
|
||||||
TableView<JavaFxModel> table = new TableView<>();
|
|
||||||
private List<TableColumn<JavaFxModel, ?>> columns = new ArrayList<>();
|
|
||||||
ObservableList<JavaFxModel> observableModels = FXCollections.observableArrayList();
|
|
||||||
ObservableList<JavaFxModel> filteredModels = FXCollections.observableArrayList();
|
|
||||||
ContextMenu popup;
|
|
||||||
|
|
||||||
Label modelLabel = new Label("Model");
|
|
||||||
AutoFillTextField model;
|
|
||||||
Button addModelButton = new Button("Record");
|
|
||||||
Button pauseAll = new Button("Pause All");
|
Button pauseAll = new Button("Pause All");
|
||||||
Button resumeAll = new Button("Resume All");
|
Button resumeAll = new Button("Resume All");
|
||||||
ToggleButton toggleRecording = new ToggleButton("Pause Recording");
|
ToggleButton toggleRecording = new ToggleButton("Pause Recording");
|
||||||
Button checkModelAccountExistance = new Button("Check URLs");
|
|
||||||
TextField filter;
|
|
||||||
|
|
||||||
LoadingCache<Model, Image> portraitCache = CacheBuilder.newBuilder()
|
|
||||||
.expireAfterAccess(1, TimeUnit.DAYS)
|
|
||||||
.maximumSize(1000)
|
|
||||||
.build(CacheLoader.from(RecordedModelsTab::loadModelPortrait));
|
|
||||||
|
|
||||||
public RecordedModelsTab(String title, Recorder recorder, List<Site> sites) {
|
public RecordedModelsTab(String title, Recorder recorder, List<Site> sites) {
|
||||||
super(title);
|
super(title);
|
||||||
|
@ -147,77 +76,17 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
initializeUpdateService();
|
initializeUpdateService();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createGui() {
|
@Override
|
||||||
grid.setPadding(new Insets(5));
|
protected void createGui() {
|
||||||
grid.setHgap(5);
|
super.createGui();
|
||||||
grid.setVgap(5);
|
|
||||||
|
|
||||||
scrollPane.setContent(grid);
|
|
||||||
scrollPane.setFitToHeight(true);
|
|
||||||
scrollPane.setFitToWidth(true);
|
|
||||||
BorderPane.setMargin(scrollPane, new Insets(5));
|
|
||||||
|
|
||||||
int idx = 0;
|
int idx = 0;
|
||||||
table.setEditable(true);
|
|
||||||
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
|
||||||
var previewPopupHandler = new PreviewPopupHandler(table);
|
|
||||||
table.setRowFactory(tableview -> {
|
|
||||||
TableRow<JavaFxModel> row = new TableRow<>();
|
|
||||||
row.addEventHandler(MouseEvent.ANY, previewPopupHandler);
|
|
||||||
return row;
|
|
||||||
});
|
|
||||||
TableColumn<JavaFxModel, String> preview = new TableColumn<>("🎥");
|
|
||||||
preview.setPrefWidth(35);
|
|
||||||
preview.setCellValueFactory(cdf -> new SimpleStringProperty(" ▶ "));
|
|
||||||
preview.setEditable(false);
|
|
||||||
preview.setId("preview");
|
|
||||||
preview.setUserData(idx++);
|
|
||||||
columns.add(preview);
|
|
||||||
addTableColumnIfEnabled(preview);
|
|
||||||
if (!Config.getInstance().getSettings().livePreviews) {
|
|
||||||
preview.setVisible(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
TableColumn<JavaFxModel, Image> portrait = new TableColumn<>("Portrait");
|
addPreviewColumn(idx++);
|
||||||
portrait.setPrefWidth(80);
|
addPortraitColumn(idx++);
|
||||||
portrait.setCellValueFactory(param -> {
|
addModelColumn(idx++);
|
||||||
Model mdl = param.getValue().getDelegate();
|
addUrlColumn(idx++);
|
||||||
Image image = null;
|
|
||||||
try {
|
|
||||||
image = portraitCache.get(mdl);
|
|
||||||
} catch (ExecutionException e) {
|
|
||||||
LOG.error("Error while loading portrait from cache for {}", mdl, e);
|
|
||||||
}
|
|
||||||
return new SimpleObjectProperty<Image>(image);
|
|
||||||
});
|
|
||||||
portrait.setCellFactory(param -> new ImageTableCell());
|
|
||||||
portrait.setEditable(false);
|
|
||||||
portrait.setId("portrait");
|
|
||||||
portrait.setUserData(idx++);
|
|
||||||
columns.add(portrait);
|
|
||||||
addTableColumnIfEnabled(portrait);
|
|
||||||
|
|
||||||
TableColumn<JavaFxModel, ModelName> name = new TableColumn<>("Model");
|
|
||||||
name.setPrefWidth(200);
|
|
||||||
name.setCellValueFactory(param -> {
|
|
||||||
var modelName = new ModelName(param.getValue(), recorder);
|
|
||||||
return new SimpleObjectProperty<>(modelName);
|
|
||||||
});
|
|
||||||
name.setCellFactory(param -> new ModelNameTableCell(recorder));
|
|
||||||
name.setEditable(false);
|
|
||||||
name.setId("name");
|
|
||||||
name.setUserData(idx++);
|
|
||||||
columns.add(name);
|
|
||||||
addTableColumnIfEnabled(name);
|
|
||||||
TableColumn<JavaFxModel, String> url = new TableColumn<>("URL");
|
|
||||||
url.setCellValueFactory(new PropertyValueFactory<>("url"));
|
|
||||||
url.setCellFactory(new ClickableCellFactory<>());
|
|
||||||
url.setPrefWidth(400);
|
|
||||||
url.setEditable(false);
|
|
||||||
url.setId("url");
|
|
||||||
url.setUserData(idx++);
|
|
||||||
columns.add(url);
|
|
||||||
addTableColumnIfEnabled(url);
|
|
||||||
TableColumn<JavaFxModel, Boolean> online = new TableColumn<>("Online");
|
TableColumn<JavaFxModel, Boolean> online = new TableColumn<>("Online");
|
||||||
online.setCellValueFactory(cdf -> cdf.getValue().getOnlineProperty());
|
online.setCellValueFactory(cdf -> cdf.getValue().getOnlineProperty());
|
||||||
online.setCellFactory(param -> new OnlineTableCell());
|
online.setCellFactory(param -> new OnlineTableCell());
|
||||||
|
@ -277,73 +146,12 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
lastRecorded.setUserData(idx++);
|
lastRecorded.setUserData(idx++);
|
||||||
columns.add(lastRecorded);
|
columns.add(lastRecorded);
|
||||||
addTableColumnIfEnabled(lastRecorded);
|
addTableColumnIfEnabled(lastRecorded);
|
||||||
TableColumn<JavaFxModel, String> notes = new TableColumn<>("Notes");
|
|
||||||
notes.setCellValueFactory(cdf -> {
|
|
||||||
JavaFxModel m = cdf.getValue();
|
|
||||||
return new StringPropertyBase() {
|
|
||||||
@Override
|
|
||||||
public String getName() {
|
|
||||||
return "Model Notes";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
addNotesColumn(idx);
|
||||||
public Object getBean() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
addModelBox.getChildren().add(3, pauseAll);
|
||||||
public String get() {
|
addModelBox.getChildren().add(4, resumeAll);
|
||||||
String modelNotes = Config.getInstance().getModelNotes(m);
|
addModelBox.getChildren().add(5, toggleRecording);
|
||||||
return modelNotes;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
});
|
|
||||||
notes.setPrefWidth(400);
|
|
||||||
notes.setEditable(false);
|
|
||||||
notes.setId("notes");
|
|
||||||
notes.setUserData(idx);
|
|
||||||
columns.add(notes);
|
|
||||||
addTableColumnIfEnabled(notes);
|
|
||||||
|
|
||||||
table.setItems(observableModels);
|
|
||||||
table.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
|
|
||||||
popup = createContextMenu();
|
|
||||||
if (popup != null) {
|
|
||||||
popup.show(table, event.getScreenX(), event.getScreenY());
|
|
||||||
}
|
|
||||||
event.consume();
|
|
||||||
});
|
|
||||||
table.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
|
|
||||||
if (popup != null) {
|
|
||||||
popup.hide();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
table.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
|
|
||||||
List<JavaFxModel> selectedModels = table.getSelectionModel().getSelectedItems();
|
|
||||||
if (event.getCode() == KeyCode.DELETE) {
|
|
||||||
stopAction(selectedModels);
|
|
||||||
} else {
|
|
||||||
jumpToNextModel(event.getCode());
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
scrollPane.setContent(table);
|
|
||||||
|
|
||||||
var addModelBox = new HBox(5);
|
|
||||||
modelLabel.setPadding(new Insets(5, 0, 0, 0));
|
|
||||||
ObservableList<String> suggestions = FXCollections.observableArrayList();
|
|
||||||
sites.forEach(site -> suggestions.add(site.getClass().getSimpleName()));
|
|
||||||
model = new AutoFillTextField(new ObservableListSuggester(suggestions));
|
|
||||||
model.minWidth(150);
|
|
||||||
model.prefWidth(600);
|
|
||||||
HBox.setHgrow(model, Priority.ALWAYS);
|
|
||||||
model.setPromptText("e.g. MyFreeCams:ModelName or an URL like https://chaturbate.com/modelname/");
|
|
||||||
model.onActionHandler(this::addModel);
|
|
||||||
model.setTooltip(new Tooltip("To add a model enter SiteName:ModelName\n" + "press ENTER to confirm a suggested site name"));
|
|
||||||
BorderPane.setMargin(addModelBox, new Insets(5));
|
|
||||||
addModelButton.setOnAction(this::addModel);
|
|
||||||
addModelButton.setPadding(new Insets(5));
|
|
||||||
addModelBox.getChildren().addAll(modelLabel, model, addModelButton, pauseAll, resumeAll, toggleRecording, checkModelAccountExistance);
|
|
||||||
HBox.setMargin(pauseAll, new Insets(0, 0, 0, 20));
|
HBox.setMargin(pauseAll, new Insets(0, 0, 0, 20));
|
||||||
pauseAll.setOnAction(this::pauseAll);
|
pauseAll.setOnAction(this::pauseAll);
|
||||||
resumeAll.setOnAction(this::resumeAll);
|
resumeAll.setOnAction(this::resumeAll);
|
||||||
|
@ -352,44 +160,10 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
toggleRecording.setPadding(new Insets(5));
|
toggleRecording.setPadding(new Insets(5));
|
||||||
toggleRecording.setOnAction(this::toggleRecording);
|
toggleRecording.setOnAction(this::toggleRecording);
|
||||||
HBox.setMargin(toggleRecording, new Insets(0, 0, 0, 20));
|
HBox.setMargin(toggleRecording, new Insets(0, 0, 0, 20));
|
||||||
checkModelAccountExistance.setPadding(new Insets(5));
|
|
||||||
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));
|
|
||||||
checkModelAccountExistance
|
checkModelAccountExistance
|
||||||
.setOnAction(evt -> new CheckModelAccountAction(checkModelAccountExistance, recorder).execute(Predicate.not(Model::isMarkedForLaterRecording)));
|
.setOnAction(evt -> new CheckModelAccountAction(checkModelAccountExistance, recorder).execute(Predicate.not(Model::isMarkedForLaterRecording)));
|
||||||
|
|
||||||
|
|
||||||
var filterContainer = new HBox();
|
|
||||||
filterContainer.setSpacing(5);
|
|
||||||
filterContainer.setPadding(new Insets(0));
|
|
||||||
filterContainer.setAlignment(Pos.CENTER_RIGHT);
|
|
||||||
filterContainer.minWidth(100);
|
|
||||||
filterContainer.prefWidth(150);
|
|
||||||
HBox.setHgrow(filterContainer, Priority.ALWAYS);
|
|
||||||
filter = new SearchBox(false);
|
|
||||||
filter.minWidth(100);
|
|
||||||
filter.prefWidth(150);
|
|
||||||
filter.setPromptText("Filter");
|
|
||||||
filter.textProperty().addListener((observableValue, oldValue, newValue) -> {
|
|
||||||
String q = filter.getText();
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
filter(q);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
filter.getStyleClass().remove("search-box-icon");
|
|
||||||
|
|
||||||
var columnSelection = new Button("⚙");
|
|
||||||
columnSelection.setOnAction(this::showColumnSelection);
|
|
||||||
columnSelection.setTooltip(new Tooltip("Select columns"));
|
|
||||||
columnSelection.prefHeightProperty().bind(filter.prefHeightProperty());
|
|
||||||
columnSelection.prefWidthProperty().bind(columnSelection.prefHeightProperty());
|
|
||||||
|
|
||||||
filterContainer.getChildren().addAll(columnSelection, filter);
|
|
||||||
addModelBox.getChildren().add(filterContainer);
|
|
||||||
|
|
||||||
var root = new BorderPane();
|
var root = new BorderPane();
|
||||||
root.setPadding(new Insets(5));
|
root.setPadding(new Insets(5));
|
||||||
root.setTop(addModelBox);
|
root.setTop(addModelBox);
|
||||||
|
@ -399,41 +173,6 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
restoreState();
|
restoreState();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTableColumnIfEnabled(TableColumn<JavaFxModel, ?> tc) {
|
|
||||||
if(isColumnEnabled(tc)) {
|
|
||||||
table.getColumns().add(tc);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void jumpToNextModel(KeyCode code) {
|
|
||||||
if (!table.getItems().isEmpty() && (code.isLetterKey() || code.isDigitKey())) {
|
|
||||||
// determine where to start looking for the next model
|
|
||||||
var startAt = 0;
|
|
||||||
if (table.getSelectionModel().getSelectedIndex() >= 0) {
|
|
||||||
startAt = table.getSelectionModel().getSelectedIndex() + 1;
|
|
||||||
if (startAt >= table.getItems().size()) {
|
|
||||||
startAt = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var c = code.getChar().toLowerCase();
|
|
||||||
int i = startAt;
|
|
||||||
do {
|
|
||||||
JavaFxModel current = table.getItems().get(i);
|
|
||||||
if (current.getName().toLowerCase().replaceAll("[^0-9a-z]", "").startsWith(c)) {
|
|
||||||
table.getSelectionModel().clearAndSelect(i);
|
|
||||||
table.scrollTo(i);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
i++;
|
|
||||||
if (i >= table.getItems().size()) {
|
|
||||||
i = 0;
|
|
||||||
}
|
|
||||||
} while (i != startAt);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void onUpdatePriority(CellEditEvent<JavaFxModel, Number> evt) {
|
private void onUpdatePriority(CellEditEvent<JavaFxModel, Number> evt) {
|
||||||
try {
|
try {
|
||||||
int prio = Optional.ofNullable(evt.getNewValue()).map(Number::intValue).orElse(-1);
|
int prio = Optional.ofNullable(evt.getNewValue()).map(Number::intValue).orElse(-1);
|
||||||
|
@ -459,64 +198,6 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addModel(ActionEvent e) {
|
|
||||||
String input = model.getText().trim();
|
|
||||||
if (StringUtil.isBlank(input)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (input.startsWith("http")) {
|
|
||||||
addModelByUrl(input);
|
|
||||||
} else {
|
|
||||||
addModelByName(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addModelByUrl(String url) {
|
|
||||||
for (Site site : sites) {
|
|
||||||
var newModel = site.createModelFromUrl(url);
|
|
||||||
if (newModel != null) {
|
|
||||||
try {
|
|
||||||
recorder.addModel(newModel);
|
|
||||||
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
|
||||||
Dialogs.showError(getTabPane().getScene(), "Couldn't add model", "The model " + newModel.getName() + " could not be added: ", e1);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Dialogs.showError(getTabPane().getScene(), "Unknown URL format",
|
|
||||||
"The URL you entered has an unknown format or the function does not support this site, yet", null);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void addModelByName(String siteModelCombo) {
|
|
||||||
String[] parts = siteModelCombo.trim().split(":");
|
|
||||||
if (parts.length != 2) {
|
|
||||||
Dialogs.showError(getTabPane().getScene(), "Wrong input format", "Use something like \"MyFreeCams:ModelName\"", null);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String siteName = parts[0];
|
|
||||||
String modelName = parts[1];
|
|
||||||
for (Site site : sites) {
|
|
||||||
if (Objects.equals(siteName.toLowerCase(), site.getClass().getSimpleName().toLowerCase())) {
|
|
||||||
try {
|
|
||||||
var m = site.createModel(modelName);
|
|
||||||
recorder.addModel(m);
|
|
||||||
} catch (IOException | InvalidKeyException | NoSuchAlgorithmException | IllegalStateException e1) {
|
|
||||||
Dialogs.showError(getTabPane().getScene(), "Couldn't add model", "The model " + modelName + " could not be added:", e1);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Alert alert = new AutosizeAlert(Alert.AlertType.ERROR, getTabPane().getScene());
|
|
||||||
alert.setTitle("Unknown site");
|
|
||||||
alert.setHeaderText("Couldn't add model");
|
|
||||||
alert.setContentText("The site you entered is unknown");
|
|
||||||
alert.showAndWait();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void pauseAll(ActionEvent evt) {
|
private void pauseAll(ActionEvent evt) {
|
||||||
boolean yes = Dialogs.showConfirmDialog("Pause all models", "", "Pause the recording of all models?", getTabPane().getScene());
|
boolean yes = Dialogs.showConfirmDialog("Pause all models", "", "Pause the recording of all models?", getTabPane().getScene());
|
||||||
if (yes) {
|
if (yes) {
|
||||||
|
@ -609,47 +290,6 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void filter(String filter) {
|
|
||||||
lock.lock();
|
|
||||||
try {
|
|
||||||
if (StringUtil.isBlank(filter)) {
|
|
||||||
observableModels.addAll(filteredModels);
|
|
||||||
filteredModels.clear();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
String[] tokens = filter.split(" ");
|
|
||||||
observableModels.addAll(filteredModels);
|
|
||||||
filteredModels.clear();
|
|
||||||
for (var i = 0; i < table.getItems().size(); i++) {
|
|
||||||
var sb = new StringBuilder();
|
|
||||||
for (TableColumn<JavaFxModel, ?> tc : table.getColumns()) {
|
|
||||||
Object cellData = tc.getCellData(i);
|
|
||||||
if (cellData != null) {
|
|
||||||
var content = cellData.toString();
|
|
||||||
sb.append(content).append(' ');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var searchText = sb.toString();
|
|
||||||
|
|
||||||
var tokensMissing = false;
|
|
||||||
for (String token : tokens) {
|
|
||||||
if (!searchText.toLowerCase().contains(token.toLowerCase())) {
|
|
||||||
tokensMissing = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (tokensMissing) {
|
|
||||||
JavaFxModel filteredModel = table.getItems().get(i);
|
|
||||||
filteredModels.add(filteredModel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
observableModels.removeAll(filteredModels);
|
|
||||||
} finally {
|
|
||||||
lock.unlock();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private ScheduledService<List<JavaFxModel>> createUpdateService() {
|
private ScheduledService<List<JavaFxModel>> createUpdateService() {
|
||||||
ScheduledService<List<JavaFxModel>> modelUpdateService = new ScheduledService<List<JavaFxModel>>() {
|
ScheduledService<List<JavaFxModel>> modelUpdateService = new ScheduledService<List<JavaFxModel>>() {
|
||||||
@Override
|
@Override
|
||||||
|
@ -704,28 +344,8 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ContextMenu createContextMenu() {
|
@Override
|
||||||
List<Model> selectedModels = table.getSelectionModel().getSelectedItems().stream().map(JavaFxModel::getDelegate).collect(Collectors.toList());
|
void stopAction(List<JavaFxModel> selectedModels) {
|
||||||
if (selectedModels.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
ContextMenu menu = new CustomMouseBehaviorContextMenu();
|
|
||||||
|
|
||||||
ModelMenuContributor.newContributor(getTabPane(), Config.getInstance(), recorder) //
|
|
||||||
.withStartStopCallback(m -> getTabPane().setCursor(Cursor.DEFAULT)) //
|
|
||||||
.removeModelAfterIgnore(true) //
|
|
||||||
.withPortraitCallback(m -> {
|
|
||||||
portraitCache.invalidate(m);
|
|
||||||
table.refresh();
|
|
||||||
})
|
|
||||||
.afterwards(table::refresh) //
|
|
||||||
.contributeToMenu(selectedModels, menu);
|
|
||||||
|
|
||||||
return menu;
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean stopAction(List<JavaFxModel> selectedModels) {
|
|
||||||
var confirmed = true;
|
var confirmed = true;
|
||||||
if (Config.getInstance().getSettings().confirmationForDangerousActions) {
|
if (Config.getInstance().getSettings().confirmationForDangerousActions) {
|
||||||
int n = selectedModels.size();
|
int n = selectedModels.size();
|
||||||
|
@ -740,7 +360,6 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
table.getItems().remove(m);
|
table.getItems().remove(m);
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
return confirmed;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void pauseRecording(List<JavaFxModel> selectedModels) {
|
private void pauseRecording(List<JavaFxModel> selectedModels) {
|
||||||
|
@ -753,88 +372,6 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
new ResumeAction(getTabPane(), models, recorder).execute();
|
new ResumeAction(getTabPane(), models, recorder).execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveState() {
|
|
||||||
if (!table.getSortOrder().isEmpty()) {
|
|
||||||
TableColumn<JavaFxModel, ?> col = table.getSortOrder().get(0);
|
|
||||||
Config.getInstance().getSettings().recordedModelsSortColumn = col.getText();
|
|
||||||
Config.getInstance().getSettings().recordedModelsSortType = col.getSortType().toString();
|
|
||||||
}
|
|
||||||
int columns = table.getColumns().size();
|
|
||||||
var columnWidths = new double[columns];
|
|
||||||
var columnIds = new String[columns];
|
|
||||||
for (var i = 0; i < columnWidths.length; i++) {
|
|
||||||
columnWidths[i] = table.getColumns().get(i).getWidth();
|
|
||||||
columnIds[i] = table.getColumns().get(i).getId();
|
|
||||||
}
|
|
||||||
Config.getInstance().getSettings().recordedModelsColumnWidths = columnWidths;
|
|
||||||
Config.getInstance().getSettings().recordedModelsColumnIds = columnIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restoreState() {
|
|
||||||
restoreColumnOrder();
|
|
||||||
restoreColumnWidths();
|
|
||||||
restoreSorting();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restoreSorting() {
|
|
||||||
String sortCol = Config.getInstance().getSettings().recordedModelsSortColumn;
|
|
||||||
if (StringUtil.isNotBlank(sortCol)) {
|
|
||||||
for (TableColumn<JavaFxModel, ?> col : table.getColumns()) {
|
|
||||||
if (Objects.equals(sortCol, col.getText())) {
|
|
||||||
col.setSortType(SortType.valueOf(Config.getInstance().getSettings().recordedModelsSortType));
|
|
||||||
table.getSortOrder().clear();
|
|
||||||
table.getSortOrder().add(col);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restoreColumnOrder() {
|
|
||||||
String[] columnIds = Config.getInstance().getSettings().recordedModelsColumnIds;
|
|
||||||
ObservableList<TableColumn<JavaFxModel, ?>> columns = table.getColumns();
|
|
||||||
for (var i = 0; i < columnIds.length; i++) {
|
|
||||||
for (var j = 0; j < table.getColumns().size(); j++) {
|
|
||||||
if (Objects.equals(columnIds[i], columns.get(j).getId())) {
|
|
||||||
TableColumn<JavaFxModel, ?> col = columns.get(j);
|
|
||||||
columns.remove(j); // NOSONAR
|
|
||||||
columns.add(i, col);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void restoreColumnWidths() {
|
|
||||||
double[] columnWidths = Config.getInstance().getSettings().recordedModelsColumnWidths;
|
|
||||||
if (columnWidths != null && columnWidths.length == table.getColumns().size()) {
|
|
||||||
for (var i = 0; i < columnWidths.length; i++) {
|
|
||||||
table.getColumns().get(i).setPrefWidth(columnWidths[i]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class ClickableCellFactory<S, T> implements Callback<TableColumn<S, T>, TableCell<S, T>> {
|
|
||||||
@Override
|
|
||||||
public TableCell<S, T> call(TableColumn<S, T> param) {
|
|
||||||
TableCell<S, T> cell = new TableCell<>() {
|
|
||||||
@Override
|
|
||||||
protected void updateItem(Object item, boolean empty) {
|
|
||||||
setText(empty ? "" : Objects.toString(item));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
cell.addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
|
|
||||||
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
|
|
||||||
JavaFxModel selectedModel = table.getSelectionModel().getSelectedItem();
|
|
||||||
if (selectedModel != null) {
|
|
||||||
new PlayAction(table, selectedModel).execute();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return cell;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class PriorityCellFactory implements Callback<TableColumn<JavaFxModel, Number>, TableCell<JavaFxModel, Number>> {
|
private class PriorityCellFactory implements Callback<TableColumn<JavaFxModel, Number>, TableCell<JavaFxModel, Number>> {
|
||||||
@Override
|
@Override
|
||||||
public TableCell<JavaFxModel, Number> call(TableColumn<JavaFxModel, Number> param) {
|
public TableCell<JavaFxModel, Number> call(TableColumn<JavaFxModel, Number> param) {
|
||||||
|
@ -862,86 +399,53 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class ModelName {
|
@Override
|
||||||
private Model mdl;
|
boolean getMarkModelsForLaterRecording() {
|
||||||
private Recorder rec;
|
return false;
|
||||||
|
|
||||||
public ModelName(Model model, Recorder recorder) {
|
|
||||||
mdl = model;
|
|
||||||
rec = recorder;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
Optional<ModelGroup> modelGroup = rec.getModelGroup(mdl);
|
|
||||||
String s;
|
|
||||||
if (modelGroup.isPresent()) {
|
|
||||||
s = modelGroup.get().getName() + " (aka " + mdl.getDisplayName() + ')';
|
|
||||||
} else {
|
|
||||||
return mdl.toString();
|
|
||||||
}
|
|
||||||
return s;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Image loadModelPortrait(Model model) {
|
@Override
|
||||||
String portraitId = Config.getInstance().getSettings().modelPortraits.get(model.getUrl());
|
String getSortColumn() {
|
||||||
if (StringUtil.isNotBlank(portraitId)) {
|
return Config.getInstance().getSettings().recordedModelsSortColumn;
|
||||||
File configDir = Config.getInstance().getConfigDir();
|
|
||||||
File portraitDir = new File(configDir, "portraits");
|
|
||||||
File portraitFile = new File(portraitDir, portraitId + '.' + SetPortraitAction.FORMAT);
|
|
||||||
try {
|
|
||||||
return new Image(new FileInputStream(portraitFile));
|
|
||||||
} catch (FileNotFoundException e) {
|
|
||||||
LOG.error("Couldn't load portrait file {}", portraitFile, e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return new Image(RecordedModelsTab.class.getResourceAsStream("/silhouette_256.png"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showColumnSelection(ActionEvent evt) {
|
@Override
|
||||||
ContextMenu menu = new CustomMouseBehaviorContextMenu();
|
void setSortColumn(String column) {
|
||||||
for (TableColumn<JavaFxModel, ?> tc : columns) {
|
Config.getInstance().getSettings().recordedModelsSortColumn = column;
|
||||||
var item = new CheckMenuItem(tc.getText());
|
|
||||||
item.setSelected(isColumnEnabled(tc));
|
|
||||||
menu.getItems().add(item);
|
|
||||||
item.setOnAction(e -> {
|
|
||||||
try {
|
|
||||||
if (item.isSelected()) {
|
|
||||||
Config.getInstance().getSettings().disabledRecordedModelsTableColumns.remove(tc.getText());
|
|
||||||
boolean added = false;
|
|
||||||
for (int i = table.getColumns().size() - 1; i >= 0; i--) {
|
|
||||||
TableColumn<JavaFxModel, ?> other = table.getColumns().get(i);
|
|
||||||
if (!other.isVisible()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int idx = (int) tc.getUserData();
|
|
||||||
LOG.debug("otherIdx {}", other.getText());
|
|
||||||
int otherIdx = (int) other.getUserData();
|
|
||||||
if (otherIdx < idx) {
|
|
||||||
table.getColumns().add(i + 1, tc);
|
|
||||||
added = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!added) {
|
|
||||||
table.getColumns().add(0, tc);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Config.getInstance().getSettings().disabledRecordedModelsTableColumns.add(tc.getText());
|
|
||||||
table.getColumns().remove(tc);
|
|
||||||
}
|
|
||||||
} catch (Exception ex) {
|
|
||||||
LOG.error("Couldn't activate column {}", tc, ex);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Button src = (Button) evt.getSource();
|
|
||||||
Point2D location = src.localToScreen(src.getTranslateX(), src.getTranslateY());
|
|
||||||
menu.show(getTabPane().getScene().getWindow(), location.getX(), location.getY() + src.getHeight() + 5);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isColumnEnabled(TableColumn<JavaFxModel, ?> tc) {
|
@Override
|
||||||
return !Config.getInstance().getSettings().disabledRecordedModelsTableColumns.contains(tc.getText());
|
String getSortType() {
|
||||||
|
return Config.getInstance().getSettings().recordedModelsSortType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setSortType(String sortType) {
|
||||||
|
Config.getInstance().getSettings().recordedModelsSortType = sortType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
String[] getColumnIds() {
|
||||||
|
return Config.getInstance().getSettings().recordedModelsColumnIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setColumnIds(String[] ids) {
|
||||||
|
Config.getInstance().getSettings().recordedModelsColumnIds = ids;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
double[] getColumnWidths() {
|
||||||
|
return Config.getInstance().getSettings().recordedModelsColumnWidths;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
void setColumnWidths(double[] widths) {
|
||||||
|
Config.getInstance().getSettings().recordedModelsColumnWidths = widths;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
List<String> getDisabledColumns() {
|
||||||
|
return Config.getInstance().getSettings().recordedModelsDisabledTableColumns;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -64,7 +64,6 @@ public class Settings {
|
||||||
public boolean confirmationForDangerousActions = false;
|
public boolean confirmationForDangerousActions = false;
|
||||||
public String contactsheetTimestampLook = "font=sans-serif:fontcolor=white:fontsize=60:box=1:boxcolor=black@0.5:boxborderw=5";
|
public String contactsheetTimestampLook = "font=sans-serif:fontcolor=white:fontsize=60:box=1:boxcolor=black@0.5:boxborderw=5";
|
||||||
public boolean determineResolution = false;
|
public boolean determineResolution = false;
|
||||||
public List<String> disabledRecordedModelsTableColumns = new ArrayList<>();
|
|
||||||
public List<String> disabledSites = new ArrayList<>();
|
public List<String> disabledSites = new ArrayList<>();
|
||||||
public String downloadFilename = "${modelSanitizedName}-${localDateTime}";
|
public String downloadFilename = "${modelSanitizedName}-${localDateTime}";
|
||||||
public List<EventHandlerConfiguration> eventHandlers = new ArrayList<>();
|
public List<EventHandlerConfiguration> eventHandlers = new ArrayList<>();
|
||||||
|
@ -139,10 +138,12 @@ public class Settings {
|
||||||
public boolean recentlyWatched = true;
|
public boolean recentlyWatched = true;
|
||||||
public double[] recordLaterColumnWidths = new double[0];
|
public double[] recordLaterColumnWidths = new double[0];
|
||||||
public String[] recordLaterColumnIds = new String[0];
|
public String[] recordLaterColumnIds = new String[0];
|
||||||
|
public List<String> recordLaterDisabledColumns = new ArrayList<>();
|
||||||
public String recordLaterSortColumn = "";
|
public String recordLaterSortColumn = "";
|
||||||
public String recordLaterSortType = "";
|
public String recordLaterSortType = "";
|
||||||
public double[] recordedModelsColumnWidths = new double[0];
|
public double[] recordedModelsColumnWidths = new double[0];
|
||||||
public String[] recordedModelsColumnIds = new String[0];
|
public String[] recordedModelsColumnIds = new String[0];
|
||||||
|
public List<String> recordedModelsDisabledTableColumns = new ArrayList<>();
|
||||||
public String recordedModelsSortColumn = "";
|
public String recordedModelsSortColumn = "";
|
||||||
public String recordedModelsSortType = "";
|
public String recordedModelsSortType = "";
|
||||||
public double[] recordingsColumnWidths = new double[0];
|
public double[] recordingsColumnWidths = new double[0];
|
||||||
|
|
Loading…
Reference in New Issue