Add tab for recently watched models
This commit is contained in:
parent
66d234e668
commit
368120e8e6
|
@ -1,5 +1,6 @@
|
||||||
3.12.3
|
3.12.3
|
||||||
========================
|
========================
|
||||||
|
* Added "Recently watched" tab. Can be disabled in Settings -> General
|
||||||
* Recording size now takes all associated files into account
|
* Recording size now takes all associated files into account
|
||||||
* Removed restriction of download thread pool size (was 100 before)
|
* Removed restriction of download thread pool size (was 100 before)
|
||||||
|
|
||||||
|
|
|
@ -63,6 +63,7 @@ import ctbrec.ui.news.NewsTab;
|
||||||
import ctbrec.ui.settings.SettingsTab;
|
import ctbrec.ui.settings.SettingsTab;
|
||||||
import ctbrec.ui.tabs.DonateTabFx;
|
import ctbrec.ui.tabs.DonateTabFx;
|
||||||
import ctbrec.ui.tabs.HelpTab;
|
import ctbrec.ui.tabs.HelpTab;
|
||||||
|
import ctbrec.ui.tabs.RecentlyWatchedTab;
|
||||||
import ctbrec.ui.tabs.RecordedTab;
|
import ctbrec.ui.tabs.RecordedTab;
|
||||||
import ctbrec.ui.tabs.RecordingsTab;
|
import ctbrec.ui.tabs.RecordingsTab;
|
||||||
import ctbrec.ui.tabs.SiteTab;
|
import ctbrec.ui.tabs.SiteTab;
|
||||||
|
@ -216,6 +217,9 @@ public class CamrecApplication extends Application {
|
||||||
tabPane.getTabs().add(modelsTab);
|
tabPane.getTabs().add(modelsTab);
|
||||||
recordingsTab = new RecordingsTab("Recordings", recorder, config);
|
recordingsTab = new RecordingsTab("Recordings", recorder, config);
|
||||||
tabPane.getTabs().add(recordingsTab);
|
tabPane.getTabs().add(recordingsTab);
|
||||||
|
if (config.getSettings().recentlyWatched) {
|
||||||
|
tabPane.getTabs().add(new RecentlyWatchedTab(recorder, sites));
|
||||||
|
}
|
||||||
tabPane.getTabs().add(new SettingsTab(sites, recorder));
|
tabPane.getTabs().add(new SettingsTab(sites, recorder));
|
||||||
tabPane.getTabs().add(new NewsTab());
|
tabPane.getTabs().add(new NewsTab());
|
||||||
tabPane.getTabs().add(new DonateTabFx());
|
tabPane.getTabs().add(new DonateTabFx());
|
||||||
|
@ -299,8 +303,11 @@ public class CamrecApplication extends Application {
|
||||||
|
|
||||||
final boolean immediately = shutdownNow;
|
final boolean immediately = shutdownNow;
|
||||||
new Thread(() -> {
|
new Thread(() -> {
|
||||||
modelsTab.saveState();
|
for (Tab tab : tabPane.getTabs()) {
|
||||||
recordingsTab.saveState();
|
if (tab instanceof ShutdownListener) {
|
||||||
|
((ShutdownListener) tab).onShutdown();
|
||||||
|
}
|
||||||
|
}
|
||||||
onlineMonitor.shutdown();
|
onlineMonitor.shutdown();
|
||||||
recorder.shutdown(immediately);
|
recorder.shutdown(immediately);
|
||||||
for (Site site : sites) {
|
for (Site site : sites) {
|
||||||
|
|
|
@ -25,10 +25,12 @@ import ctbrec.Config;
|
||||||
import ctbrec.Model;
|
import ctbrec.Model;
|
||||||
import ctbrec.OS;
|
import ctbrec.OS;
|
||||||
import ctbrec.Recording;
|
import ctbrec.Recording;
|
||||||
|
import ctbrec.event.EventBusHolder;
|
||||||
import ctbrec.io.StreamRedirector;
|
import ctbrec.io.StreamRedirector;
|
||||||
import ctbrec.io.UrlUtil;
|
import ctbrec.io.UrlUtil;
|
||||||
import ctbrec.recorder.download.StreamSource;
|
import ctbrec.recorder.download.StreamSource;
|
||||||
import ctbrec.ui.controls.Dialogs;
|
import ctbrec.ui.controls.Dialogs;
|
||||||
|
import ctbrec.ui.event.PlayerStartedEvent;
|
||||||
import javafx.scene.Scene;
|
import javafx.scene.Scene;
|
||||||
|
|
||||||
public class Player {
|
public class Player {
|
||||||
|
@ -85,6 +87,7 @@ public class Player {
|
||||||
}
|
}
|
||||||
String playlistUrl = getPlaylistUrl(model);
|
String playlistUrl = getPlaylistUrl(model);
|
||||||
LOG.debug("Playing {}", playlistUrl);
|
LOG.debug("Playing {}", playlistUrl);
|
||||||
|
EventBusHolder.BUS.post(new PlayerStartedEvent(model));
|
||||||
return Player.play(playlistUrl, async);
|
return Player.play(playlistUrl, async);
|
||||||
} else {
|
} else {
|
||||||
Dialogs.showError(scene, "Room not public", "Room is currently not public", null);
|
Dialogs.showError(scene, "Room not public", "Room is currently not public", null);
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package ctbrec.ui;
|
||||||
|
|
||||||
|
public interface ShutdownListener {
|
||||||
|
void onShutdown();
|
||||||
|
}
|
|
@ -0,0 +1,60 @@
|
||||||
|
package ctbrec.ui.event;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.ui.JavaFxModel;
|
||||||
|
|
||||||
|
public class PlayerStartedEvent {
|
||||||
|
|
||||||
|
private Model model;
|
||||||
|
private Instant timestamp;
|
||||||
|
|
||||||
|
public PlayerStartedEvent(Model model) {
|
||||||
|
this(model, Instant.now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public PlayerStartedEvent(Model model, Instant timestamp) {
|
||||||
|
this.model = unwrap(model);
|
||||||
|
this.timestamp = timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Model getModel() {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Instant getTimestamp() {
|
||||||
|
return timestamp;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj)
|
||||||
|
return true;
|
||||||
|
if (obj == null)
|
||||||
|
return false;
|
||||||
|
if (getClass() != obj.getClass())
|
||||||
|
return false;
|
||||||
|
PlayerStartedEvent other = (PlayerStartedEvent) obj;
|
||||||
|
return Objects.equals(timestamp, other.timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "PlayerStartedEvent [model=" + model + ", timestamp=" + timestamp + "]";
|
||||||
|
}
|
||||||
|
|
||||||
|
private Model unwrap(Model model) {
|
||||||
|
if (model instanceof JavaFxModel) {
|
||||||
|
return ((JavaFxModel) model).getDelegate();
|
||||||
|
} else {
|
||||||
|
return model;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -129,6 +129,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
private SimpleBooleanProperty transportLayerSecurity;
|
private SimpleBooleanProperty transportLayerSecurity;
|
||||||
private SimpleBooleanProperty fastScrollSpeed;
|
private SimpleBooleanProperty fastScrollSpeed;
|
||||||
private SimpleBooleanProperty useHlsdl;
|
private SimpleBooleanProperty useHlsdl;
|
||||||
|
private SimpleBooleanProperty recentlyWatched;
|
||||||
private SimpleFileProperty hlsdlExecutable;
|
private SimpleFileProperty hlsdlExecutable;
|
||||||
private ExclusiveSelectionProperty recordLocal;
|
private ExclusiveSelectionProperty recordLocal;
|
||||||
private SimpleIntegerProperty postProcessingThreads;
|
private SimpleIntegerProperty postProcessingThreads;
|
||||||
|
@ -191,6 +192,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
confirmationDialogs = new SimpleBooleanProperty(null, "confirmationForDangerousActions", settings.confirmationForDangerousActions);
|
confirmationDialogs = new SimpleBooleanProperty(null, "confirmationForDangerousActions", settings.confirmationForDangerousActions);
|
||||||
useHlsdl = new SimpleBooleanProperty(null, "useHlsdl", settings.useHlsdl);
|
useHlsdl = new SimpleBooleanProperty(null, "useHlsdl", settings.useHlsdl);
|
||||||
hlsdlExecutable = new SimpleFileProperty(null, "hlsdlExecutable", settings.hlsdlExecutable);
|
hlsdlExecutable = new SimpleFileProperty(null, "hlsdlExecutable", settings.hlsdlExecutable);
|
||||||
|
recentlyWatched = new SimpleBooleanProperty(null, "recentlyWatched", settings.recentlyWatched);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createGui() {
|
private void createGui() {
|
||||||
|
@ -214,6 +216,7 @@ public class SettingsTab extends Tab implements TabSelectionListener {
|
||||||
Setting.of("Display stream resolution in overview", determineResolution),
|
Setting.of("Display stream resolution in overview", determineResolution),
|
||||||
Setting.of("Manually select stream quality", chooseStreamQuality, "Opens a dialog to select the video resolution before recording"),
|
Setting.of("Manually select stream quality", chooseStreamQuality, "Opens a dialog to select the video resolution before recording"),
|
||||||
Setting.of("Enable live previews (experimental)", livePreviews),
|
Setting.of("Enable live previews (experimental)", livePreviews),
|
||||||
|
Setting.of("Enable recently watched tab", recentlyWatched).needsRestart(),
|
||||||
Setting.of("Add models from clipboard", monitorClipboard, "Monitor clipboard for model URLs and automatically add them to the recorder").needsRestart(),
|
Setting.of("Add models from clipboard", monitorClipboard, "Monitor clipboard for model URLs and automatically add them to the recorder").needsRestart(),
|
||||||
Setting.of("Fast scroll speed", fastScrollSpeed, "Makes the thumbnail overviews scroll faster with the mouse wheel").needsRestart(),
|
Setting.of("Fast scroll speed", fastScrollSpeed, "Makes the thumbnail overviews scroll faster with the mouse wheel").needsRestart(),
|
||||||
Setting.of("Show confirmation dialogs", confirmationDialogs, "Show confirmation dialogs for irreversible actions"),
|
Setting.of("Show confirmation dialogs", confirmationDialogs, "Show confirmation dialogs for irreversible actions"),
|
||||||
|
|
|
@ -0,0 +1,346 @@
|
||||||
|
package ctbrec.ui.tabs;
|
||||||
|
|
||||||
|
import static java.nio.charset.StandardCharsets.*;
|
||||||
|
import static java.nio.file.StandardOpenOption.*;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.locks.ReentrantLock;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import com.google.common.eventbus.Subscribe;
|
||||||
|
import com.squareup.moshi.JsonAdapter;
|
||||||
|
import com.squareup.moshi.Moshi;
|
||||||
|
import com.squareup.moshi.Types;
|
||||||
|
|
||||||
|
import ctbrec.Config;
|
||||||
|
import ctbrec.Model;
|
||||||
|
import ctbrec.StringUtil;
|
||||||
|
import ctbrec.event.EventBusHolder;
|
||||||
|
import ctbrec.io.InstantJsonAdapter;
|
||||||
|
import ctbrec.io.ModelJsonAdapter;
|
||||||
|
import ctbrec.recorder.Recorder;
|
||||||
|
import ctbrec.sites.Site;
|
||||||
|
import ctbrec.ui.DesktopIntegration;
|
||||||
|
import ctbrec.ui.ShutdownListener;
|
||||||
|
import ctbrec.ui.action.FollowAction;
|
||||||
|
import ctbrec.ui.action.PlayAction;
|
||||||
|
import ctbrec.ui.action.StartRecordingAction;
|
||||||
|
import ctbrec.ui.controls.CustomMouseBehaviorContextMenu;
|
||||||
|
import ctbrec.ui.controls.DateTimeCellFactory;
|
||||||
|
import ctbrec.ui.controls.SearchBox;
|
||||||
|
import ctbrec.ui.event.PlayerStartedEvent;
|
||||||
|
import javafx.beans.property.SimpleObjectProperty;
|
||||||
|
import javafx.beans.property.SimpleStringProperty;
|
||||||
|
import javafx.collections.FXCollections;
|
||||||
|
import javafx.collections.ObservableList;
|
||||||
|
import javafx.geometry.Insets;
|
||||||
|
import javafx.scene.control.ContextMenu;
|
||||||
|
import javafx.scene.control.MenuItem;
|
||||||
|
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.TableView;
|
||||||
|
import javafx.scene.input.Clipboard;
|
||||||
|
import javafx.scene.input.ClipboardContent;
|
||||||
|
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.HBox;
|
||||||
|
import javafx.scene.layout.Priority;
|
||||||
|
import javafx.util.Callback;
|
||||||
|
|
||||||
|
public class RecentlyWatchedTab extends Tab implements ShutdownListener {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(RecentlyWatchedTab.class);
|
||||||
|
|
||||||
|
private ObservableList<PlayerStartedEvent> filteredModels = FXCollections.observableArrayList();
|
||||||
|
private ObservableList<PlayerStartedEvent> observableModels = FXCollections.observableArrayList();
|
||||||
|
private TableView<PlayerStartedEvent> table = new TableView<>();
|
||||||
|
private ContextMenu popup;
|
||||||
|
private ReentrantLock lock = new ReentrantLock();
|
||||||
|
private Recorder recorder;
|
||||||
|
private List<Site> sites;
|
||||||
|
|
||||||
|
public RecentlyWatchedTab(Recorder recorder, List<Site> sites) {
|
||||||
|
this.recorder = recorder;
|
||||||
|
this.sites = sites;
|
||||||
|
setText("Recently Watched");
|
||||||
|
createGui();
|
||||||
|
loadHistory();
|
||||||
|
subscribeToPlayerEvents();
|
||||||
|
setOnClosed(evt -> onShutdown());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createGui() {
|
||||||
|
BorderPane layout = new BorderPane();
|
||||||
|
layout.setPadding(new Insets(5, 10, 10, 10));
|
||||||
|
|
||||||
|
SearchBox filterInput = new SearchBox(false);
|
||||||
|
filterInput.setPromptText("Filter");
|
||||||
|
filterInput.textProperty().addListener( (observableValue, oldValue, newValue) -> {
|
||||||
|
String filter = filterInput.getText();
|
||||||
|
lock.lock();
|
||||||
|
try {
|
||||||
|
filter(filter);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
filterInput.getStyleClass().remove("search-box-icon");
|
||||||
|
HBox.setHgrow(filterInput, Priority.ALWAYS);
|
||||||
|
HBox topBar = new HBox(5);
|
||||||
|
topBar.getChildren().addAll(filterInput);
|
||||||
|
layout.setTop(topBar);
|
||||||
|
BorderPane.setMargin(topBar, new Insets(0, 0, 5, 0));
|
||||||
|
|
||||||
|
table.setItems(observableModels);
|
||||||
|
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
|
||||||
|
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<PlayerStartedEvent> selectedModels = table.getSelectionModel().getSelectedItems();
|
||||||
|
if (event.getCode() == KeyCode.DELETE) {
|
||||||
|
delete(selectedModels);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
int idx = 0;
|
||||||
|
TableColumn<PlayerStartedEvent, String> name = createTableColumn("Model", 200, idx++);
|
||||||
|
name.setId("name");
|
||||||
|
name.setCellValueFactory(cdf -> new SimpleStringProperty(cdf.getValue().getModel().getDisplayName()));
|
||||||
|
name.setCellFactory(new ClickableCellFactory<>());
|
||||||
|
table.getColumns().add(name);
|
||||||
|
|
||||||
|
TableColumn<PlayerStartedEvent, String> url = createTableColumn("URL", 400, idx);
|
||||||
|
url.setCellValueFactory(cdf -> new SimpleStringProperty(cdf.getValue().getModel().getUrl()));
|
||||||
|
url.setCellFactory(new ClickableCellFactory<>());
|
||||||
|
url.setEditable(false);
|
||||||
|
url.setId("url");
|
||||||
|
table.getColumns().add(url);
|
||||||
|
|
||||||
|
TableColumn<PlayerStartedEvent, Instant> timestamp = createTableColumn("Timestamp", 150, idx);
|
||||||
|
timestamp.setId("timestamp");
|
||||||
|
timestamp.setCellValueFactory(cdf -> new SimpleObjectProperty<Instant>(cdf.getValue().getTimestamp()));
|
||||||
|
timestamp.setCellFactory(new DateTimeCellFactory<>());
|
||||||
|
timestamp.setEditable(false);
|
||||||
|
timestamp.setSortType(SortType.DESCENDING);
|
||||||
|
table.getColumns().add(timestamp);
|
||||||
|
table.getSortOrder().add(timestamp);
|
||||||
|
|
||||||
|
ScrollPane scrollPane = new ScrollPane();
|
||||||
|
scrollPane.setFitToHeight(true);
|
||||||
|
scrollPane.setFitToWidth(true);
|
||||||
|
scrollPane.setContent(table);
|
||||||
|
scrollPane.setStyle("-fx-background-color: -fx-background");
|
||||||
|
layout.setCenter(scrollPane);
|
||||||
|
setContent(layout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T> TableColumn<PlayerStartedEvent, T> createTableColumn(String text, int width, int idx) {
|
||||||
|
TableColumn<PlayerStartedEvent, T> tc = new TableColumn<>(text);
|
||||||
|
tc.setPrefWidth(width);
|
||||||
|
tc.setUserData(idx);
|
||||||
|
return tc;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 (int i = 0; i < table.getItems().size(); i++) {
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
for (TableColumn<PlayerStartedEvent, ?> tc : table.getColumns()) {
|
||||||
|
Object cellData = tc.getCellData(i);
|
||||||
|
if(cellData != null) {
|
||||||
|
String content = cellData.toString();
|
||||||
|
sb.append(content).append(' ');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
String searchText = sb.toString();
|
||||||
|
|
||||||
|
boolean tokensMissing = false;
|
||||||
|
for (String token : tokens) {
|
||||||
|
if(!searchText.toLowerCase().contains(token.toLowerCase())) {
|
||||||
|
tokensMissing = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(tokensMissing) {
|
||||||
|
PlayerStartedEvent sessionState = table.getItems().get(i);
|
||||||
|
filteredModels.add(sessionState);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
observableModels.removeAll(filteredModels);
|
||||||
|
} finally {
|
||||||
|
lock.unlock();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ContextMenu createContextMenu() {
|
||||||
|
ObservableList<PlayerStartedEvent> selectedRows = table.getSelectionModel().getSelectedItems();
|
||||||
|
if (selectedRows.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<Model> selectedModels = selectedRows.stream().map(PlayerStartedEvent::getModel).collect(Collectors.toList());
|
||||||
|
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 startRecording = new MenuItem("Start Recording");
|
||||||
|
startRecording.setOnAction(e -> startRecording(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 -> new FollowAction(getTabPane(), selectedModels).execute());
|
||||||
|
MenuItem delete = new MenuItem("Delete");
|
||||||
|
delete.setOnAction(e -> delete(selectedRows));
|
||||||
|
|
||||||
|
MenuItem clearHistory = new MenuItem("Clear history");
|
||||||
|
clearHistory.setOnAction(e -> clearHistory());
|
||||||
|
|
||||||
|
ContextMenu menu = new CustomMouseBehaviorContextMenu();
|
||||||
|
menu.getItems().addAll(startRecording, copyUrl, openInPlayer, openInBrowser, follow, delete, clearHistory);
|
||||||
|
|
||||||
|
if (selectedModels.size() > 1) {
|
||||||
|
copyUrl.setDisable(true);
|
||||||
|
openInPlayer.setDisable(true);
|
||||||
|
openInBrowser.setDisable(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
return menu;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void clearHistory() {
|
||||||
|
observableModels.clear();
|
||||||
|
filteredModels.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void delete(List<PlayerStartedEvent> selectedRows) {
|
||||||
|
observableModels.removeAll(selectedRows);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void startRecording(List<Model> selectedModels) {
|
||||||
|
new StartRecordingAction(getTabPane(), selectedModels, recorder).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void openInPlayer(Model selectedModel) {
|
||||||
|
new PlayAction(getTabPane(), selectedModel).execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void subscribeToPlayerEvents() {
|
||||||
|
EventBusHolder.BUS.register(new Object() {
|
||||||
|
@Subscribe
|
||||||
|
public void handleEvent(PlayerStartedEvent evt) {
|
||||||
|
table.getItems().add(evt);
|
||||||
|
table.sort();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
Model selectedModel = table.getSelectionModel().getSelectedItem().getModel();
|
||||||
|
if(selectedModel != null) {
|
||||||
|
new PlayAction(table, selectedModel).execute();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return cell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveHistory() throws IOException {
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.add(Model.class, new ModelJsonAdapter(sites))
|
||||||
|
.add(Instant.class, new InstantJsonAdapter())
|
||||||
|
.build();
|
||||||
|
Type type = Types.newParameterizedType(List.class, PlayerStartedEvent.class);
|
||||||
|
JsonAdapter<List<PlayerStartedEvent>> adapter = moshi.adapter(type);
|
||||||
|
String json = adapter.indent(" ").toJson(observableModels);
|
||||||
|
File recentlyWatched = new File(Config.getInstance().getConfigDir(), "recently_watched.json");
|
||||||
|
LOG.debug("Saving recently watched models to {}", recentlyWatched.getAbsolutePath());
|
||||||
|
Files.createDirectories(recentlyWatched.getParentFile().toPath());
|
||||||
|
Files.write(recentlyWatched.toPath(), json.getBytes(UTF_8), CREATE, WRITE, TRUNCATE_EXISTING);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadHistory() {
|
||||||
|
File recentlyWatched = new File(Config.getInstance().getConfigDir(), "recently_watched.json");
|
||||||
|
if(!recentlyWatched.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LOG.debug("Loading recently watched models from {}", recentlyWatched.getAbsolutePath());
|
||||||
|
Moshi moshi = new Moshi.Builder()
|
||||||
|
.add(Model.class, new ModelJsonAdapter(sites))
|
||||||
|
.add(Instant.class, new InstantJsonAdapter())
|
||||||
|
.build();
|
||||||
|
Type type = Types.newParameterizedType(List.class, PlayerStartedEvent.class);
|
||||||
|
JsonAdapter<List<PlayerStartedEvent>> adapter = moshi.adapter(type);
|
||||||
|
try {
|
||||||
|
List<PlayerStartedEvent> fromJson = adapter.fromJson(Files.readString(recentlyWatched.toPath(), UTF_8));
|
||||||
|
observableModels.addAll(fromJson);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Couldn't load recently watched models", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onShutdown() {
|
||||||
|
try {
|
||||||
|
saveHistory();
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.error("Couldn't safe recently watched models", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -4,12 +4,13 @@ import java.util.List;
|
||||||
|
|
||||||
import ctbrec.recorder.Recorder;
|
import ctbrec.recorder.Recorder;
|
||||||
import ctbrec.sites.Site;
|
import ctbrec.sites.Site;
|
||||||
|
import ctbrec.ui.ShutdownListener;
|
||||||
import javafx.beans.value.ChangeListener;
|
import javafx.beans.value.ChangeListener;
|
||||||
import javafx.geometry.Side;
|
import javafx.geometry.Side;
|
||||||
import javafx.scene.control.Tab;
|
import javafx.scene.control.Tab;
|
||||||
import javafx.scene.control.TabPane;
|
import javafx.scene.control.TabPane;
|
||||||
|
|
||||||
public class RecordedTab extends Tab implements TabSelectionListener {
|
public class RecordedTab extends Tab implements TabSelectionListener, ShutdownListener {
|
||||||
|
|
||||||
private TabPane tabPane;
|
private TabPane tabPane;
|
||||||
private RecordedModelsTab recordedModelsTab;
|
private RecordedModelsTab recordedModelsTab;
|
||||||
|
@ -54,7 +55,8 @@ public class RecordedTab extends Tab implements TabSelectionListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveState() {
|
@Override
|
||||||
|
public void onShutdown() {
|
||||||
recordedModelsTab.saveState();
|
recordedModelsTab.saveState();
|
||||||
recordLaterTab.saveState();
|
recordLaterTab.saveState();
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,7 @@ import ctbrec.ui.DesktopIntegration;
|
||||||
import ctbrec.ui.FileDownload;
|
import ctbrec.ui.FileDownload;
|
||||||
import ctbrec.ui.JavaFxRecording;
|
import ctbrec.ui.JavaFxRecording;
|
||||||
import ctbrec.ui.Player;
|
import ctbrec.ui.Player;
|
||||||
|
import ctbrec.ui.ShutdownListener;
|
||||||
import ctbrec.ui.action.FollowAction;
|
import ctbrec.ui.action.FollowAction;
|
||||||
import ctbrec.ui.action.PauseAction;
|
import ctbrec.ui.action.PauseAction;
|
||||||
import ctbrec.ui.action.PlayAction;
|
import ctbrec.ui.action.PlayAction;
|
||||||
|
@ -91,7 +92,7 @@ import javafx.scene.text.Font;
|
||||||
import javafx.stage.FileChooser;
|
import javafx.stage.FileChooser;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
|
||||||
public class RecordingsTab extends Tab implements TabSelectionListener {
|
public class RecordingsTab extends Tab implements TabSelectionListener, ShutdownListener {
|
||||||
private static final String ERROR_WHILE_DOWNLOADING_RECORDING = "Error while downloading recording";
|
private static final String ERROR_WHILE_DOWNLOADING_RECORDING = "Error while downloading recording";
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(RecordingsTab.class);
|
private static final Logger LOG = LoggerFactory.getLogger(RecordingsTab.class);
|
||||||
|
@ -822,7 +823,8 @@ public class RecordingsTab extends Tab implements TabSelectionListener {
|
||||||
deleteThread.start();
|
deleteThread.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void saveState() {
|
@Override
|
||||||
|
public void onShutdown() {
|
||||||
if (!table.getSortOrder().isEmpty()) {
|
if (!table.getSortOrder().isEmpty()) {
|
||||||
TableColumn<JavaFxRecording, ?> col = table.getSortOrder().get(0);
|
TableColumn<JavaFxRecording, ?> col = table.getSortOrder().get(0);
|
||||||
Config.getInstance().getSettings().recordingsSortColumn = col.getText();
|
Config.getInstance().getSettings().recordingsSortColumn = col.getText();
|
||||||
|
|
|
@ -120,6 +120,7 @@ public class Settings {
|
||||||
public String proxyPort;
|
public String proxyPort;
|
||||||
public ProxyType proxyType = ProxyType.DIRECT;
|
public ProxyType proxyType = ProxyType.DIRECT;
|
||||||
public String proxyUser;
|
public String proxyUser;
|
||||||
|
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 String recordLaterSortColumn = "";
|
public String recordLaterSortColumn = "";
|
||||||
|
|
Loading…
Reference in New Issue