mirror of
https://github.com/xpipe-io/xpipe.git
synced 2026-09-29 11:55:36 +00:00
Rework context menus
This commit is contained in:
@@ -15,6 +15,7 @@ import io.xpipe.app.platform.PlatformThread;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
|
||||
import io.xpipe.app.util.ContextMenuWrapper;
|
||||
import javafx.application.Platform;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
@@ -65,14 +66,19 @@ public class BrowserSessionTabsComp extends SimpleRegionBuilder {
|
||||
}
|
||||
|
||||
if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN).match(keyEvent)) {
|
||||
tabs.getTabs().remove(current);
|
||||
if (current.isClosable()) {
|
||||
tabs.getTabs().remove(current);
|
||||
}
|
||||
keyEvent.consume();
|
||||
return;
|
||||
}
|
||||
|
||||
if (new KeyCodeCombination(KeyCode.W, KeyCombination.SHORTCUT_DOWN, KeyCombination.SHIFT_DOWN)
|
||||
.match(keyEvent)) {
|
||||
tabs.getTabs().clear();
|
||||
tabs.getTabs()
|
||||
.removeAll(tabs.getTabs().stream()
|
||||
.filter(t -> t.isClosable())
|
||||
.toList());
|
||||
keyEvent.consume();
|
||||
}
|
||||
|
||||
@@ -298,7 +304,7 @@ public class BrowserSessionTabsComp extends SimpleRegionBuilder {
|
||||
}
|
||||
|
||||
private ContextMenu createContextMenu(TabPane tabs, Tab tab, BrowserSessionTab tabModel) {
|
||||
var cm = MenuHelper.createContextMenu();
|
||||
var cm = new ContextMenu();
|
||||
|
||||
if (tabModel.isCloseable()) {
|
||||
var unpin = MenuHelper.createMenuItem(LabelGraphic.none(), "unpinTab");
|
||||
@@ -408,9 +414,6 @@ public class BrowserSessionTabsComp extends SimpleRegionBuilder {
|
||||
|
||||
private Tab createTab(TabPane tabs, BrowserSessionTab tabModel) {
|
||||
var tab = new Tab();
|
||||
if (tabModel.isCloseable()) {
|
||||
tab.setContextMenu(createContextMenu(tabs, tab, tabModel));
|
||||
}
|
||||
|
||||
tab.setClosable(tabModel.isCloseable());
|
||||
// Prevent closing while busy
|
||||
@@ -514,6 +517,7 @@ public class BrowserSessionTabsComp extends SimpleRegionBuilder {
|
||||
if (color != null) {
|
||||
c.getStyleClass().add(color.getId());
|
||||
}
|
||||
|
||||
c.addEventHandler(DragEvent.DRAG_ENTERED, de -> {
|
||||
// Prevent switch when dragging local files into app
|
||||
if (tabModel.isCloseable() && !de.getDragboard().hasContent(DataFormat.FILES)) {
|
||||
@@ -521,6 +525,11 @@ public class BrowserSessionTabsComp extends SimpleRegionBuilder {
|
||||
de.consume();
|
||||
}
|
||||
});
|
||||
|
||||
if (tabModel.isCloseable()) {
|
||||
var cm = new ContextMenuWrapper(() -> createContextMenu(tabs, tab, tabModel));
|
||||
cm.installOnMouseClick(c, mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
+6
-1
@@ -4,6 +4,7 @@ import io.xpipe.app.browser.action.BrowserAction;
|
||||
import io.xpipe.app.browser.action.BrowserActionProvider;
|
||||
import io.xpipe.app.browser.file.BrowserEntry;
|
||||
import io.xpipe.app.browser.file.BrowserFileSystemTabModel;
|
||||
import io.xpipe.app.fs.FileKind;
|
||||
import io.xpipe.app.process.ShellControl;
|
||||
import io.xpipe.app.util.DesktopHelper;
|
||||
|
||||
@@ -42,7 +43,11 @@ public class BrowseInNativeManagerActionProvider implements BrowserActionProvide
|
||||
for (BrowserEntry entry : getEntries()) {
|
||||
var e = entry.getRawFileEntry().getPath();
|
||||
var localFile = sc.getLocalSystemAccess().translateToLocalSystemPath(e);
|
||||
DesktopHelper.browseFileInDirectory(localFile.asLocalPath());
|
||||
if (entry.getRawFileEntry().getKind() == FileKind.DIRECTORY) {
|
||||
DesktopHelper.browseFile(localFile.asLocalPath());
|
||||
} else {
|
||||
DesktopHelper.browseFileInDirectory(localFile.asLocalPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,10 +4,7 @@ import io.xpipe.app.browser.BrowserFullSessionModel;
|
||||
import io.xpipe.app.core.AppSystemInfo;
|
||||
import io.xpipe.app.fs.FileKind;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.GlobalTimer;
|
||||
import io.xpipe.app.util.OsType;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
import io.xpipe.app.util.*;
|
||||
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
@@ -15,6 +12,7 @@ import javafx.scene.control.TableView;
|
||||
import javafx.scene.image.Image;
|
||||
import javafx.scene.input.*;
|
||||
|
||||
import javafx.scene.layout.Region;
|
||||
import lombok.Getter;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -28,31 +26,25 @@ import java.util.Objects;
|
||||
public class BrowserFileListCompEntry {
|
||||
|
||||
private final TableView<BrowserEntry> tv;
|
||||
private final Node row;
|
||||
private final Region row;
|
||||
private final BrowserEntry item;
|
||||
private final BrowserFileListModel model;
|
||||
private final ContextMenuWrapper contextMenu;
|
||||
|
||||
private Instant lastHoverUpdate;
|
||||
private ContextMenu lastContextMenu;
|
||||
|
||||
public BrowserFileListCompEntry(
|
||||
TableView<BrowserEntry> tv, Node row, BrowserEntry item, BrowserFileListModel model) {
|
||||
TableView<BrowserEntry> tv, Region row, BrowserEntry item, BrowserFileListModel model) {
|
||||
this.tv = tv;
|
||||
this.row = row;
|
||||
this.item = item;
|
||||
this.model = model;
|
||||
this.contextMenu = new ContextMenuWrapper(() -> new BrowserContextMenu(model.getFileSystemModel(), item, false));
|
||||
}
|
||||
|
||||
public void onMouseClick(MouseEvent t) {
|
||||
if (lastContextMenu != null) {
|
||||
lastContextMenu.hide();
|
||||
lastContextMenu = null;
|
||||
}
|
||||
|
||||
if (showContextMenu(t)) {
|
||||
var cm = new BrowserContextMenu(model.getFileSystemModel(), item, false);
|
||||
cm.show(row, t.getScreenX(), t.getScreenY());
|
||||
lastContextMenu = cm;
|
||||
contextMenu.show(row, t.getScreenX(), t.getScreenY());
|
||||
t.consume();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import io.xpipe.app.core.AppSizeBreakpoints;
|
||||
import io.xpipe.app.platform.InputHelper;
|
||||
import io.xpipe.app.platform.MenuHelper;
|
||||
import io.xpipe.app.platform.PlatformThread;
|
||||
import io.xpipe.app.util.ContextMenuWrapper;
|
||||
import io.xpipe.app.util.FilePath;
|
||||
import io.xpipe.app.util.GlobalTimer;
|
||||
|
||||
@@ -75,11 +76,8 @@ public class BrowserFileSystemTabComp extends SimpleRegionBuilder {
|
||||
|
||||
var menuButton = MenuHelper.createMenuButton();
|
||||
menuButton.setGraphic(new FontIcon("mdral-folder_open"));
|
||||
new ContextMenuAugment<>(
|
||||
event -> event.getButton() == MouseButton.PRIMARY,
|
||||
null,
|
||||
() -> new BrowserContextMenu(model, null, false))
|
||||
.accept(menuButton);
|
||||
var cm = new ContextMenuWrapper(() -> new BrowserContextMenu(model, null, false));
|
||||
cm.installOnMouseClick(menuButton, mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, false);
|
||||
menuButton.disableProperty().bind(PlatformThread.sync(model.getInOverview()));
|
||||
RegionDescriptor.builder().nameKey("directoryOptions").build().apply(menuButton);
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ import io.xpipe.app.comp.RegionBuilder;
|
||||
import io.xpipe.app.comp.RegionStructure;
|
||||
import io.xpipe.app.comp.RegionStructureBuilder;
|
||||
import io.xpipe.app.comp.base.ButtonComp;
|
||||
import io.xpipe.app.comp.base.ContextMenuAugment;
|
||||
import io.xpipe.app.comp.base.PrettyImageHelper;
|
||||
import io.xpipe.app.comp.base.TextFieldComp;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
@@ -14,6 +13,7 @@ import io.xpipe.app.platform.LabelGraphic;
|
||||
import io.xpipe.app.platform.MenuHelper;
|
||||
import io.xpipe.app.platform.PlatformThread;
|
||||
import io.xpipe.app.util.BooleanScope;
|
||||
import io.xpipe.app.util.ContextMenuWrapper;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
import javafx.application.Platform;
|
||||
@@ -63,9 +63,11 @@ public class BrowserNavBarComp extends RegionStructureBuilder<HBox, BrowserNavBa
|
||||
|
||||
var homeButton = new ButtonComp(null, breadcrumbsGraphic, null)
|
||||
.describe(d -> d.nameKey("directoryOptions"))
|
||||
.apply(new ContextMenuAugment<>(event -> event.getButton() == MouseButton.PRIMARY, null, () -> {
|
||||
return model.getInOverview().get() ? null : new BrowserContextMenu(model, null, false);
|
||||
}))
|
||||
.apply(struc -> {
|
||||
var cm = new ContextMenuWrapper(
|
||||
() -> model.getInOverview().get() ? null : new BrowserContextMenu(model, null, false));
|
||||
cm.installOnButton(struc);
|
||||
})
|
||||
.build();
|
||||
homeButton.getStyleClass().add(Styles.LEFT_PILL);
|
||||
homeButton.getStyleClass().add("path-graphic-button");
|
||||
@@ -75,8 +77,10 @@ public class BrowserNavBarComp extends RegionStructureBuilder<HBox, BrowserNavBa
|
||||
.describe(
|
||||
d -> d.nameKey("history").shortcut(new KeyCodeCombination(KeyCode.H, KeyCombination.ALT_DOWN)))
|
||||
.style(Styles.RIGHT_PILL)
|
||||
.apply(new ContextMenuAugment<>(
|
||||
event -> event.getButton() == MouseButton.PRIMARY, null, this::createContextMenu))
|
||||
.apply(struc -> {
|
||||
var cm = new ContextMenuWrapper(() -> createContextMenu());
|
||||
cm.installOnMouseClick(struc, event -> event.getButton() == MouseButton.PRIMARY, false);
|
||||
})
|
||||
.build();
|
||||
AppFontSizes.xs(historyButton);
|
||||
|
||||
@@ -212,7 +216,7 @@ public class BrowserNavBarComp extends RegionStructureBuilder<HBox, BrowserNavBa
|
||||
}
|
||||
|
||||
private ContextMenu createContextMenu() {
|
||||
var cm = MenuHelper.createContextMenu();
|
||||
var cm = new ContextMenu();
|
||||
|
||||
var f = model.getHistory().getForwardHistory(8).stream().toList();
|
||||
for (int i = f.size() - 1; i >= 0; i--) {
|
||||
|
||||
@@ -3,13 +3,13 @@ package io.xpipe.app.browser.file;
|
||||
import io.xpipe.app.comp.BaseRegionBuilder;
|
||||
import io.xpipe.app.comp.RegionBuilder;
|
||||
import io.xpipe.app.comp.SimpleRegionBuilder;
|
||||
import io.xpipe.app.comp.base.ContextMenuAugment;
|
||||
import io.xpipe.app.comp.base.HorizontalComp;
|
||||
import io.xpipe.app.comp.base.IconButtonComp;
|
||||
import io.xpipe.app.comp.base.LabelComp;
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.platform.BindingsHelper;
|
||||
import io.xpipe.app.platform.PlatformThread;
|
||||
import io.xpipe.app.util.ContextMenuWrapper;
|
||||
import io.xpipe.app.util.HumanReadableFormat;
|
||||
import io.xpipe.app.util.ThreadHelper;
|
||||
|
||||
@@ -236,12 +236,5 @@ public class BrowserStatusBarComp extends SimpleRegionBuilder {
|
||||
r.setOnDragDone(event -> {
|
||||
emptyEntry.onDragDone(event);
|
||||
});
|
||||
|
||||
// Use status bar as an extension of file list
|
||||
new ContextMenuAugment<>(
|
||||
mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY,
|
||||
null,
|
||||
() -> new BrowserContextMenu(model, null, false))
|
||||
.accept(r);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,103 +0,0 @@
|
||||
package io.xpipe.app.comp.base;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.control.ButtonBase;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ContextMenuAugment<S extends Region> implements Consumer<S> {
|
||||
|
||||
private final Predicate<MouseEvent> mouseEventCheck;
|
||||
private final Predicate<KeyEvent> keyEventCheck;
|
||||
private final Supplier<ContextMenu> contextMenu;
|
||||
|
||||
public ContextMenuAugment(
|
||||
Predicate<MouseEvent> mouseEventCheck,
|
||||
Predicate<KeyEvent> keyEventCheck,
|
||||
Supplier<ContextMenu> contextMenu) {
|
||||
this.mouseEventCheck = mouseEventCheck;
|
||||
this.keyEventCheck = keyEventCheck;
|
||||
this.contextMenu = contextMenu;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accept(S struc) {
|
||||
var currentContextMenu = new AtomicReference<ContextMenu>();
|
||||
|
||||
Supplier<Boolean> hide = () -> {
|
||||
if (currentContextMenu.get() != null && currentContextMenu.get().isShowing()) {
|
||||
currentContextMenu.get().hide();
|
||||
currentContextMenu.set(null);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
var r = struc;
|
||||
r.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
||||
var hidden = hide.get();
|
||||
if (mouseEventCheck != null && mouseEventCheck.test(event)) {
|
||||
if (!hidden) {
|
||||
var cm = contextMenu.get();
|
||||
if (cm != null) {
|
||||
cm.show(r, event.getScreenX(), event.getScreenY());
|
||||
currentContextMenu.set(cm);
|
||||
}
|
||||
}
|
||||
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
r.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
|
||||
if (mouseEventCheck != null && mouseEventCheck.test(event)) {
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
|
||||
r.addEventHandler(KeyEvent.KEY_RELEASED, event -> {
|
||||
if (keyEventCheck != null && keyEventCheck.test(event)) {
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
r.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
|
||||
if (keyEventCheck != null) {
|
||||
var hidden = hide.get();
|
||||
if (keyEventCheck.test(event)) {
|
||||
if (!hidden) {
|
||||
var cm = contextMenu.get();
|
||||
if (cm != null) {
|
||||
cm.show(r, Side.BOTTOM, 0, 0);
|
||||
currentContextMenu.set(cm);
|
||||
}
|
||||
}
|
||||
event.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
if (r instanceof ButtonBase buttonBase && keyEventCheck == null) {
|
||||
buttonBase.addEventHandler(ActionEvent.ACTION, event -> {
|
||||
if (buttonBase.getOnAction() != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!hide.get()) {
|
||||
var cm = contextMenu.get();
|
||||
if (cm != null) {
|
||||
cm.show(r, Side.TOP, 0, 0);
|
||||
currentContextMenu.set(cm);
|
||||
}
|
||||
}
|
||||
event.consume();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ import io.xpipe.app.platform.*;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import io.xpipe.app.storage.DataStorage;
|
||||
import io.xpipe.app.storage.DataStoreColor;
|
||||
import io.xpipe.app.util.ContextMenuWrapper;
|
||||
import io.xpipe.app.util.DesktopHelper;
|
||||
import io.xpipe.app.util.OsType;
|
||||
|
||||
@@ -68,7 +69,9 @@ public class StoreCategoryComp extends SimpleRegionBuilder {
|
||||
});
|
||||
})
|
||||
.build();
|
||||
var showing = new SimpleBooleanProperty();
|
||||
|
||||
var contextMenu = new ContextMenuWrapper(() -> createContextMenu());
|
||||
var contextMenuShowing = new SimpleBooleanProperty();
|
||||
|
||||
var expandIcon = Bindings.createObjectBinding(
|
||||
() -> {
|
||||
@@ -123,12 +126,9 @@ public class StoreCategoryComp extends SimpleRegionBuilder {
|
||||
struc.setAlignment(Pos.CENTER);
|
||||
struc.setPadding(new Insets(0, 0, 0, 0));
|
||||
})
|
||||
.apply(new ContextMenuAugment<>(
|
||||
mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, null, () -> {
|
||||
var cm = createContextMenu();
|
||||
showing.bind(cm.showingProperty());
|
||||
return cm;
|
||||
}))
|
||||
.apply(struc -> {
|
||||
contextMenu.installOnButton(struc);
|
||||
})
|
||||
.describe(d -> d.nameKey("configuration"))
|
||||
.style("status-button");
|
||||
|
||||
@@ -156,7 +156,7 @@ public class StoreCategoryComp extends SimpleRegionBuilder {
|
||||
var dragIntoIndicator = createDragIntoIndicator(dragOver);
|
||||
|
||||
var showStatus = hover.or(new SimpleBooleanProperty(DataStorage.get().syncEnabled()))
|
||||
.or(showing)
|
||||
.or(contextMenuShowing)
|
||||
.or(focus);
|
||||
var indentSpacer = RegionBuilder.hspacer(Bindings.createDoubleBinding(() -> {
|
||||
return category.getDepth().getValue() * 8.0;
|
||||
@@ -200,12 +200,11 @@ public class StoreCategoryComp extends SimpleRegionBuilder {
|
||||
});
|
||||
});
|
||||
|
||||
h.apply(new ContextMenuAugment<>(
|
||||
mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY,
|
||||
keyEvent -> keyEvent.getCode() == KeyCode.SPACE,
|
||||
() -> createContextMenu()));
|
||||
h.apply(struc -> {
|
||||
struc.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||
contextMenu.installOnMouseClick(struc, mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, true);
|
||||
});
|
||||
h.apply(struc -> {
|
||||
struc.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
|
||||
if (event.getCode() == KeyCode.SPACE) {
|
||||
category.toggleExpanded();
|
||||
event.consume();
|
||||
@@ -452,7 +451,7 @@ public class StoreCategoryComp extends SimpleRegionBuilder {
|
||||
}
|
||||
|
||||
private ContextMenu createContextMenu() {
|
||||
var contextMenu = MenuHelper.createContextMenu();
|
||||
var contextMenu = new ContextMenu();
|
||||
|
||||
if (AppPrefs.get().enableHttpApi().get()) {
|
||||
var copyId = new MenuItem(AppI18n.get("copyId"), new FontIcon("mdi2c-content-copy"));
|
||||
|
||||
@@ -82,7 +82,7 @@ public class DenseStoreEntryComp extends StoreEntryComp {
|
||||
? contentRegion
|
||||
: RegionBuilder.empty().hide(new ReadOnlyBooleanWrapper(true)).build();
|
||||
cr.getStyleClass().add("custom-content");
|
||||
var bb = createButtonBar(name);
|
||||
var bb = createButtonBar();
|
||||
var controls = new HBox(cr, bb);
|
||||
controls.setFillHeight(true);
|
||||
HBox.setHgrow(cr, Priority.ALWAYS);
|
||||
|
||||
@@ -99,7 +99,7 @@ public class StandardStoreEntryComp extends StoreEntryComp {
|
||||
? contentRegion
|
||||
: RegionBuilder.empty().hide(new ReadOnlyBooleanWrapper(true)).build();
|
||||
cr.getStyleClass().add("custom-content");
|
||||
var bb = createButtonBar(name);
|
||||
var bb = createButtonBar();
|
||||
var controls = new HBox(cr, bb);
|
||||
controls.setFillHeight(true);
|
||||
HBox.setHgrow(cr, Priority.ALWAYS);
|
||||
|
||||
@@ -47,21 +47,30 @@ public interface StoreEntryBadge {
|
||||
}
|
||||
|
||||
static Action providerMenu(String... ids) {
|
||||
var cm = new AtomicReference<ContextMenu>();
|
||||
return new Action() {
|
||||
@Override
|
||||
public void run(StoreEntryWrapper wrapper, Button button) {
|
||||
if (cm.get() == null) {
|
||||
cm.set(MenuHelper.createContextMenu());
|
||||
|
||||
private ContextMenu contextMenu;
|
||||
|
||||
private void buildContextMenu(StoreEntryWrapper wrapper) {
|
||||
if (contextMenu == null) {
|
||||
contextMenu = new ContextMenu();
|
||||
}
|
||||
|
||||
contextMenu.getItems().clear();
|
||||
var provs = getProviders(wrapper);
|
||||
cm.get().getItems().clear();
|
||||
for (var p : provs) {
|
||||
var item = StoreEntryComp.buildMenuItemForAction(wrapper, p);
|
||||
cm.get().getItems().add(item);
|
||||
contextMenu.getItems().add(item);
|
||||
}
|
||||
MenuHelper.show(cm.get(), button, Side.BOTTOM);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(StoreEntryWrapper wrapper, Button button) {
|
||||
var cm = new ContextMenuWrapper(() -> {
|
||||
buildContextMenu(wrapper);
|
||||
return contextMenu;
|
||||
});
|
||||
cm.show(button, Side.BOTTOM);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -189,58 +198,71 @@ public interface StoreEntryBadge {
|
||||
return null;
|
||||
}
|
||||
|
||||
var cm = new AtomicReference<ContextMenu>();
|
||||
var busy = new SimpleBooleanProperty();
|
||||
return of("mdi2s-server-network-outline", effective).withAction((wrapper, b) -> {
|
||||
b.opacityProperty()
|
||||
.bind(PlatformThread.sync(Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
return busy.get() ? 0.5 : 1.0;
|
||||
},
|
||||
busy)));
|
||||
return of("mdi2s-server-network-outline", effective).withAction(new Action() {
|
||||
|
||||
if (wrapper.getEntry().getStore() instanceof HostAddressStore has) {
|
||||
if (busy.get()) {
|
||||
return;
|
||||
private ContextMenu contextMenu;
|
||||
|
||||
private void buildContextMenu(HostAddress has) {
|
||||
if (contextMenu == null) {
|
||||
contextMenu = new ContextMenu();
|
||||
}
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
has.refreshHostAddressOrThrow();
|
||||
contextMenu.getItems().clear();
|
||||
for (var a : has.getAvailable()) {
|
||||
var i = new MenuItem();
|
||||
i.setText(a);
|
||||
i.setGraphic(new FontIcon("mdi2c-clipboard-multiple-outline"));
|
||||
i.setOnAction(event -> {
|
||||
ClipboardHelper.copyText(a);
|
||||
event.consume();
|
||||
});
|
||||
|
||||
var refreshed = has.getHostAddress();
|
||||
if (refreshed == null || refreshed.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (refreshed.isSingle()) {
|
||||
ClipboardHelper.copyText(refreshed.get());
|
||||
return;
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
if (cm.get() == null) {
|
||||
cm.set(MenuHelper.createContextMenu());
|
||||
}
|
||||
|
||||
cm.get().getItems().clear();
|
||||
for (var a : refreshed.getAvailable()) {
|
||||
var i = new MenuItem();
|
||||
i.setText(a);
|
||||
i.setGraphic(new FontIcon("mdi2c-clipboard-multiple-outline"));
|
||||
i.setOnAction(event -> {
|
||||
ClipboardHelper.copyText(a);
|
||||
event.consume();
|
||||
});
|
||||
cm.get().getItems().add(i);
|
||||
}
|
||||
MenuHelper.show(cm.get(), b, Side.BOTTOM);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
ClipboardHelper.copyText(effective);
|
||||
contextMenu.getItems().add(i);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run(StoreEntryWrapper wrapper, Button b) {
|
||||
b.opacityProperty()
|
||||
.bind(PlatformThread.sync(Bindings.createDoubleBinding(
|
||||
() -> {
|
||||
return busy.get() ? 0.5 : 1.0;
|
||||
},
|
||||
busy)));
|
||||
|
||||
if (wrapper.getEntry().getStore() instanceof HostAddressStore has) {
|
||||
if (busy.get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
ThreadHelper.runFailableAsync(() -> {
|
||||
BooleanScope.executeExclusive(busy, () -> {
|
||||
has.refreshHostAddressOrThrow();
|
||||
});
|
||||
|
||||
var refreshed = has.getHostAddress();
|
||||
if (refreshed == null || refreshed.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (refreshed.isSingle()) {
|
||||
ClipboardHelper.copyText(refreshed.get());
|
||||
return;
|
||||
}
|
||||
|
||||
Platform.runLater(() -> {
|
||||
var cm = new ContextMenuWrapper(() -> {
|
||||
buildContextMenu(addr);
|
||||
return contextMenu;
|
||||
});
|
||||
cm.show(b, Side.BOTTOM);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
ClipboardHelper.copyText(effective);
|
||||
}
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -39,7 +39,6 @@ import atlantafx.base.layout.InputGroup;
|
||||
import atlantafx.base.theme.Styles;
|
||||
import org.kordamp.ikonli.javafx.FontIcon;
|
||||
|
||||
import java.lang.ref.WeakReference;
|
||||
import java.nio.file.Files;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
@@ -63,10 +62,11 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
|
||||
}
|
||||
},
|
||||
App.getApp().getStage().widthProperty());
|
||||
|
||||
private static String DEFAULT_NOTES = null;
|
||||
protected final StoreSection section;
|
||||
protected final BaseRegionBuilder<?, ?> content;
|
||||
protected final IntegerProperty contextMenuCount = new SimpleIntegerProperty();
|
||||
protected final ContextMenuWrapper contextMenu = new ContextMenuWrapper(() -> createContextMenu());
|
||||
|
||||
public StoreEntryComp(StoreSection section, BaseRegionBuilder<?, ?> content) {
|
||||
this.section = section;
|
||||
@@ -137,10 +137,6 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getWrapper().getRenaming().get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
var count = AppPrefs.get().requireDoubleClickForConnections().get() ? 2 : 1;
|
||||
if (mouseEvent.getClickCount() != count) {
|
||||
return;
|
||||
@@ -157,11 +153,7 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
|
||||
mouseEvent.consume();
|
||||
});
|
||||
|
||||
new ContextMenuAugment<>(
|
||||
mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY,
|
||||
null,
|
||||
() -> this.createContextMenu(name))
|
||||
.accept(r);
|
||||
contextMenu.installOnMouseClick(r, mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, true);
|
||||
|
||||
var loading =
|
||||
new LoadingOverlayComp(RegionBuilder.of(() -> r), getWrapper().getEffectiveBusy(), false);
|
||||
@@ -224,7 +216,10 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
|
||||
var name = new LazyTextFieldComp(prop);
|
||||
name.style("name");
|
||||
name.applyStructure(struc -> {
|
||||
getWrapper().getRenaming().bind(struc.getTextField().focusedProperty());
|
||||
getWrapper().getRenameTrigger().onFire(() -> {
|
||||
struc.get().requestFocus();
|
||||
struc.getTextField().selectAll();
|
||||
});
|
||||
});
|
||||
return name;
|
||||
}
|
||||
@@ -337,7 +332,7 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
|
||||
return stack;
|
||||
}
|
||||
|
||||
protected Region createButtonBar(Region name) {
|
||||
protected Region createButtonBar() {
|
||||
var list = DerivedObservableList.wrap(getWrapper().getMajorActionProviders(), false);
|
||||
var buttons = list.mapped(actionProvider -> {
|
||||
var button = buildButton(actionProvider);
|
||||
@@ -349,7 +344,7 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
|
||||
var ig = new InputGroup();
|
||||
Runnable update = () -> {
|
||||
var l = new ArrayList<Node>(buttons);
|
||||
var settingsButton = createSettingsButton(name).build();
|
||||
var settingsButton = createSettingsButton().build();
|
||||
l.add(settingsButton);
|
||||
l.forEach(o -> o.getStyleClass().remove(Styles.FLAT));
|
||||
ig.getChildren().setAll(l);
|
||||
@@ -373,56 +368,55 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
|
||||
}
|
||||
: null);
|
||||
if (branch != null) {
|
||||
button.apply(new ContextMenuAugment<>(
|
||||
mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, keyEvent -> false, () -> {
|
||||
var cm = MenuHelper.createContextMenu();
|
||||
var children = branch
|
||||
.getChildren(getWrapper().getEntry().ref())
|
||||
.stream()
|
||||
.filter(hubMenuItemProvider -> {
|
||||
return hubMenuItemProvider.isApplicable(
|
||||
getWrapper().getEntry().ref());
|
||||
})
|
||||
.toList();
|
||||
var cats = Arrays.stream(StoreActionCategory.values())
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
cats.addFirst(null);
|
||||
for (var cat : cats) {
|
||||
var catChildren = children.stream()
|
||||
.filter(actionProvider -> actionProvider.getCategory() == cat)
|
||||
.toList();
|
||||
if (catChildren.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
var buttonCm = new ContextMenuWrapper(() -> {
|
||||
var cm = new ContextMenu();
|
||||
var children = branch
|
||||
.getChildren(getWrapper().getEntry().ref())
|
||||
.stream()
|
||||
.filter(hubMenuItemProvider -> {
|
||||
return hubMenuItemProvider.isApplicable(
|
||||
getWrapper().getEntry().ref());
|
||||
})
|
||||
.toList();
|
||||
var cats = Arrays.stream(StoreActionCategory.values())
|
||||
.collect(Collectors.toCollection(ArrayList::new));
|
||||
cats.addFirst(null);
|
||||
for (var cat : cats) {
|
||||
var catChildren = children.stream()
|
||||
.filter(actionProvider -> actionProvider.getCategory() == cat)
|
||||
.toList();
|
||||
if (catChildren.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
catChildren.forEach(childProvider -> {
|
||||
var menu = buildMenuItemForAction(getWrapper(), childProvider);
|
||||
if (menu != null) {
|
||||
cm.getItems().add(menu);
|
||||
}
|
||||
});
|
||||
cm.getItems().add(new SeparatorMenuItem());
|
||||
catChildren.forEach(childProvider -> {
|
||||
var menu = buildMenuItemForAction(getWrapper(), childProvider);
|
||||
if (menu != null) {
|
||||
cm.getItems().add(menu);
|
||||
}
|
||||
});
|
||||
cm.getItems().add(new SeparatorMenuItem());
|
||||
}
|
||||
|
||||
if (cm.getItems().getLast() instanceof SeparatorMenuItem) {
|
||||
cm.getItems().removeLast();
|
||||
}
|
||||
if (cm.getItems().getLast() instanceof SeparatorMenuItem) {
|
||||
cm.getItems().removeLast();
|
||||
}
|
||||
|
||||
return cm;
|
||||
}));
|
||||
return cm;
|
||||
});
|
||||
button.apply(struc -> buttonCm.installOnButton(struc));
|
||||
}
|
||||
button.describe(d -> d.name(p.getName(getWrapper().getEntry().ref())));
|
||||
return button;
|
||||
}
|
||||
|
||||
protected BaseRegionBuilder<?, ?> createSettingsButton(Region name) {
|
||||
protected BaseRegionBuilder<?, ?> createSettingsButton() {
|
||||
var settingsButton = new IconButtonComp("mdi2d-dots-horizontal-circle-outline", null);
|
||||
settingsButton.style("settings");
|
||||
settingsButton.describe(d -> d.nameKey("more"));
|
||||
settingsButton.apply(new ContextMenuAugment<>(
|
||||
event -> event.getButton() == MouseButton.PRIMARY,
|
||||
null,
|
||||
() -> StoreEntryComp.this.createContextMenu(name)));
|
||||
settingsButton.apply(struc -> {
|
||||
contextMenu.installOnButton(struc);
|
||||
});
|
||||
return settingsButton;
|
||||
}
|
||||
|
||||
@@ -432,20 +426,8 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
|
||||
return c;
|
||||
}
|
||||
|
||||
private void handleContextMenuCount(ContextMenu contextMenu) {
|
||||
var ref = new WeakReference<>(contextMenu);
|
||||
contextMenuCount.set(contextMenuCount.get() + 1);
|
||||
contextMenuCount.addListener((observable, oldValue, newValue) -> {
|
||||
var cm = ref.get();
|
||||
if (cm != null) {
|
||||
cm.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected ContextMenu createContextMenu(Region name) {
|
||||
var contextMenu = MenuHelper.createContextMenu();
|
||||
handleContextMenuCount(contextMenu);
|
||||
protected ContextMenu createContextMenu() {
|
||||
var contextMenu = new ContextMenu();
|
||||
|
||||
var cats = Arrays.stream(StoreActionCategory.values()).collect(Collectors.toCollection(ArrayList::new));
|
||||
cats.addFirst(null);
|
||||
@@ -470,7 +452,7 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
|
||||
case OsType.Windows ignored -> new KeyCodeCombination(KeyCode.F2);
|
||||
});
|
||||
rename.setOnAction(event -> {
|
||||
name.requestFocus();
|
||||
getWrapper().getRenameTrigger().fire(null);
|
||||
event.consume();
|
||||
});
|
||||
items.add(items.size(), rename);
|
||||
@@ -589,7 +571,7 @@ public abstract class StoreEntryComp extends SimpleRegionBuilder {
|
||||
items.add(tags);
|
||||
}
|
||||
|
||||
if (getWrapper().canBreakOutCategory()) {
|
||||
if (getWrapper().canToggleBreakOutCategory()) {
|
||||
var breakOut = new MenuItem();
|
||||
var is = getWrapper().getBreakoutCategory().isPresent();
|
||||
if (is) {
|
||||
|
||||
@@ -36,6 +36,7 @@ import javafx.collections.FXCollections;
|
||||
import javafx.collections.ObservableList;
|
||||
|
||||
import lombok.Getter;
|
||||
import org.int4.fx.values.util.Trigger;
|
||||
|
||||
import java.net.Inet4Address;
|
||||
import java.time.Instant;
|
||||
@@ -74,13 +75,13 @@ public class StoreEntryWrapper {
|
||||
private final Property<String> shownDescription = new SimpleObjectProperty<>();
|
||||
private final Property<StoreEntryInformation> shownInformation = new SimpleObjectProperty<>();
|
||||
private final BooleanProperty template = new SimpleBooleanProperty();
|
||||
private final BooleanProperty renaming = new SimpleBooleanProperty();
|
||||
private final BooleanProperty pinToTop = new SimpleBooleanProperty();
|
||||
private final DoubleProperty orderIndex = new SimpleDoubleProperty();
|
||||
private final BooleanProperty effectiveBusy = new SimpleBooleanProperty();
|
||||
private final Property<StoreCategoryWrapper> lastInformationCategory = new SimpleObjectProperty<>();
|
||||
private final ObservableList<String> tags = FXCollections.observableArrayList();
|
||||
private final Property<Inet4Address> nameIpAddress = new SimpleObjectProperty<>();
|
||||
private final Trigger<Void> renameTrigger = Trigger.of();
|
||||
private boolean effectiveBusyProviderBound = false;
|
||||
|
||||
public StoreEntryWrapper(DataStoreEntry entry) {
|
||||
@@ -387,10 +388,17 @@ public class StoreEntryWrapper {
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean canBreakOutCategory() {
|
||||
public boolean canToggleBreakOutCategory() {
|
||||
if (entry.getBreakOutCategory() != null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var section = StoreViewState.get().getSectionForWrapper(this);
|
||||
var parentSection = StoreViewState.get().getParentSectionForWrapper(this);
|
||||
return (getStore().getValue() instanceof FixedHierarchyStore
|
||||
|| getStore().getValue() instanceof GroupStore<?>)
|
||||
&& StoreViewState.get().getParentSectionForWrapper(this).isPresent();
|
||||
&& parentSection.isPresent()
|
||||
&& section.isPresent() && !section.get().getAllChildren().getList().isEmpty();
|
||||
}
|
||||
|
||||
public void breakOutCategory() {
|
||||
|
||||
@@ -8,6 +8,7 @@ import io.xpipe.app.platform.LabelGraphic;
|
||||
import io.xpipe.app.platform.MenuHelper;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
|
||||
import io.xpipe.app.util.ContextMenuWrapper;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.control.Button;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
@@ -28,12 +29,12 @@ public class StoreQuickAccessButtonComp extends RegionBuilder<Button> {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
private ContextMenu createMenu() {
|
||||
private ContextMenu createContextMenu() {
|
||||
if (section.getShownChildren().getList().isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var cm = MenuHelper.createContextMenu();
|
||||
var cm = new ContextMenu();
|
||||
cm.getStyleClass().add("condensed");
|
||||
Menu menu = (Menu) recurse(cm, section);
|
||||
cm.getItems().addAll(menu.getItems());
|
||||
@@ -85,21 +86,10 @@ public class StoreQuickAccessButtonComp extends RegionBuilder<Button> {
|
||||
|
||||
@Override
|
||||
public Button createSimple() {
|
||||
var cm = new ContextMenuWrapper(() -> createContextMenu());
|
||||
var button = new IconButtonComp("mdi2c-chevron-double-right");
|
||||
button.apply(struc -> {
|
||||
AtomicReference<ContextMenu> menu = new AtomicReference<>();
|
||||
struc.setOnAction(event -> {
|
||||
if (menu.get() == null) {
|
||||
menu.set(createMenu());
|
||||
}
|
||||
|
||||
if (menu.get() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
MenuHelper.toggleMenuShow(menu.get(), struc, Side.RIGHT);
|
||||
event.consume();
|
||||
});
|
||||
cm.installOnButton(struc);
|
||||
});
|
||||
return button.build();
|
||||
}
|
||||
|
||||
@@ -15,12 +15,14 @@ import io.xpipe.app.platform.MenuHelper;
|
||||
import io.xpipe.app.storage.DataStoreEntryRef;
|
||||
import io.xpipe.app.store.DataStore;
|
||||
|
||||
import io.xpipe.app.util.ContextMenuWrapper;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.BooleanProperty;
|
||||
import javafx.beans.property.SimpleBooleanProperty;
|
||||
import javafx.beans.property.SimpleObjectProperty;
|
||||
import javafx.collections.ListChangeListener;
|
||||
import javafx.geometry.Pos;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.input.MouseButton;
|
||||
@@ -203,12 +205,14 @@ public class StoreEntryListBatchBarComp extends SimpleRegionBuilder {
|
||||
},
|
||||
expanded,
|
||||
s.getName());
|
||||
var button = new ButtonComp(name, new SimpleObjectProperty<>(s.getIcon()), () -> {
|
||||
if (batchActions.size() > 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
runActions(s);
|
||||
var button = new ButtonComp(name, s.getIcon(), null);
|
||||
button.apply(struc -> {
|
||||
struc.setOnAction(e -> {
|
||||
if (batchActions.size() == 0) {
|
||||
runActions(s);
|
||||
e.consume();
|
||||
}
|
||||
});
|
||||
});
|
||||
button.describe(d -> d.name(s.getName()));
|
||||
|
||||
@@ -219,15 +223,17 @@ public class StoreEntryListBatchBarComp extends SimpleRegionBuilder {
|
||||
childrenRefs.getList()));
|
||||
|
||||
if (batchActions.size() > 0) {
|
||||
button.apply(new ContextMenuAugment<>(
|
||||
mouseEvent -> mouseEvent.getButton() == MouseButton.PRIMARY, keyEvent -> false, () -> {
|
||||
var cm = MenuHelper.createContextMenu();
|
||||
s.getChildren(childrenRefs.getList()).forEach(childProvider -> {
|
||||
var menu = buildMenuItemForAction(childrenRefs.getList(), childProvider);
|
||||
cm.getItems().add(menu);
|
||||
});
|
||||
return cm;
|
||||
}));
|
||||
var cm = new ContextMenuWrapper(() -> {
|
||||
var c = new ContextMenu();
|
||||
s.getChildren(childrenRefs.getList()).forEach(childProvider -> {
|
||||
var menu = buildMenuItemForAction(childrenRefs.getList(), childProvider);
|
||||
c.getItems().add(menu);
|
||||
});
|
||||
return c;
|
||||
});
|
||||
button.apply(struc -> {
|
||||
cm.installOnButton(struc);
|
||||
});
|
||||
}
|
||||
return button;
|
||||
}
|
||||
|
||||
@@ -2,7 +2,6 @@ package io.xpipe.app.hub.list;
|
||||
|
||||
import io.xpipe.app.comp.BaseRegionBuilder;
|
||||
import io.xpipe.app.comp.SimpleRegionBuilder;
|
||||
import io.xpipe.app.comp.base.ContextMenuAugment;
|
||||
import io.xpipe.app.comp.base.ListBoxViewComp;
|
||||
import io.xpipe.app.comp.base.MultiContentComp;
|
||||
import io.xpipe.app.comp.base.VerticalComp;
|
||||
@@ -14,6 +13,7 @@ import io.xpipe.app.hub.section.StoreSectionComp;
|
||||
import io.xpipe.app.platform.MenuHelper;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
|
||||
import io.xpipe.app.util.ContextMenuWrapper;
|
||||
import javafx.animation.AnimationTimer;
|
||||
import javafx.beans.binding.Bindings;
|
||||
import javafx.beans.property.DoubleProperty;
|
||||
@@ -22,6 +22,7 @@ import javafx.beans.property.SimpleDoubleProperty;
|
||||
import javafx.beans.value.ObservableValue;
|
||||
import javafx.css.PseudoClass;
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.ScrollBar;
|
||||
import javafx.scene.control.ScrollPane;
|
||||
import javafx.scene.input.DragEvent;
|
||||
@@ -81,14 +82,12 @@ public class StoreEntryListComp extends SimpleRegionBuilder {
|
||||
content.style("store-list-comp");
|
||||
content.vgrow();
|
||||
|
||||
content.apply(s -> {
|
||||
var cm = new ContextMenuAugment<>(me -> me.getButton() == MouseButton.SECONDARY, null, () -> {
|
||||
var menu = MenuHelper.createContextMenu();
|
||||
StoreCreationMenu.addButtons(menu.getItems(), false);
|
||||
return menu;
|
||||
});
|
||||
cm.accept(s);
|
||||
var cm = new ContextMenuWrapper(() -> {
|
||||
var menu = new ContextMenu();
|
||||
StoreCreationMenu.addButtons(menu.getItems(), false);
|
||||
return menu;
|
||||
});
|
||||
content.apply(struc -> cm.installOnMouseClick(struc, mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, true));
|
||||
|
||||
content.apply(s -> {
|
||||
setupBorderScroll(s);
|
||||
@@ -270,12 +269,12 @@ public class StoreEntryListComp extends SimpleRegionBuilder {
|
||||
var map = new LinkedHashMap<BaseRegionBuilder<?, ?>, ObservableValue<Boolean>>();
|
||||
map.put(
|
||||
new StoreNotFoundComp().apply(s -> {
|
||||
var cm = new ContextMenuAugment<>(me -> me.getButton() == MouseButton.SECONDARY, null, () -> {
|
||||
var menu = MenuHelper.createContextMenu();
|
||||
var cm = new ContextMenuWrapper(() -> {
|
||||
var menu = new ContextMenu();
|
||||
StoreCreationMenu.addButtons(menu.getItems(), false);
|
||||
return menu;
|
||||
});
|
||||
cm.accept(s);
|
||||
cm.installOnMouseClick(s, mouseEvent -> mouseEvent.getButton() == MouseButton.SECONDARY, true);
|
||||
}),
|
||||
Bindings.and(
|
||||
Bindings.not(Bindings.isEmpty(
|
||||
|
||||
@@ -148,10 +148,6 @@ public class StoreSectionComp extends StoreSectionBaseComp {
|
||||
entryButton.hgrow();
|
||||
entryButton.apply(struc -> {
|
||||
struc.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
|
||||
if (section.getWrapper().getRenaming().get()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.getCode() == KeyCode.SPACE) {
|
||||
section.getWrapper().toggleExpanded();
|
||||
event.consume();
|
||||
|
||||
@@ -48,34 +48,6 @@ public class MenuHelper {
|
||||
return cb;
|
||||
}
|
||||
|
||||
public static ContextMenu createContextMenu() {
|
||||
ContextMenu contextMenu = new ContextMenu();
|
||||
contextMenu.setAutoHide(!AppPrefs.get().limitedTouchscreenMode().get());
|
||||
InputHelper.onLeft(contextMenu, false, e -> {
|
||||
contextMenu.hide();
|
||||
e.consume();
|
||||
});
|
||||
contextMenu.addEventFilter(Menu.ON_SHOWING, e -> {
|
||||
Node content = contextMenu.getSkin().getNode();
|
||||
if (content instanceof Region r) {
|
||||
r.setMaxWidth(500);
|
||||
}
|
||||
});
|
||||
contextMenu.addEventFilter(Menu.ON_SHOWN, e -> {
|
||||
Platform.runLater(() -> {
|
||||
var first = contextMenu.getItems().getFirst();
|
||||
if (first != null) {
|
||||
var s = first.getStyleableNode();
|
||||
if (s != null) {
|
||||
s.requestFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
AppFontSizes.lg(contextMenu.getStyleableNode());
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
public static MenuItem createMenuItem(LabelGraphic graphic, String nameKey) {
|
||||
var i = new MenuItem();
|
||||
i.textProperty().bind(AppI18n.observable(nameKey));
|
||||
@@ -92,16 +64,4 @@ public class MenuHelper {
|
||||
contextMenu.show(ref, side, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static void toggleMenuShow(ContextMenu contextMenu, Node ref, Side side) {
|
||||
if (!contextMenu.isShowing()) {
|
||||
// Prevent NPE in show()
|
||||
if (contextMenu.getScene() == null || ref == null || ref.getScene() == null) {
|
||||
return;
|
||||
}
|
||||
contextMenu.show(ref, side, 0, 0);
|
||||
} else {
|
||||
contextMenu.hide();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
package io.xpipe.app.util;
|
||||
|
||||
import io.xpipe.app.core.AppFontSizes;
|
||||
import io.xpipe.app.platform.InputHelper;
|
||||
import io.xpipe.app.prefs.AppPrefs;
|
||||
import javafx.application.Platform;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.geometry.Side;
|
||||
import javafx.scene.Node;
|
||||
import javafx.scene.control.ButtonBase;
|
||||
import javafx.scene.control.ContextMenu;
|
||||
import javafx.scene.control.Menu;
|
||||
import javafx.scene.control.MenuItem;
|
||||
import javafx.scene.input.KeyEvent;
|
||||
import javafx.scene.input.MouseEvent;
|
||||
import javafx.scene.layout.Region;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
public class ContextMenuWrapper {
|
||||
|
||||
private static final Set<ContextMenu> allContextMenus = new HashSet<>();
|
||||
|
||||
private final Supplier<ContextMenu> contextMenuSupplier;
|
||||
private ContextMenu contextMenu;
|
||||
|
||||
public ContextMenuWrapper(Supplier<ContextMenu> contextMenuSupplier) {this.contextMenuSupplier = contextMenuSupplier;}
|
||||
|
||||
private void applyFixes() {
|
||||
contextMenu.setAutoHide(!AppPrefs.get().limitedTouchscreenMode().get());
|
||||
InputHelper.onLeft(contextMenu, false, e -> {
|
||||
contextMenu.hide();
|
||||
e.consume();
|
||||
});
|
||||
contextMenu.addEventFilter(Menu.ON_SHOWING, e -> {
|
||||
Node content = contextMenu.getSkin().getNode();
|
||||
if (content instanceof Region r) {
|
||||
r.setMaxWidth(500);
|
||||
}
|
||||
});
|
||||
contextMenu.addEventFilter(Menu.ON_SHOWN, e -> {
|
||||
Platform.runLater(() -> {
|
||||
var first = contextMenu.getItems().getFirst();
|
||||
if (first != null) {
|
||||
var s = first.getStyleableNode();
|
||||
if (s != null) {
|
||||
s.requestFocus();
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
AppFontSizes.lg(contextMenu.getStyleableNode());
|
||||
|
||||
contextMenu.addEventFilter(KeyEvent.KEY_PRESSED, e -> {
|
||||
for (MenuItem item : contextMenu.getItems()) {
|
||||
if (item.getAccelerator() != null && item.getAccelerator().match(e)) {
|
||||
contextMenu.hide();
|
||||
item.fire();
|
||||
e.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private ContextMenu getOrCreate() {
|
||||
if (contextMenu == null) {
|
||||
contextMenu = contextMenuSupplier.get();
|
||||
if (contextMenu != null) {
|
||||
applyFixes();
|
||||
}
|
||||
}
|
||||
return contextMenu;
|
||||
}
|
||||
|
||||
private boolean isHidden() {
|
||||
return contextMenu == null || !contextMenu.isShowing();
|
||||
}
|
||||
|
||||
private boolean isShowing() {
|
||||
return contextMenu != null && contextMenu.isShowing();
|
||||
}
|
||||
|
||||
private void hideOthers(boolean includeSelf) {
|
||||
for (ContextMenu cm : allContextMenus) {
|
||||
if (!includeSelf && cm.equals(contextMenu)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
cm.hide();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean show(Region r, Side side) {
|
||||
if (isShowing()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var cm = getOrCreate();
|
||||
if (cm != null) {
|
||||
// Prevent NPE in show()
|
||||
if (contextMenu.getScene() == null || r == null || r.getScene() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cm.show(r, side, 0, 0);
|
||||
|
||||
allContextMenus.add(cm);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean show(Region r, double x, double y) {
|
||||
if (isShowing()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
var cm = getOrCreate();
|
||||
if (cm != null) {
|
||||
// Prevent NPE in show()
|
||||
if (contextMenu.getScene() == null || r == null || r.getScene() == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
cm.show(r, x, y);
|
||||
allContextMenus.add(cm);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public void installOnButton(ButtonBase r) {
|
||||
installOnButton(r, Side.BOTTOM);
|
||||
}
|
||||
|
||||
public void installOnButton(ButtonBase r, Side side) {
|
||||
r.addEventFilter(ActionEvent.ACTION, event -> {
|
||||
if (r.getOnAction() != null) {
|
||||
return;
|
||||
}
|
||||
|
||||
var showing = isShowing();
|
||||
hideOthers(showing);
|
||||
if (!showing && show(r, side)) {
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public void installOnMouseClick(Region r, Predicate<MouseEvent> mouseEventCheck, boolean showAtLocation) {
|
||||
r.addEventHandler(MouseEvent.MOUSE_CLICKED, event -> {
|
||||
if (mouseEventCheck != null && mouseEventCheck.test(event)) {
|
||||
hideOthers(showAtLocation);
|
||||
var shown = (showAtLocation && show(r, event.getScreenX(), event.getScreenY())) || (!showAtLocation && show(r, Side.BOTTOM));
|
||||
if (shown) {
|
||||
event.consume();
|
||||
}
|
||||
}
|
||||
});
|
||||
r.addEventHandler(MouseEvent.MOUSE_PRESSED, event -> {
|
||||
if (mouseEventCheck != null && mouseEventCheck.test(event)) {
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
public void installOnKeyPress(Region r, Predicate<KeyEvent> keyEventCheck) {
|
||||
r.addEventHandler(KeyEvent.KEY_RELEASED, event -> {
|
||||
if (keyEventCheck != null && keyEventCheck.test(event)) {
|
||||
event.consume();
|
||||
}
|
||||
});
|
||||
r.addEventHandler(KeyEvent.KEY_PRESSED, event -> {
|
||||
if (keyEventCheck != null) {
|
||||
if (keyEventCheck.test(event)) {
|
||||
var showing = isShowing();
|
||||
hideOthers(showing);
|
||||
if (!showing && show(r, Side.BOTTOM)) {
|
||||
event.consume();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user