Add dialog to edit model groups

This commit is contained in:
0xb00bface 2021-05-09 18:38:42 +02:00
parent 0358a35a84
commit 9bb2d5d593
32 changed files with 548 additions and 122 deletions

View File

@ -63,12 +63,12 @@ import ctbrec.ui.settings.SettingsTab;
import ctbrec.ui.tabs.DonateTabFx;
import ctbrec.ui.tabs.HelpTab;
import ctbrec.ui.tabs.RecentlyWatchedTab;
import ctbrec.ui.tabs.RecordedTab;
import ctbrec.ui.tabs.RecordingsTab;
import ctbrec.ui.tabs.SiteTab;
import ctbrec.ui.tabs.TabSelectionListener;
import ctbrec.ui.tabs.UpdateTab;
import ctbrec.ui.tabs.logging.LoggingTab;
import ctbrec.ui.tabs.recorded.RecordedTab;
import javafx.application.Application;
import javafx.application.HostServices;
import javafx.application.Platform;

View File

@ -0,0 +1,18 @@
package ctbrec.ui;
public enum Icon {
GROUP_16(Icon.class.getResource("/16/users.png").toExternalForm()),
CHECK_16(Icon.class.getResource("/16/check-small.png").toExternalForm()),
CLOCK_16(Icon.class.getResource("/16/clock.png").toExternalForm());
private String url;
private Icon(String url) {
this.url = url;
}
public String url() {
return url;
}
}

View File

@ -22,15 +22,13 @@ import javafx.beans.property.BooleanProperty;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
/**
* Just a wrapper for Model, which augments it with JavaFX value binding properties, so that UI widgets get updated proeprly
*/
public class JavaFxModel implements Model {
private transient StringProperty onlineProperty = new SimpleStringProperty();
private transient StringProperty recordingProperty = new SimpleStringProperty();
private transient BooleanProperty onlineProperty = new SimpleBooleanProperty();
private transient BooleanProperty recordingProperty = new SimpleBooleanProperty();
private transient BooleanProperty pausedProperty = new SimpleBooleanProperty();
private transient SimpleIntegerProperty priorityProperty = new SimpleIntegerProperty();
private transient SimpleObjectProperty<Instant> lastSeenProperty = new SimpleObjectProperty<>();
@ -105,14 +103,22 @@ public class JavaFxModel implements Model {
return delegate.toString();
}
public StringProperty getOnlineProperty() {
public BooleanProperty getOnlineProperty() {
return onlineProperty;
}
public StringProperty getRecordingProperty() {
public void setOnlineProperty(boolean online) {
this.onlineProperty.set(online);
}
public BooleanProperty getRecordingProperty() {
return recordingProperty;
}
public void setRecordingProperty(boolean recording) {
this.recordingProperty.setValue(recording);
}
public BooleanProperty getPausedProperty() {
return pausedProperty;
}

View File

@ -41,7 +41,7 @@ public class AddToGroupAction {
public void execute() {
source.setCursor(Cursor.WAIT);
try {
var dialog = new ModelGroupDialog();
var dialog = new AddModelGroupDialog();
boolean ok = Dialogs.showCustomInput(source.getScene(), "Add model to group", dialog.getMainPane());
dialog.requestFocus();
if (ok) {
@ -70,7 +70,7 @@ public class AddToGroupAction {
}
}
private static class ModelGroupDialog {
private static class AddModelGroupDialog {
private ComboBox<ModelGroupListItem> comboBox;
private TextField editor;
private ObservableListSuggester suggester;

View File

@ -0,0 +1,161 @@
package ctbrec.ui.action;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.ModelGroup;
import ctbrec.recorder.Recorder;
import ctbrec.ui.controls.Dialogs;
import javafx.collections.FXCollections;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.Cursor;
import javafx.scene.Node;
import javafx.scene.control.Button;
import javafx.scene.control.ListView;
import javafx.scene.control.TextField;
import javafx.scene.control.Tooltip;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
public class EditGroupAction {
private static final String DIALOG_TITLE = "Edit model group";
private Node source;
private Model model;
private Recorder recorder;
public EditGroupAction(Node source, Recorder recorder, Model model) {
this.source = source;
this.recorder = recorder;
this.model = model;
}
public void execute() {
source.setCursor(Cursor.WAIT);
try {
var dialog = new EditModelGroupDialog(model);
boolean ok = Dialogs.showCustomInput(source.getScene(), DIALOG_TITLE, dialog);
if (ok) {
var group = dialog.getModelGroup();
group.setName(dialog.getGroupName());
group.getModelUrls().clear();
group.getModelUrls().addAll(dialog.getUrls());
recorder.saveModelGroup(group);
}
} catch (Exception e) {
Dialogs.showError(source.getScene(), DIALOG_TITLE, "Editing model group failed", e);
} finally {
source.setCursor(Cursor.DEFAULT);
}
}
private static class EditModelGroupDialog extends GridPane {
private TextField groupName;
private ListView<String> urlListView;
private ObservableList<String> urlList;
private ModelGroup modelGroup;
private List<String> urls;
public EditModelGroupDialog(Model model) {
Optional<ModelGroup> optionalModelGroup = Config.getInstance().getModelGroup(model);
if (optionalModelGroup.isPresent()) {
modelGroup = optionalModelGroup.get();
urls = new ArrayList<>(modelGroup.getModelUrls());
createGui(modelGroup);
} else {
Dialogs.showError(getScene(), DIALOG_TITLE, "No group found for model", null);
}
}
public ModelGroup getModelGroup() {
return modelGroup;
}
public List<String> getUrls() {
return urls;
}
public String getGroupName() {
return groupName.getText();
}
void createGui(ModelGroup modelGroup) {
setHgap(5);
vgapProperty().bind(hgapProperty());
groupName = new TextField(modelGroup.getName());
Button up = createUpButton();
Button down = createDownButton();
Button remove = createRemoveButton();
var buttons = new VBox(3, up, down, remove);
urlList = FXCollections.observableList(modelGroup.getModelUrls());
urlList.addListener((ListChangeListener<String>) change -> {
urls = new ArrayList<>(urlList);
});
urlListView = new ListView<>(urlList);
GridPane.setHgrow(urlListView, Priority.ALWAYS);
var row = 0;
add(groupName, 0, row++);
add(urlListView, 0, row);
add(buttons, 1, row);
urlListView.getSelectionModel().selectedIndexProperty().addListener((obs, oldV, newV) -> {
var idx = newV.intValue();
boolean noSelection = idx == -1;
up.setDisable(noSelection || idx == 0);
down.setDisable(noSelection || idx == urlList.size() - 1);
remove.setDisable(noSelection);
});
}
private Button createUpButton() {
var button = createButton("\u25B4", "Move step up");
button.setOnAction(evt -> {
int idx = urlListView.getSelectionModel().getSelectedIndex();
String selectedItem = urlListView.getSelectionModel().getSelectedItem();
urlList.remove(idx);
urlList.add(idx - 1, selectedItem);
urlListView.getSelectionModel().select(idx - 1);
});
return button;
}
private Button createDownButton() {
var button = createButton("\u25BE", "Move step down");
button.setOnAction(evt -> {
int idx = urlListView.getSelectionModel().getSelectedIndex();
String selectedItem = urlListView.getSelectionModel().getSelectedItem();
urlList.remove(idx);
urlList.add(idx + 1, selectedItem);
urlListView.getSelectionModel().select(idx + 1);
});
return button;
}
private Button createRemoveButton() {
var button = createButton("-", "Remove selected step");
button.setOnAction(evt -> {
String selectedItem = urlListView.getSelectionModel().getSelectedItem();
if (selectedItem != null) {
urlList.remove(selectedItem);
}
});
return button;
}
private Button createButton(String text, String tooltip) {
var b = new Button(text);
b.setTooltip(new Tooltip(tooltip));
b.setDisable(true);
b.setPrefSize(32, 32);
return b;
}
}
}

View File

@ -56,7 +56,7 @@ public class Dialogs {
}
alert.setContentText(content);
if (parent != null) {
Stage stage = (Stage) alert.getDialogPane().getScene().getWindow();
var stage = (Stage) alert.getDialogPane().getScene().getWindow();
stage.getScene().getStylesheets().addAll(parent.getStylesheets());
}
alert.showAndWait();
@ -77,18 +77,18 @@ public class Dialogs {
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setResizable(true);
InputStream icon = Dialogs.class.getResourceAsStream("/icon.png");
Stage stage = (Stage) dialog.getDialogPane().getScene().getWindow();
var stage = (Stage) dialog.getDialogPane().getScene().getWindow();
stage.getIcons().add(new Image(icon));
if (parent != null) {
stage.getScene().getStylesheets().addAll(parent.getStylesheets());
}
GridPane grid = new GridPane();
var grid = new GridPane();
grid.setHgap(10);
grid.setVgap(10);
grid.setPadding(new Insets(20, 150, 10, 10));
TextArea notes = new TextArea(text);
var notes = new TextArea(text);
notes.setPrefRowCount(3);
grid.add(notes, 0, 0);
dialog.getDialogPane().setContent(grid);
@ -112,7 +112,7 @@ public class Dialogs {
dialog.initModality(Modality.APPLICATION_MODAL);
dialog.setResizable(true);
InputStream icon = Dialogs.class.getResourceAsStream("/icon.png");
Stage stage = (Stage) dialog.getDialogPane().getScene().getWindow();
var stage = (Stage) dialog.getDialogPane().getScene().getWindow();
stage.getIcons().add(new Image(icon));
if (parent != null) {
stage.getScene().getStylesheets().addAll(parent.getStylesheets());
@ -123,7 +123,7 @@ public class Dialogs {
}
public static boolean showConfirmDialog(String title, String message, String header, Scene parent) {
AutosizeAlert confirm = new AutosizeAlert(AlertType.CONFIRMATION, message, parent, YES, NO);
var confirm = new AutosizeAlert(AlertType.CONFIRMATION, message, parent, YES, NO);
confirm.setTitle(title);
confirm.setHeaderText(header);
confirm.showAndWait();
@ -131,8 +131,8 @@ public class Dialogs {
}
public static ButtonType showShutdownDialog(Scene parent) {
String message = "There are recordings in progress";
AutosizeAlert confirm = new AutosizeAlert(AlertType.CONFIRMATION, "", parent, YES, FINISH, NO);
var message = "There are recordings in progress";
var confirm = new AutosizeAlert(AlertType.CONFIRMATION, "", parent, YES, FINISH, NO);
confirm.setTitle("Shutdown");
confirm.setHeaderText(message);
((Button) confirm.getDialogPane().lookupButton(ButtonType.YES)).setText("Shutdown Now");
@ -146,7 +146,7 @@ public class Dialogs {
}
public static Optional<ModelGroup> showModelGroupSelectionDialog(Scene parent, Model model) {
GridPane dialogPane = new GridPane();
var dialogPane = new GridPane();
Set<ModelGroup> modelGroups = Config.getInstance().getSettings().modelGroups;
ObservableList<ModelGroup> comboBoxModel = FXCollections.observableArrayList(modelGroups);
ComboBox<ModelGroup> comboBox = new ComboBox<>(comboBoxModel);
@ -165,7 +165,7 @@ public class Dialogs {
existingGroup.get().add(model);
return existingGroup;
} else {
ModelGroup group = new ModelGroup();
var group = new ModelGroup();
group.setId(UUID.randomUUID());
group.setName(text);
group.add(model);

View File

@ -41,6 +41,7 @@ import ctbrec.ui.SiteUiFactory;
import ctbrec.ui.TipDialog;
import ctbrec.ui.TokenLabel;
import ctbrec.ui.action.AddToGroupAction;
import ctbrec.ui.action.EditGroupAction;
import ctbrec.ui.action.IgnoreModelsAction;
import ctbrec.ui.action.OpenRecordingsDir;
import ctbrec.ui.action.SetStopDateAction;
@ -469,57 +470,57 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
}
private ContextMenu createContextMenu(ThumbCell cell) {
Model model = cell.getModel();
var model = cell.getModel();
boolean modelIsTrackedByRecorder = recorder.isTracked(model);
MenuItem openInPlayer = new MenuItem("Open in Player");
var openInPlayer = new MenuItem("Open in Player");
openInPlayer.setOnAction(e -> startPlayer(getSelectedThumbCells(cell)));
MenuItem start = new MenuItem("Start Recording");
var start = new MenuItem("Start Recording");
start.setOnAction(e -> startStopAction(getSelectedThumbCells(cell), true));
MenuItem stop = new MenuItem("Stop Recording");
var stop = new MenuItem("Stop Recording");
stop.setOnAction(e -> startStopAction(getSelectedThumbCells(cell), false));
MenuItem startStop = recorder.isTracked(model) ? stop : start;
var startStop = recorder.isTracked(model) ? stop : start;
MenuItem recordUntil = new MenuItem("Start Recording Until");
var recordUntil = new MenuItem("Start Recording Until");
recordUntil.setOnAction(e -> startRecordingWithTimeLimit(getSelectedThumbCells(cell)));
MenuItem addPaused = new MenuItem("Add in paused state");
var addPaused = new MenuItem("Add in paused state");
addPaused.setOnAction(e -> addPaused(getSelectedThumbCells(cell)));
MenuItem recordLater = new MenuItem("Record Later");
var recordLater = new MenuItem("Record Later");
recordLater.setOnAction(e -> recordLater(getSelectedThumbCells(cell), true));
MenuItem removeRecordLater = new MenuItem("Forget Model");
var removeRecordLater = new MenuItem("Forget Model");
removeRecordLater.setOnAction(e -> recordLater(getSelectedThumbCells(cell), false));
MenuItem addRemoveBookmark = recorder.isMarkedForLaterRecording(model) ? removeRecordLater : recordLater;
var addRemoveBookmark = recorder.isMarkedForLaterRecording(model) ? removeRecordLater : recordLater;
MenuItem pause = new MenuItem("Pause Recording");
var pause = new MenuItem("Pause Recording");
pause.setOnAction(e -> pauseResumeAction(getSelectedThumbCells(cell), true));
MenuItem resume = new MenuItem("Resume Recording");
var resume = new MenuItem("Resume Recording");
resume.setOnAction(e -> pauseResumeAction(getSelectedThumbCells(cell), false));
MenuItem pauseResume = recorder.isSuspended(model) ? resume : pause;
var pauseResume = recorder.isSuspended(model) ? resume : pause;
MenuItem follow = new MenuItem("Follow");
var follow = new MenuItem("Follow");
follow.setOnAction(e -> follow(getSelectedThumbCells(cell), true));
MenuItem unfollow = new MenuItem("Unfollow");
var unfollow = new MenuItem("Unfollow");
unfollow.setOnAction(e -> follow(getSelectedThumbCells(cell), false));
MenuItem addToGroup = new MenuItem("Add to group");
var addToGroup = new MenuItem("Add to group");
addToGroup.setOnAction(e -> addToGroup(model));
MenuItem editGroup = new MenuItem("Edit group");
var editGroup = new MenuItem("Edit group");
editGroup.setOnAction(e -> editGroup(model));
MenuItem ignore = new MenuItem("Ignore");
var ignore = new MenuItem("Ignore");
ignore.setOnAction(e -> ignore(getSelectedThumbCells(cell)));
MenuItem refresh = new MenuItem("Refresh Overview");
var refresh = new MenuItem("Refresh Overview");
refresh.setOnAction(e -> refresh());
MenuItem openRecDir = new MenuItem("Open recording directory");
var openRecDir = new MenuItem("Open recording directory");
openRecDir.setOnAction(e -> new OpenRecordingsDir(cell, model).execute());
MenuItem copyUrl = createCopyUrlMenuItem(cell);
MenuItem openInBrowser = createOpenInBrowser(cell);
MenuItem sendTip = createTipMenuItem(cell);
var copyUrl = createCopyUrlMenuItem(cell);
var openInBrowser = createOpenInBrowser(cell);
var sendTip = createTipMenuItem(cell);
configureItemsForSelection(cell, openInPlayer, copyUrl, sendTip);
@ -535,7 +536,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
}
contextMenu.getItems().add(new SeparatorMenuItem());
if (site.supportsFollow()) {
MenuItem followOrUnFollow = (this instanceof FollowedTab) ? unfollow : follow;
var followOrUnFollow = (this instanceof FollowedTab) ? unfollow : follow;
followOrUnFollow.setDisable(!site.credentialsAvailable());
contextMenu.getItems().add(followOrUnFollow);
}
@ -546,7 +547,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
contextMenu.getItems().add(modelGroup.isEmpty() ? addToGroup : editGroup);
contextMenu.getItems().addAll(copyUrl, openInBrowser, ignore, refresh, openRecDir);
if (model instanceof MyFreeCamsModel && Objects.equals(System.getenv("CTBREC_DEV"), "1")) {
MenuItem debug = new MenuItem("debug");
var debug = new MenuItem("debug");
debug.setOnAction(e -> MyFreeCamsClient.getInstance().getSessionState(model));
contextMenu.getItems().add(debug);
}
@ -555,6 +556,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
}
private void editGroup(Model model) {
new EditGroupAction(this.getContent(), recorder, model).execute();
}
private void addToGroup(Model model) {

View File

@ -0,0 +1,21 @@
package ctbrec.ui.tabs.recorded;
import ctbrec.ui.JavaFxModel;
import ctbrec.ui.action.PlayAction;
import javafx.scene.control.TableCell;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
public class ClickableTableCell<T> extends TableCell<JavaFxModel, T> {
public ClickableTableCell() {
addEventFilter(MouseEvent.MOUSE_CLICKED, event -> {
if (event.getButton() == MouseButton.PRIMARY && event.getClickCount() == 2) {
JavaFxModel selectedModel = getTableView().getSelectionModel().getSelectedItem();
if(selectedModel != null) {
new PlayAction(getTableView(), selectedModel).execute();
}
}
});
}
}

View File

@ -0,0 +1,47 @@
package ctbrec.ui.tabs.recorded;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
import ctbrec.ui.Icon;
import javafx.scene.control.Tooltip;
import javafx.scene.image.ImageView;
import javafx.scene.layout.HBox;
public class IconTableCell<T> extends ClickableTableCell<T> {
protected String tooltip;
protected HBox iconRow;
private Map<Icon, ImageView> icons;
public IconTableCell(Map<Icon, ImageView> icons) {
this.icons = Objects.requireNonNullElse(icons, new HashMap<>());
iconRow = new HBox(3);
}
protected void show(Icon iconName) {
var imageView = icons.get(iconName);
if (imageView != null) {
iconRow.getChildren().remove(imageView);
iconRow.getChildren().add(imageView);
}
}
protected void hide(Icon iconName) {
var imageView = icons.get(iconName);
if (imageView != null) {
iconRow.getChildren().remove(imageView);
}
}
@Override
protected void updateItem(T value, boolean empty) {
if (tooltip != null) {
setTooltip(new Tooltip(tooltip));
} else {
setTooltip(null);
}
setGraphic(iconRow);
}
}

View File

@ -0,0 +1,40 @@
package ctbrec.ui.tabs.recorded;
import static ctbrec.ui.Icon.*;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.ModelGroup;
import javafx.scene.image.ImageView;
public class ModelNameTableCell extends IconTableCell<String> {
public ModelNameTableCell() {
super(Map.of(GROUP_16, new ImageView(GROUP_16.url())));
}
@Override
protected void updateItem(String item, boolean empty) {
setText(null);
tooltip = null;
hide(GROUP_16);
if (item != null && !empty) {
setText(item);
Model m = getTableView().getItems().get(getTableRow().getIndex());
Optional<ModelGroup> optionalGroup = Config.getInstance().getModelGroup(m);
if (optionalGroup.isPresent()) {
ModelGroup group = optionalGroup.get();
setText(group.getName() + " (aka " + item + ')');
show(GROUP_16);
tooltip = group.getModelUrls().size() + " models:\n";
tooltip += group.getModelUrls().stream().collect(Collectors.joining("\n"));
}
}
super.updateItem(item, empty);
}
}

View File

@ -0,0 +1,32 @@
package ctbrec.ui.tabs.recorded;
import static ctbrec.ui.Icon.*;
import java.util.Map;
import com.google.common.base.Objects;
import javafx.scene.image.ImageView;
public class OnlineTableCell extends IconTableCell<Boolean> {
public OnlineTableCell() {
super(Map.of(CHECK_16, new ImageView(CHECK_16.url())));
}
@Override
protected void updateItem(Boolean value, boolean empty) {
if (!empty) {
if (Objects.equal(value, Boolean.TRUE)) {
show(CHECK_16);
tooltip = "Online";
} else {
hide(CHECK_16);
tooltip = null;
}
} else {
tooltip = null;
}
super.updateItem(value, empty);
}
}

View File

@ -1,4 +1,4 @@
package ctbrec.ui.tabs;
package ctbrec.ui.tabs.recorded;
import java.io.IOException;
import java.security.InvalidKeyException;
@ -37,6 +37,7 @@ 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.tabs.TabSelectionListener;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringPropertyBase;

View File

@ -1,16 +1,11 @@
package ctbrec.ui.tabs;
package ctbrec.ui.tabs.recorded;
import static ctbrec.Recording.State.*;
import static ctbrec.ui.UnicodeEmoji.*;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.time.Instant;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
@ -31,6 +26,7 @@ import org.slf4j.LoggerFactory;
import ctbrec.Config;
import ctbrec.Model;
import ctbrec.ModelGroup;
import ctbrec.Recording;
import ctbrec.StringUtil;
import ctbrec.recorder.Recorder;
@ -40,7 +36,9 @@ import ctbrec.ui.DesktopIntegration;
import ctbrec.ui.JavaFxModel;
import ctbrec.ui.PreviewPopupHandler;
import ctbrec.ui.StreamSourceSelectionDialog;
import ctbrec.ui.action.AddToGroupAction;
import ctbrec.ui.action.CheckModelAccountAction;
import ctbrec.ui.action.EditGroupAction;
import ctbrec.ui.action.EditNotesAction;
import ctbrec.ui.action.FollowAction;
import ctbrec.ui.action.IgnoreModelsAction;
@ -59,6 +57,7 @@ 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.tabs.TabSelectionListener;
import javafx.application.Platform;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringPropertyBase;
@ -157,7 +156,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
table.setEditable(true);
table.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE);
PreviewPopupHandler previewPopupHandler = new PreviewPopupHandler(table);
var previewPopupHandler = new PreviewPopupHandler(table);
table.setRowFactory(tableview -> {
TableRow<JavaFxModel> row = new TableRow<>();
row.addEventHandler(MouseEvent.ANY, previewPopupHandler);
@ -174,7 +173,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
TableColumn<JavaFxModel, String> name = new TableColumn<>("Model");
name.setPrefWidth(200);
name.setCellValueFactory(new PropertyValueFactory<>("displayName"));
name.setCellFactory(new ClickableCellFactory<>());
name.setCellFactory(param -> new ModelNameTableCell());
name.setEditable(false);
name.setId("name");
TableColumn<JavaFxModel, String> url = new TableColumn<>("URL");
@ -183,15 +182,16 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
url.setPrefWidth(400);
url.setEditable(false);
url.setId("url");
TableColumn<JavaFxModel, String> online = new TableColumn<>("Online");
TableColumn<JavaFxModel, Boolean> online = new TableColumn<>("Online");
online.setCellValueFactory(cdf -> cdf.getValue().getOnlineProperty());
online.setCellFactory(param -> new OnlineTableCell());
online.setPrefWidth(100);
online.setEditable(false);
online.setId("online");
online.setStyle(STYLE_ALIGN_CENTER);
TableColumn<JavaFxModel, String> recording = new TableColumn<>("Recording");
TableColumn<JavaFxModel, Boolean> recording = new TableColumn<>("Recording");
recording.setCellValueFactory(cdf -> cdf.getValue().getRecordingProperty());
recording.setCellFactory(tc -> new RecordingCell());
recording.setCellFactory(tc -> new RecordingTableCell());
recording.setPrefWidth(100);
recording.setEditable(false);
recording.setId("recording");
@ -272,7 +272,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
scrollPane.setContent(table);
HBox addModelBox = new HBox(5);
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()));
@ -302,7 +302,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
checkModelAccountExistance.setOnAction(evt -> new CheckModelAccountAction(checkModelAccountExistance, recorder)
.execute(Predicate.not(Model::isMarkedForLaterRecording)));
HBox filterContainer = new HBox();
var filterContainer = new HBox();
filterContainer.setSpacing(0);
filterContainer.setPadding(new Insets(0));
filterContainer.setAlignment(Pos.CENTER_RIGHT);
@ -326,7 +326,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
filterContainer.getChildren().add(filter);
addModelBox.getChildren().add(filterContainer);
BorderPane root = new BorderPane();
var root = new BorderPane();
root.setPadding(new Insets(5));
root.setTop(addModelBox);
root.setCenter(scrollPane);
@ -338,7 +338,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
private void jumpToNextModel(KeyCode code) {
if (!table.getItems().isEmpty() && (code.isLetterKey() || code.isDigitKey())) {
// determine where to start looking for the next model
int startAt = 0;
var startAt = 0;
if (table.getSelectionModel().getSelectedIndex() >= 0) {
startAt = table.getSelectionModel().getSelectedIndex() + 1;
if (startAt >= table.getItems().size()) {
@ -346,7 +346,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
}
}
String c = code.getChar().toLowerCase();
var c = code.getChar().toLowerCase();
int i = startAt;
do {
JavaFxModel current = table.getItems().get(i);
@ -378,7 +378,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
private void updatePriority(JavaFxModel model, int priority) {
try {
if (priority < 0 || priority > 100) {
String msg = "Priority has to be between 0 and 100";
var msg = "Priority has to be between 0 and 100";
Dialogs.showError(table.getScene(), "Invalid value", msg, null);
} else {
model.setPriority(priority);
@ -404,7 +404,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
private void addModelByUrl(String url) {
for (Site site : sites) {
Model newModel = site.createModelFromUrl(url);
var newModel = site.createModelFromUrl(url);
if (newModel != null) {
try {
recorder.addModel(newModel);
@ -431,7 +431,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
for (Site site : sites) {
if (Objects.equals(siteName.toLowerCase(), site.getClass().getSimpleName().toLowerCase())) {
try {
Model m = site.createModel(modelName);
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);
@ -548,18 +548,18 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
String[] tokens = filter.split(" ");
observableModels.addAll(filteredModels);
filteredModels.clear();
for (int i = 0; i < table.getItems().size(); i++) {
StringBuilder sb = new StringBuilder();
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) {
String content = cellData.toString();
var content = cellData.toString();
sb.append(content).append(' ');
}
}
String searchText = sb.toString();
var searchText = sb.toString();
boolean tokensMissing = false;
var tokensMissing = false;
for (String token : tokens) {
if (!searchText.toLowerCase().contains(token.toLowerCase())) {
tokensMissing = true;
@ -594,18 +594,14 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
.peek(fxm -> { // NOSONAR
for (Recording recording : recordings) {
if(recording.getStatus() == RECORDING && Objects.equals(recording.getModel(), fxm)){
String recordingValue = HEAVY_CHECK_MARK;
if(!Objects.equals(recording.getModel().getRecordUntil(), Instant.ofEpochMilli(Model.RECORD_INDEFINITELY))) {
recordingValue += ' ' + CLOCK;
}
fxm.getRecordingProperty().set(recordingValue);
fxm.setRecordingProperty(true);
break;
}
}
for (Model onlineModel : onlineModels) {
if(Objects.equals(onlineModel, fxm)) {
fxm.getOnlineProperty().set(HEAVY_CHECK_MARK);
fxm.setOnlineProperty(true);
break;
}
}
@ -616,7 +612,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
}
};
ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r);
var t = new Thread(r);
t.setDaemon(true);
t.setName("RecordedModelsTab UpdateService");
return t;
@ -645,44 +641,49 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
if (selectedModels.isEmpty()) {
return null;
}
MenuItem stop = new MenuItem("Remove Model");
var stop = new MenuItem("Remove Model");
stop.setOnAction(e -> stopAction(selectedModels));
MenuItem recordLater = new MenuItem("Record Later");
var recordLater = new MenuItem("Record Later");
recordLater.setOnAction(e -> recordLater(selectedModels));
MenuItem copyUrl = new MenuItem("Copy URL");
var copyUrl = new MenuItem("Copy URL");
copyUrl.setOnAction(e -> {
Model selected = selectedModels.get(0);
final Clipboard clipboard = Clipboard.getSystemClipboard();
final ClipboardContent content = new ClipboardContent();
final var clipboard = Clipboard.getSystemClipboard();
final var content = new ClipboardContent();
content.putString(selected.getUrl());
clipboard.setContent(content);
});
MenuItem pauseRecording = new MenuItem("Pause Recording");
var pauseRecording = new MenuItem("Pause Recording");
pauseRecording.setOnAction(e -> pauseRecording(selectedModels));
MenuItem resumeRecording = new MenuItem("Resume Recording");
var resumeRecording = new MenuItem("Resume Recording");
resumeRecording.setOnAction(e -> resumeRecording(selectedModels));
MenuItem stopRecordingAt = new MenuItem("Stop Recording at Date");
var stopRecordingAt = new MenuItem("Stop Recording at Date");
stopRecordingAt.setOnAction(e -> setStopDate(selectedModels.get(0)));
MenuItem removeTimeLimit = new MenuItem("Remove Time Limit");
var removeTimeLimit = new MenuItem("Remove Time Limit");
removeTimeLimit.setOnAction(e -> removeTimeLimit(selectedModels.get(0)));
MenuItem openInBrowser = new MenuItem("Open in Browser");
var openInBrowser = new MenuItem("Open in Browser");
openInBrowser.setOnAction(e -> DesktopIntegration.open(selectedModels.get(0).getUrl()));
MenuItem openInPlayer = new MenuItem("Open in Player");
var openInPlayer = new MenuItem("Open in Player");
openInPlayer.setOnAction(e -> openInPlayer(selectedModels.get(0)));
MenuItem switchStreamSource = new MenuItem("Switch resolution");
var switchStreamSource = new MenuItem("Switch resolution");
switchStreamSource.setOnAction(e -> switchStreamSource(selectedModels.get(0)));
MenuItem follow = new MenuItem("Follow");
var follow = new MenuItem("Follow");
follow.setOnAction(e -> follow(selectedModels));
follow.setDisable(!selectedModels.stream().allMatch(m -> m.getSite().supportsFollow() && m.getSite().credentialsAvailable()));
MenuItem ignore = new MenuItem("Ignore");
var ignore = new MenuItem("Ignore");
ignore.setOnAction(e -> ignore(selectedModels));
MenuItem notes = new MenuItem("Notes");
var notes = new MenuItem("Notes");
notes.setOnAction(e -> notes(selectedModels));
MenuItem openRecDir = new MenuItem("Open recording directory");
var openRecDir = new MenuItem("Open recording directory");
openRecDir.setOnAction(e -> new OpenRecordingsDir(table, selectedModels.get(0)).execute());
var addToGroup = new MenuItem("Add to group");
addToGroup.setOnAction(e -> addToGroup(selectedModels.get(0)));
var editGroup = new MenuItem("Edit group");
editGroup.setOnAction(e -> editGroup(selectedModels.get(0)));
ContextMenu menu = new CustomMouseBehaviorContextMenu(stop, recordLater);
if (selectedModels.size() == 1) {
menu.getItems().add(selectedModels.get(0).isSuspended() ? resumeRecording : pauseRecording);
@ -693,6 +694,8 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
} else {
menu.getItems().addAll(resumeRecording, pauseRecording);
}
Optional<ModelGroup> modelGroup = Config.getInstance().getModelGroup(selectedModels.get(0));
menu.getItems().add(modelGroup.isEmpty() ? addToGroup : editGroup);
menu.getItems().addAll(copyUrl, openInPlayer, openInBrowser, openRecDir, switchStreamSource, follow, notes, ignore);
if (selectedModels.size() > 1) {
@ -706,6 +709,16 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
return menu;
}
private void addToGroup(Model model) {
new AddToGroupAction(this.getContent(), recorder, model).execute();
table.refresh();
}
private void editGroup(Model model) {
new EditGroupAction(this.getContent(), recorder, model).execute();
table.refresh();
}
private void setStopDate(JavaFxModel model) {
new SetStopDateAction(table, model.getDelegate(), recorder) //
.execute() //
@ -735,7 +748,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
}
private void switchStreamSource(JavaFxModel fxModel) {
String couldntSwitchHeaderText = "Couldn't switch stream resolution";
var couldntSwitchHeaderText = "Couldn't switch stream resolution";
try {
if (!fxModel.isOnline(true)) {
@ -781,7 +794,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
}
private boolean stopAction(List<JavaFxModel> selectedModels) {
boolean confirmed = true;
var confirmed = true;
if (Config.getInstance().getSettings().confirmationForDangerousActions) {
int n = selectedModels.size();
String plural = n > 1 ? "s" : "";
@ -829,9 +842,9 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
Config.getInstance().getSettings().recordedModelsSortType = col.getSortType().toString();
}
int columns = table.getColumns().size();
double[] columnWidths = new double[columns];
String[] columnIds = new String[columns];
for (int i = 0; i < columnWidths.length; i++) {
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();
}
@ -862,8 +875,8 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
private void restoreColumnOrder() {
String[] columnIds = Config.getInstance().getSettings().recordedModelsColumnIds;
ObservableList<TableColumn<JavaFxModel,?>> columns = table.getColumns();
for (int i = 0; i < columnIds.length; i++) {
for (int j = 0; j < table.getColumns().size(); j++) {
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
@ -876,7 +889,7 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
private void restoreColumnWidths() {
double[] columnWidths = Config.getInstance().getSettings().recordedModelsColumnWidths;
if (columnWidths != null && columnWidths.length == table.getColumns().size()) {
for (int i = 0; i < columnWidths.length; i++) {
for (var i = 0; i < columnWidths.length; i++) {
table.getColumns().get(i).setPrefWidth(columnWidths[i]);
}
}
@ -930,25 +943,4 @@ public class RecordedModelsTab extends Tab implements TabSelectionListener {
return tableCell;
}
}
private class RecordingCell extends TableCell<JavaFxModel, String> {
@Override
protected void updateItem(String value, boolean empty) {
super.updateItem(value, empty);
if (value == null) {
setTooltip(null);
setText(null);
} else {
Model m = getTableView().getItems().get(getTableRow().getIndex());
if (m.isRecordingTimeLimited()) {
Tooltip tooltip = new Tooltip();
DateTimeFormatter dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
ZonedDateTime zonedDateTime = m.getRecordUntil().atZone(ZoneId.systemDefault());
tooltip.setText("Recording until " + dtf.format(zonedDateTime));
setTooltip(tooltip);
}
setText(value);
}
}
}
}

View File

@ -1,10 +1,11 @@
package ctbrec.ui.tabs;
package ctbrec.ui.tabs.recorded;
import java.util.List;
import ctbrec.recorder.Recorder;
import ctbrec.sites.Site;
import ctbrec.ui.ShutdownListener;
import ctbrec.ui.tabs.TabSelectionListener;
import javafx.beans.value.ChangeListener;
import javafx.geometry.Side;
import javafx.scene.control.Tab;

View File

@ -0,0 +1,47 @@
package ctbrec.ui.tabs.recorded;
import static ctbrec.ui.Icon.*;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;
import java.util.Map;
import java.util.Objects;
import ctbrec.Model;
import ctbrec.SubsequentAction;
import javafx.scene.image.ImageView;
public class RecordingTableCell extends IconTableCell<Boolean> {
public RecordingTableCell() {
super(Map.of( //
CHECK_16, new ImageView(CHECK_16.url()), //
CLOCK_16, new ImageView(CLOCK_16.url())));
}
@Override
protected void updateItem(Boolean value, boolean empty) {
tooltip = null;
hide(CHECK_16);
hide(CLOCK_16);
if (value == null || empty) {
return;
}
if (Objects.equals(value, Boolean.TRUE)) {
show(CHECK_16);
tooltip = "Recording";
}
Model m = getTableView().getItems().get(getTableRow().getIndex());
if (m.isRecordingTimeLimited()) {
show(CLOCK_16);
var dtf = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
var zonedDateTime = m.getRecordUntil().atZone(ZoneId.systemDefault());
String action = m.getRecordUntilSubsequentAction() == SubsequentAction.PAUSE ? "Pause" : "Remove";
tooltip = action + " at " + dtf.format(zonedDateTime);
}
super.updateItem(value, empty);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 822 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

View File

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
viewBox="0 0 512 512"
version="1.1"
id="svg4"
sodipodi:docname="check-small.svg"
inkscape:version="1.0.2 (e86c870879, 2021-01-15, custom)"
inkscape:export-filename="/tmp/check-small.png"
inkscape:export-xdpi="6.1875"
inkscape:export-ydpi="6.1875">
<metadata
id="metadata10">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs8" />
<sodipodi:namedview
pagecolor="#ffffff"
bordercolor="#666666"
borderopacity="1"
objecttolerance="10"
gridtolerance="10"
guidetolerance="10"
inkscape:pageopacity="0"
inkscape:pageshadow="2"
inkscape:window-width="1692"
inkscape:window-height="996"
id="namedview6"
showgrid="false"
inkscape:zoom="0.86731066"
inkscape:cx="195.31674"
inkscape:cy="250.01464"
inkscape:window-x="492"
inkscape:window-y="126"
inkscape:window-maximized="0"
inkscape:current-layer="svg4" />
<!-- Font Awesome Free 5.15.3 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) -->
<path
id="path2"
d="m 381.39691,131.00011 c -3.5266,-0.0142 -7.05951,1.31523 -9.76101,3.9928 L 209.31438,295.84932 140.6532,226.69926 c -5.35937,-5.39755 -14.08537,-5.43238 -19.48837,-0.0784 l -26.089151,25.85232 c -5.402999,5.35397 -5.439003,14.07004 -0.07851,19.46875 L 199.24608,376.92842 c 5.35938,5.39756 14.08538,5.43354 19.48837,0.0784 L 416.9263,180.60297 c 5.40184,-5.35511 5.43567,-14.07117 0.0762,-19.46875 l -25.87831,-26.0629 c -2.67968,-2.69878 -6.20076,-4.05702 -9.72735,-4.07121 z"
style="stroke-width:1.14778" />
</svg>

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 291 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 757 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 603 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.6 KiB

View File

@ -106,6 +106,7 @@ public class Settings {
public String mfcPassword = "";
public String mfcUsername = "";
public boolean minimizeToTray = false;
@Deprecated
public int minimumLengthInSeconds = 0;
public long minimumSpaceLeftInBytes = 0;
public Map<String, String> modelNotes = new HashMap<>();