diff --git a/client/src/main/java/ctbrec/ui/RecordedModelsTab.java b/client/src/main/java/ctbrec/ui/RecordedModelsTab.java index c4276e8a..e03695eb 100644 --- a/client/src/main/java/ctbrec/ui/RecordedModelsTab.java +++ b/client/src/main/java/ctbrec/ui/RecordedModelsTab.java @@ -16,7 +16,6 @@ import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadFactory; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; -import java.util.function.Consumer; import java.util.function.Function; import java.util.stream.Collectors; @@ -29,6 +28,10 @@ import ctbrec.Recording; import ctbrec.StringUtil; import ctbrec.recorder.Recorder; import ctbrec.sites.Site; +import ctbrec.ui.action.FollowAction; +import ctbrec.ui.action.PauseAction; +import ctbrec.ui.action.ResumeAction; +import ctbrec.ui.action.StopRecordingAction; import ctbrec.ui.controls.AutoFillTextField; import ctbrec.ui.controls.Toast; import javafx.application.Platform; @@ -272,39 +275,11 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { } private void pauseAll(ActionEvent evt) { - List models = recorder.getModelsRecording(); - Consumer action = (m) -> { - try { - recorder.suspendRecording(m); - } catch(Exception e) { - Platform.runLater(() -> - showErrorDialog(e, "Couldn't suspend recording of model", "Suspending recording of " + m.getName() + " failed")); - } - }; - massEdit(models, action); + new PauseAction(getTabPane(), recorder.getModelsRecording(), recorder).execute(); } private void resumeAll(ActionEvent evt) { - List models = recorder.getModelsRecording(); - Consumer action = (m) -> { - try { - recorder.resumeRecording(m); - } catch(Exception e) { - Platform.runLater(() -> - showErrorDialog(e, "Couldn't resume recording of model", "Resuming recording of " + m.getName() + " failed")); - } - }; - massEdit(models, action); - } - - private void massEdit(List models, Consumer action) { - table.setCursor(Cursor.WAIT); - threadPool.submit(() -> { - for (Model model : models) { - action.accept(model); - } - Platform.runLater(() -> table.setCursor(Cursor.DEFAULT)); - }); + new ResumeAction(getTabPane(), recorder.getModelsRecording(), recorder).execute(); } void initializeUpdateService() { @@ -466,16 +441,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { } private void follow(ObservableList selectedModels) { - Consumer action = (m) -> { - try { - m.follow(); - } catch(Throwable e) { - LOG.error("Couldn't follow model {}", m, e); - Platform.runLater(() -> - showErrorDialog(e, "Couldn't follow model", "Following " + m.getName() + " failed: " + e.getMessage())); - } - }; - massEdit(new ArrayList(selectedModels), action); + new FollowAction(getTabPane(), new ArrayList(selectedModels)).execute(); } private void openInPlayer(JavaFxModel selectedModel) { @@ -540,45 +506,20 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener { } private void stopAction(List selectedModels) { - Consumer action = (m) -> { - try { - recorder.stopRecording(m); - observableModels.remove(m); - } catch(Exception e) { - Platform.runLater(() -> - showErrorDialog(e, "Couldn't stop recording", "Stopping recording of " + m.getName() + " failed")); - } - }; List models = selectedModels.stream().map(jfxm -> jfxm.getDelegate()).collect(Collectors.toList()); - massEdit(models, action); + new StopRecordingAction(getTabPane(), models, recorder).execute((m) -> { + observableModels.remove(m); + }); }; private void pauseRecording(List selectedModels) { - Consumer action = (m) -> { - try { - recorder.suspendRecording(m); - m.setSuspended(true); - } catch(Exception e) { - Platform.runLater(() -> - showErrorDialog(e, "Couldn't pause recording of model", "Pausing recording of " + m.getName() + " failed")); - } - }; List models = selectedModels.stream().map(jfxm -> jfxm.getDelegate()).collect(Collectors.toList()); - massEdit(models, action); + new PauseAction(getTabPane(), models, recorder).execute(); }; private void resumeRecording(List selectedModels) { - Consumer action = (m) -> { - try { - recorder.resumeRecording(m); - m.setSuspended(false); - } catch(Exception e) { - Platform.runLater(() -> - showErrorDialog(e, "Couldn't resume recording of model", "Resuming recording of " + m.getName() + " failed")); - } - }; List models = selectedModels.stream().map(jfxm -> jfxm.getDelegate()).collect(Collectors.toList()); - massEdit(models, action); + new ResumeAction(getTabPane(), models, recorder).execute(); } public void saveState() { diff --git a/client/src/main/java/ctbrec/ui/action/FollowAction.java b/client/src/main/java/ctbrec/ui/action/FollowAction.java new file mode 100644 index 00000000..d5bec925 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/action/FollowAction.java @@ -0,0 +1,29 @@ +package ctbrec.ui.action; + +import java.util.List; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ctbrec.Model; +import ctbrec.ui.controls.Dialogs; +import javafx.application.Platform; +import javafx.scene.Node; + +public class FollowAction extends ModelMassEditAction { + + private static final transient Logger LOG = LoggerFactory.getLogger(FollowAction.class); + + public FollowAction(Node source, List models) { + super(source, models); + action = (m) -> { + try { + m.follow(); + } catch(Exception e) { + LOG.error("Couldn't follow model {}", m, e); + Platform.runLater(() -> + Dialogs.showError("Couldn't follow model", "Following " + m.getName() + " failed: " + e.getMessage(), e)); + } + }; + } +} diff --git a/client/src/main/java/ctbrec/ui/action/ModelMassEditAction.java b/client/src/main/java/ctbrec/ui/action/ModelMassEditAction.java new file mode 100644 index 00000000..7da778be --- /dev/null +++ b/client/src/main/java/ctbrec/ui/action/ModelMassEditAction.java @@ -0,0 +1,52 @@ +package ctbrec.ui.action; + +import java.util.List; +import java.util.Objects; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; + +import ctbrec.Model; +import javafx.application.Platform; +import javafx.scene.Cursor; +import javafx.scene.Node; + +public class ModelMassEditAction { + + static BlockingQueue queue = new LinkedBlockingQueue<>(); + static ExecutorService threadPool = new ThreadPoolExecutor(2, 2, 10, TimeUnit.MINUTES, queue); + + protected List models; + protected Consumer action; + protected Node source; + + protected ModelMassEditAction(Node source, List models) { + this.source = source; + this.models = models; + } + + public ModelMassEditAction(Node source, List models, Consumer action) { + this.source = source; + this.models = models; + this.action = action; + } + + public void execute() { + execute((m) -> {}); + } + + public void execute(Consumer callback) { + Consumer cb = Objects.requireNonNull(callback); + source.setCursor(Cursor.WAIT); + threadPool.submit(() -> { + for (Model model : models) { + action.accept(model); + cb.accept(model); + } + Platform.runLater(() -> source.setCursor(Cursor.DEFAULT)); + }); + } +} diff --git a/client/src/main/java/ctbrec/ui/action/PauseAction.java b/client/src/main/java/ctbrec/ui/action/PauseAction.java new file mode 100644 index 00000000..c1aea4fb --- /dev/null +++ b/client/src/main/java/ctbrec/ui/action/PauseAction.java @@ -0,0 +1,24 @@ +package ctbrec.ui.action; + +import java.util.List; + +import ctbrec.Model; +import ctbrec.recorder.Recorder; +import ctbrec.ui.controls.Dialogs; +import javafx.application.Platform; +import javafx.scene.Node; + +public class PauseAction extends ModelMassEditAction { + + public PauseAction(Node source, List models, Recorder recorder) { + super(source, models); + action = (m) -> { + try { + recorder.suspendRecording(m); + } catch(Exception e) { + Platform.runLater(() -> + Dialogs.showError("Couldn't suspend recording of model", "Suspending recording of " + m.getName() + " failed", e)); + } + }; + } +} diff --git a/client/src/main/java/ctbrec/ui/action/ResumeAction.java b/client/src/main/java/ctbrec/ui/action/ResumeAction.java new file mode 100644 index 00000000..2215a67f --- /dev/null +++ b/client/src/main/java/ctbrec/ui/action/ResumeAction.java @@ -0,0 +1,24 @@ +package ctbrec.ui.action; + +import java.util.List; + +import ctbrec.Model; +import ctbrec.recorder.Recorder; +import ctbrec.ui.controls.Dialogs; +import javafx.application.Platform; +import javafx.scene.Node; + +public class ResumeAction extends ModelMassEditAction { + + public ResumeAction(Node source, List models, Recorder recorder) { + super(source, models); + action = (m) -> { + try { + recorder.resumeRecording(m); + } catch(Exception e) { + Platform.runLater(() -> + Dialogs.showError("Couldn't resume recording of model", "Resuming recording of " + m.getName() + " failed", e)); + } + }; + } +} diff --git a/client/src/main/java/ctbrec/ui/action/StopRecordingAction.java b/client/src/main/java/ctbrec/ui/action/StopRecordingAction.java new file mode 100644 index 00000000..a4dcab38 --- /dev/null +++ b/client/src/main/java/ctbrec/ui/action/StopRecordingAction.java @@ -0,0 +1,24 @@ +package ctbrec.ui.action; + +import java.util.List; + +import ctbrec.Model; +import ctbrec.recorder.Recorder; +import ctbrec.ui.controls.Dialogs; +import javafx.application.Platform; +import javafx.scene.Node; + +public class StopRecordingAction extends ModelMassEditAction { + + public StopRecordingAction(Node source, List models, Recorder recorder) { + super(source, models); + action = (m) -> { + try { + recorder.stopRecording(m); + } catch(Exception e) { + Platform.runLater(() -> + Dialogs.showError("Couldn't stop recording", "Stopping recording of " + m.getName() + " failed", e)); + } + }; + } +} diff --git a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java index fc31091a..d9630ff9 100644 --- a/client/src/main/java/ctbrec/ui/settings/SettingsTab.java +++ b/client/src/main/java/ctbrec/ui/settings/SettingsTab.java @@ -307,6 +307,7 @@ public class SettingsTab extends Tab implements TabSelectionListener { GridPane.setMargin(splitAfter, new Insets(0, 0, 0, CHECKBOX_MARGIN)); layout.add(new Label("Post-Processing"), 0, row); + // TODO allow empty strings to remove post-processing scripts postProcessing = new ProgramSelectionBox(Config.getInstance().getSettings().postProcessing); postProcessing.fileProperty().addListener((obs, o, n) -> { String path = n.getAbsolutePath(); diff --git a/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsTableTab.java b/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsTableTab.java index 9a01c796..8f19639b 100644 --- a/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsTableTab.java +++ b/client/src/main/java/ctbrec/ui/sites/myfreecams/MyFreeCamsTableTab.java @@ -16,11 +16,17 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import ctbrec.Config; +import ctbrec.Model; import ctbrec.StringUtil; import ctbrec.sites.mfc.MyFreeCams; +import ctbrec.sites.mfc.MyFreeCamsModel; import ctbrec.sites.mfc.SessionState; +import ctbrec.ui.DesktopIntegration; +import ctbrec.ui.Player; import ctbrec.ui.TabSelectionListener; import ctbrec.ui.controls.SearchBox; +import ctbrec.ui.controls.Toast; +import javafx.application.Platform; import javafx.beans.property.SimpleDoubleProperty; import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; @@ -32,15 +38,21 @@ import javafx.event.ActionEvent; import javafx.geometry.Insets; import javafx.geometry.Point2D; import javafx.geometry.Pos; +import javafx.scene.Cursor; import javafx.scene.control.Button; import javafx.scene.control.CheckMenuItem; import javafx.scene.control.ContextMenu; import javafx.scene.control.Label; +import javafx.scene.control.MenuItem; import javafx.scene.control.ScrollPane; import javafx.scene.control.Tab; import javafx.scene.control.TableColumn; import javafx.scene.control.TableColumn.SortType; import javafx.scene.control.TableView; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import javafx.scene.input.ContextMenuEvent; +import javafx.scene.input.MouseEvent; import javafx.scene.layout.BorderPane; import javafx.scene.layout.HBox; import javafx.scene.layout.Priority; @@ -58,6 +70,7 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { private SearchBox filterInput; private Label count = new Label("models"); private List> columns = new ArrayList<>(); + private ContextMenu popup; public MyFreeCamsTableTab(MyFreeCams mfc) { this.mfc = mfc; @@ -140,56 +153,84 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { table.setItems(observableModels); table.getSortOrder().addListener(createSortOrderChangedListener()); + 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(); + } + }); - TableColumn name = createTableColumn("Name", 200, 0); + int idx = 0; + TableColumn name = createTableColumn("Name", 200, idx++); name.setCellValueFactory(cdf -> { return new SimpleStringProperty(Optional.ofNullable(cdf.getValue().getNm()).orElse("n/a")); }); addTableColumnIfEnabled(name); - TableColumn state = createTableColumn("State", 130, 1); + TableColumn state = createTableColumn("State", 130, idx++); state.setCellValueFactory(cdf -> { String st = Optional.ofNullable(cdf.getValue().getVs()).map(vs -> ctbrec.sites.mfc.State.of(vs).toString()).orElse("n/a"); return new SimpleStringProperty(st); }); addTableColumnIfEnabled(state); - TableColumn camscore = createTableColumn("Score", 75, 2); + TableColumn camscore = createTableColumn("Score", 75, idx++); camscore.setCellValueFactory(cdf -> { Double camScore = Optional.ofNullable(cdf.getValue().getM()).map(m -> m.getCamscore()).orElse(0d); return new SimpleDoubleProperty(camScore); }); addTableColumnIfEnabled(camscore); - TableColumn ethnic = createTableColumn("Ethnicity", 130, 3); + // this is always 0, use https://api.myfreecams.com/missmfc and https://api.myfreecams.com/missmfc/online + // TableColumn missMfc = createTableColumn("Miss MFC", 75, idx++); + // missMfc.setCellValueFactory(cdf -> { + // Integer mmfc = Optional.ofNullable(cdf.getValue().getM()).map(m -> m.getMissmfc()).orElse(-1); + // return new SimpleIntegerProperty(mmfc); + // }); + // addTableColumnIfEnabled(missMfc); + + TableColumn newModel = createTableColumn("New", 60, idx++); + newModel.setCellValueFactory(cdf -> { + Integer nu = Optional.ofNullable(cdf.getValue().getM()).map(m -> m.getNewModel()).orElse(0); + return new SimpleStringProperty(nu == 1 ? "new" : ""); + }); + addTableColumnIfEnabled(newModel); + + TableColumn ethnic = createTableColumn("Ethnicity", 130, idx++); ethnic.setCellValueFactory(cdf -> { String eth = Optional.ofNullable(cdf.getValue().getU()).map(u -> u.getEthnic()).orElse("n/a"); return new SimpleStringProperty(eth); }); addTableColumnIfEnabled(ethnic); - TableColumn country = createTableColumn("Country", 160, 4); + TableColumn country = createTableColumn("Country", 160, idx++); country.setCellValueFactory(cdf -> { String c = Optional.ofNullable(cdf.getValue().getU()).map(u -> u.getCountry()).orElse("n/a"); return new SimpleStringProperty(c); }); addTableColumnIfEnabled(country); - TableColumn continent = createTableColumn("Continent", 100, 5); + TableColumn continent = createTableColumn("Continent", 100, idx++); continent.setCellValueFactory(cdf -> { String c = Optional.ofNullable(cdf.getValue().getM()).map(m -> m.getContinent()).orElse("n/a"); return new SimpleStringProperty(c); }); addTableColumnIfEnabled(continent); - TableColumn occupation = createTableColumn("Occupation", 160, 6); + TableColumn occupation = createTableColumn("Occupation", 160, idx++); occupation.setCellValueFactory(cdf -> { String occ = Optional.ofNullable(cdf.getValue().getU()).map(u -> u.getOccupation()).orElse("n/a"); return new SimpleStringProperty(occ); }); addTableColumnIfEnabled(occupation); - TableColumn tags = createTableColumn("Tags", 300, 7); + TableColumn tags = createTableColumn("Tags", 300, idx++); tags.setCellValueFactory(cdf -> { Set tagSet = Optional.ofNullable(cdf.getValue().getM()).map(m -> m.getTags()).orElse(Collections.emptySet()); if(tagSet.isEmpty()) { @@ -204,14 +245,14 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { }); addTableColumnIfEnabled(tags); - TableColumn blurp = createTableColumn("Blurp", 300, 8); + TableColumn blurp = createTableColumn("Blurp", 300, idx++); blurp.setCellValueFactory(cdf -> { String blrp = Optional.ofNullable(cdf.getValue().getU()).map(u -> u.getBlurb()).orElse("n/a"); return new SimpleStringProperty(blrp); }); addTableColumnIfEnabled(blurp); - TableColumn topic = createTableColumn("Topic", 600, 9); + TableColumn topic = createTableColumn("Topic", 600, idx++); topic.setCellValueFactory(cdf -> { String tpc = Optional.ofNullable(cdf.getValue().getM()).map(m -> m.getTopic()).orElse("n/a"); try { @@ -232,6 +273,69 @@ public class MyFreeCamsTableTab extends Tab implements TabSelectionListener { setContent(layout); } + private ContextMenu createContextMenu() { + ObservableList selectedStates = table.getSelectionModel().getSelectedItems(); + if (selectedStates.isEmpty()) { + return null; + } + + List selectedModels = new ArrayList<>(); + for (SessionState sessionState : selectedStates) { + if(sessionState.getNm() != null) { + MyFreeCamsModel model = mfc.createModel(sessionState.getNm()); + mfc.getClient().update(model); + selectedModels.add(model); + } + } + + MenuItem copyUrl = new MenuItem("Copy URL"); + copyUrl.setOnAction((e) -> { + Model selected = selectedModels.get(0); + final Clipboard clipboard = Clipboard.getSystemClipboard(); + final ClipboardContent content = new ClipboardContent(); + content.putString(selected.getUrl()); + clipboard.setContent(content); + }); + + // MenuItem resumeRecording = new MenuItem("Record"); + // resumeRecording.setOnAction((e) -> resumeRecording(selectedModels)); + MenuItem openInBrowser = new MenuItem("Open in Browser"); + openInBrowser.setOnAction((e) -> DesktopIntegration.open(selectedModels.get(0).getUrl())); + MenuItem openInPlayer = new MenuItem("Open in Player"); + openInPlayer.setOnAction((e) -> openInPlayer(selectedModels.get(0))); + MenuItem follow = new MenuItem("Follow"); + follow.setOnAction((e) -> follow(selectedModels)); + + ContextMenu menu = new ContextMenu(); + menu.getItems().addAll(copyUrl, openInPlayer, openInBrowser, follow); + + if (selectedModels.size() > 1) { + copyUrl.setDisable(true); + openInPlayer.setDisable(true); + openInBrowser.setDisable(true); + } + + return menu; + } + + private Object follow(List selectedModels) { + // TODO Auto-generated method stub + return null; + } + + private void openInPlayer(Model selectedModel) { + table.setCursor(Cursor.WAIT); + new Thread(() -> { + boolean started = Player.play(selectedModel); + Platform.runLater(() -> { + if (started && Config.getInstance().getSettings().showPlayerStarting) { + Toast.makeText(getTabPane().getScene(), "Starting Player", 2000, 500, 500); + } + table.setCursor(Cursor.DEFAULT); + }); + }).start(); + } + private void addTableColumnIfEnabled(TableColumn tc) { if(isColumnEnabled(tc)) { table.getColumns().add(tc);