Add multi-selection to ThumbOverviewTab
Clicking while holding shift now selects a ThumbCell and allows the multiple ThumbCells at one. The actions in the context menu are applied to all selected models. "copy url" is disabled, if multiple models are selected, because that doesn't make sense.
This commit is contained in:
parent
d58e80ce00
commit
45e569e08a
|
@ -97,6 +97,7 @@ public class CtbrecApplication extends Application {
|
|||
int windowWidth = Config.getInstance().getSettings().windowWidth;
|
||||
int windowHeight = Config.getInstance().getSettings().windowHeight;
|
||||
primaryStage.setScene(new Scene(tabPane, windowWidth, windowHeight));
|
||||
primaryStage.getScene().getStylesheets().add("/ctbrec/ui/ThumbCell.css");
|
||||
primaryStage.getScene().widthProperty().addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowWidth = newVal.intValue());
|
||||
primaryStage.getScene().heightProperty().addListener((observable, oldVal, newVal) -> Config.getInstance().getSettings().windowHeight = newVal.intValue());
|
||||
primaryStage.setMaximized(Config.getInstance().getSettings().windowMaximized);
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
.selection-background {
|
||||
/*-fx-fill: #0096C9;*/
|
||||
-fx-fill: -fx-accent;
|
||||
}
|
|
@ -24,24 +24,18 @@ import javafx.animation.Interpolator;
|
|||
import javafx.animation.ParallelTransition;
|
||||
import javafx.animation.Transition;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.value.ChangeListener;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.collections.ObservableList;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.Cursor;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.image.ImageView;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.input.ContextMenuEvent;
|
||||
import javafx.scene.input.MouseButton;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.StackPane;
|
||||
import javafx.scene.paint.Color;
|
||||
import javafx.scene.shape.Circle;
|
||||
|
@ -69,6 +63,7 @@ public class ThumbCell extends StackPane {
|
|||
private Rectangle resolutionBackground;
|
||||
private Rectangle nameBackground;
|
||||
private Rectangle topicBackground;
|
||||
private Rectangle selectionOverlay;
|
||||
private Text name;
|
||||
private Text topic;
|
||||
private Text resolutionTag;
|
||||
|
@ -80,14 +75,13 @@ public class ThumbCell extends StackPane {
|
|||
private Color colorNormal = Color.BLACK;
|
||||
private Color colorHighlight = Color.WHITE;
|
||||
private Color colorRecording = new Color(0.8, 0.28, 0.28, 1);
|
||||
private SimpleBooleanProperty selectionProperty = new SimpleBooleanProperty(false);
|
||||
|
||||
private HttpClient client;
|
||||
|
||||
private ThumbOverviewTab parent;
|
||||
private ObservableList<Node> thumbCellList;
|
||||
|
||||
public ThumbCell(ThumbOverviewTab parent, Model model, Recorder recorder, HttpClient client) {
|
||||
this.parent = parent;
|
||||
this.thumbCellList = parent.grid.getChildren();
|
||||
this.model = model;
|
||||
this.recorder = recorder;
|
||||
|
@ -158,6 +152,14 @@ public class ThumbCell extends StackPane {
|
|||
recordingAnimation.setCycleCount(FadeTransition.INDEFINITE);
|
||||
recordingAnimation.setAutoReverse(true);
|
||||
|
||||
selectionOverlay = new Rectangle();
|
||||
//selectionOverlay.setFill(new Color(0, 150f/255, 201f/255, .75));
|
||||
//selectionOverlay.setStyle("-fx-background-color: -fx-accent");
|
||||
//selectionOverlay.getStyleClass().add("table-view");
|
||||
selectionOverlay.setOpacity(0);
|
||||
StackPane.setAlignment(selectionOverlay, Pos.TOP_LEFT);
|
||||
getChildren().add(selectionOverlay);
|
||||
|
||||
setOnMouseEntered((e) -> {
|
||||
new ParallelTransition(changeColor(nameBackground, colorNormal, colorHighlight), changeColor(name, colorHighlight, colorNormal)).playFromStart();
|
||||
new ParallelTransition(changeOpacity(topicBackground, 0.7), changeOpacity(topic, 0.7)).playFromStart();
|
||||
|
@ -166,20 +168,6 @@ public class ThumbCell extends StackPane {
|
|||
new ParallelTransition(changeColor(nameBackground, colorHighlight, colorNormal), changeColor(name, colorNormal, colorHighlight)).playFromStart();
|
||||
new ParallelTransition(changeOpacity(topicBackground, 0), changeOpacity(topic, 0)).playFromStart();
|
||||
});
|
||||
setOnMouseClicked(doubleClickListener);
|
||||
addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
|
||||
parent.suspendUpdates(true);
|
||||
popup = createContextMenu();
|
||||
popup.show(ThumbCell.this, event.getScreenX(), event.getScreenY());
|
||||
popup.setOnHidden((e) -> parent.suspendUpdates(false));
|
||||
event.consume();
|
||||
});
|
||||
addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
|
||||
if(popup != null) {
|
||||
popup.hide();
|
||||
}
|
||||
});
|
||||
|
||||
setThumbWidth(width);
|
||||
|
||||
setRecording(recording);
|
||||
|
@ -188,6 +176,20 @@ public class ThumbCell extends StackPane {
|
|||
}
|
||||
}
|
||||
|
||||
public void setSelected(boolean selected) {
|
||||
selectionProperty.set(selected);
|
||||
selectionOverlay.getStyleClass().add("selection-background");
|
||||
selectionOverlay.setOpacity(selected ? .75 : 0);
|
||||
}
|
||||
|
||||
public boolean isSelected() {
|
||||
return selectionProperty.get();
|
||||
}
|
||||
|
||||
public ObservableValue<Boolean> selectionProperty() {
|
||||
return selectionProperty;
|
||||
}
|
||||
|
||||
private void determineResolution() {
|
||||
if(ThumbOverviewTab.resolutionProcessing.contains(model)) {
|
||||
LOG.debug("Already fetching resolution for model {}. Queue size {}", model.getName(), ThumbOverviewTab.resolutionProcessing.size());
|
||||
|
@ -268,38 +270,6 @@ public class ThumbCell extends StackPane {
|
|||
}
|
||||
}
|
||||
|
||||
private ContextMenu createContextMenu() {
|
||||
MenuItem openInPlayer = new MenuItem("Open in Player");
|
||||
openInPlayer.setOnAction((e) -> startPlayer());
|
||||
|
||||
MenuItem start = new MenuItem("Start Recording");
|
||||
start.setOnAction((e) -> startStopAction(true));
|
||||
MenuItem stop = new MenuItem("Stop Recording");
|
||||
stop.setOnAction((e) -> startStopAction(false));
|
||||
MenuItem startStop = recorder.isRecording(model) ? stop : start;
|
||||
|
||||
MenuItem follow = new MenuItem("Follow");
|
||||
follow.setOnAction((e) -> follow(true));
|
||||
MenuItem unfollow = new MenuItem("Unfollow");
|
||||
unfollow.setOnAction((e) -> follow(false));
|
||||
|
||||
MenuItem copyUrl = new MenuItem("Copy URL");
|
||||
copyUrl.setOnAction((e) -> {
|
||||
final Clipboard clipboard = Clipboard.getSystemClipboard();
|
||||
final ClipboardContent content = new ClipboardContent();
|
||||
content.putString(model.getUrl());
|
||||
clipboard.setContent(content);
|
||||
});
|
||||
|
||||
ContextMenu contextMenu = new ContextMenu();
|
||||
contextMenu.setAutoHide(true);
|
||||
contextMenu.setHideOnEscape(true);
|
||||
contextMenu.setAutoFix(true);
|
||||
MenuItem followOrUnFollow = parent instanceof FollowedTab ? unfollow : follow;
|
||||
contextMenu.getItems().addAll(openInPlayer, startStop , followOrUnFollow, copyUrl);
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
private Transition changeColor(Shape shape, Color from, Color to) {
|
||||
FillTransition transition = new FillTransition(ANIMATION_DURATION, from, to);
|
||||
transition.setShape(shape);
|
||||
|
@ -313,16 +283,7 @@ public class ThumbCell extends StackPane {
|
|||
return transition;
|
||||
}
|
||||
|
||||
private EventHandler<MouseEvent> doubleClickListener = new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent e) {
|
||||
if(e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) {
|
||||
startPlayer();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private void startPlayer() {
|
||||
void startPlayer() {
|
||||
// TODO if manual choice of stream quality is enabled, do the same thing as starting a download here?!?
|
||||
// or maybe not, because the player should automatically switch between resolutions depending on the
|
||||
// network bandwidth
|
||||
|
@ -359,7 +320,7 @@ public class ThumbCell extends StackPane {
|
|||
recordingIndicator.setVisible(recording);
|
||||
}
|
||||
|
||||
private void startStopAction(boolean start) {
|
||||
void startStopAction(boolean start) {
|
||||
setCursor(Cursor.WAIT);
|
||||
|
||||
boolean selectSource = Config.getInstance().getSettings().chooseStreamQuality;
|
||||
|
@ -408,7 +369,7 @@ public class ThumbCell extends StackPane {
|
|||
}.start();
|
||||
}
|
||||
|
||||
private void follow(boolean follow) {
|
||||
void follow(boolean follow) {
|
||||
setCursor(Cursor.WAIT);
|
||||
new Thread() {
|
||||
@Override
|
||||
|
@ -553,5 +514,7 @@ public class ThumbCell extends StackPane {
|
|||
int margin = 4;
|
||||
topic.maxWidth(w-margin*2);
|
||||
topic.setWrappingWidth(w-margin*2);
|
||||
selectionOverlay.setWidth(w);
|
||||
selectionOverlay.setHeight(h);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,16 +34,24 @@ import javafx.concurrent.ScheduledService;
|
|||
import javafx.concurrent.Task;
|
||||
import javafx.concurrent.Worker.State;
|
||||
import javafx.concurrent.WorkerStateEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.Alert;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ComboBox;
|
||||
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.TextField;
|
||||
import javafx.scene.control.Tooltip;
|
||||
import javafx.scene.input.Clipboard;
|
||||
import javafx.scene.input.ClipboardContent;
|
||||
import javafx.scene.input.ContextMenuEvent;
|
||||
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;
|
||||
|
@ -61,6 +69,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
ScheduledService<List<Model>> updateService;
|
||||
Recorder recorder;
|
||||
List<ThumbCell> filteredThumbCells = Collections.synchronizedList(new ArrayList<>());
|
||||
List<ThumbCell> selectedThumbCells = Collections.synchronizedList(new ArrayList<>());
|
||||
String filter;
|
||||
FlowPane grid = new FlowPane();
|
||||
ReentrantLock gridLock = new ReentrantLock();
|
||||
|
@ -73,6 +82,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
Button pagePrev = new Button("◀");
|
||||
Button pageNext = new Button("▶");
|
||||
private volatile boolean updatesSuspended = false;
|
||||
ContextMenu popup;
|
||||
|
||||
private ComboBox<Integer> thumbWidth;
|
||||
|
||||
|
@ -236,7 +246,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
}
|
||||
}
|
||||
if(!found) {
|
||||
ThumbCell newCell = new ThumbCell(this, model, recorder, client);
|
||||
ThumbCell newCell = createThumbCell(this, model, recorder, client);
|
||||
newCell.setIndex(index);
|
||||
positionChangedOrNew.add(newCell);
|
||||
}
|
||||
|
@ -260,6 +270,126 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
|
||||
}
|
||||
|
||||
private ThumbCell createThumbCell(ThumbOverviewTab thumbOverviewTab, Model model, Recorder recorder2, HttpClient client2) {
|
||||
ThumbCell newCell = new ThumbCell(this, model, recorder, client);
|
||||
newCell.addEventHandler(ContextMenuEvent.CONTEXT_MENU_REQUESTED, event -> {
|
||||
suspendUpdates(true);
|
||||
popup = createContextMenu(newCell);
|
||||
popup.show(newCell, event.getScreenX(), event.getScreenY());
|
||||
popup.setOnHidden((e) -> suspendUpdates(false));
|
||||
event.consume();
|
||||
});
|
||||
newCell.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
|
||||
if(popup != null) {
|
||||
popup.hide();
|
||||
popup = null;
|
||||
return;
|
||||
}
|
||||
});
|
||||
newCell.selectionProperty().addListener((obs, oldValue, newValue) -> {
|
||||
if(newValue) {
|
||||
selectedThumbCells.add(newCell);
|
||||
} else {
|
||||
selectedThumbCells.remove(newCell);
|
||||
}
|
||||
});
|
||||
newCell.setOnMouseClicked(mouseClickListener);
|
||||
return newCell;
|
||||
}
|
||||
|
||||
private ContextMenu createContextMenu(ThumbCell cell) {
|
||||
MenuItem openInPlayer = new MenuItem("Open in Player");
|
||||
openInPlayer.setOnAction((e) -> startPlayer(cell));
|
||||
|
||||
MenuItem start = new MenuItem("Start Recording");
|
||||
start.setOnAction((e) -> startStopAction(cell, true));
|
||||
MenuItem stop = new MenuItem("Stop Recording");
|
||||
stop.setOnAction((e) -> startStopAction(cell, false));
|
||||
MenuItem startStop = recorder.isRecording(cell.getModel()) ? stop : start;
|
||||
|
||||
MenuItem follow = new MenuItem("Follow");
|
||||
follow.setOnAction((e) -> follow(cell, true));
|
||||
MenuItem unfollow = new MenuItem("Unfollow");
|
||||
unfollow.setOnAction((e) -> follow(cell, false));
|
||||
|
||||
MenuItem copyUrl = new MenuItem("Copy URL");
|
||||
copyUrl.setOnAction((e) -> {
|
||||
final Clipboard clipboard = Clipboard.getSystemClipboard();
|
||||
final ClipboardContent content = new ClipboardContent();
|
||||
content.putString(cell.getModel().getUrl());
|
||||
clipboard.setContent(content);
|
||||
});
|
||||
|
||||
// check, if other cells are selected, too. in that case, we have to disable menu item, which make sense only for
|
||||
// single selections. but only do that, if the popup has been triggered on a selected cell. otherwise remove the
|
||||
// selection and show the normal menu
|
||||
if (selectedThumbCells.size() > 1 || selectedThumbCells.size() == 1 && selectedThumbCells.get(0) != cell) {
|
||||
if(cell.isSelected()) {
|
||||
if(Config.getInstance().getSettings().singlePlayer) {
|
||||
openInPlayer.setDisable(true);
|
||||
}
|
||||
copyUrl.setDisable(true);
|
||||
} else {
|
||||
removeSelection();
|
||||
}
|
||||
}
|
||||
|
||||
ContextMenu contextMenu = new ContextMenu();
|
||||
contextMenu.setAutoHide(true);
|
||||
contextMenu.setHideOnEscape(true);
|
||||
contextMenu.setAutoFix(true);
|
||||
MenuItem followOrUnFollow = this instanceof FollowedTab ? unfollow : follow;
|
||||
contextMenu.getItems().addAll(openInPlayer, startStop , followOrUnFollow, copyUrl);
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
private void follow(ThumbCell cell, boolean follow) {
|
||||
if(selectedThumbCells.isEmpty()) {
|
||||
cell.follow(follow);
|
||||
} else {
|
||||
for (ThumbCell thumbCell : selectedThumbCells) {
|
||||
thumbCell.follow(follow);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startStopAction(ThumbCell cell, boolean start) {
|
||||
if(selectedThumbCells.isEmpty()) {
|
||||
cell.startStopAction(start);
|
||||
} else {
|
||||
for (ThumbCell thumbCell : selectedThumbCells) {
|
||||
thumbCell.startStopAction(start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void startPlayer(ThumbCell cell) {
|
||||
if(selectedThumbCells.isEmpty()) {
|
||||
cell.startPlayer();
|
||||
} else {
|
||||
for (ThumbCell thumbCell : selectedThumbCells) {
|
||||
thumbCell.startPlayer();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private EventHandler<MouseEvent> mouseClickListener = new EventHandler<MouseEvent>() {
|
||||
@Override
|
||||
public void handle(MouseEvent e) {
|
||||
ThumbCell cell = (ThumbCell) e.getSource();
|
||||
if (e.getButton() == MouseButton.PRIMARY && e.getClickCount() == 2) {
|
||||
cell.setSelected(false);
|
||||
cell.startPlayer();
|
||||
} else if (e.getButton() == MouseButton.PRIMARY && e.isShiftDown()) {
|
||||
if(popup == null) {
|
||||
cell.setSelected(!cell.isSelected());
|
||||
}
|
||||
} else if (e.getButton() == MouseButton.PRIMARY) {
|
||||
removeSelection();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
protected void onFail(WorkerStateEvent event) {
|
||||
if(updatesSuspended) {
|
||||
return;
|
||||
|
@ -309,6 +439,7 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
if(!matches(m, filter)) {
|
||||
iterator.remove();
|
||||
filteredThumbCells.add(cell);
|
||||
cell.setSelected(false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -439,4 +570,10 @@ public class ThumbOverviewTab extends Tab implements TabSelectionListener {
|
|||
void suspendUpdates(boolean suspend) {
|
||||
this.updatesSuspended = suspend;
|
||||
}
|
||||
|
||||
private void removeSelection() {
|
||||
while(selectedThumbCells.size() > 0) {
|
||||
selectedThumbCells.get(0).setSelected(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue