mirror of
https://github.com/xpipe-io/xpipe.git
synced 2024-11-22 07:30:24 +00:00
Various small fixes
This commit is contained in:
parent
aa07f88e1d
commit
38efb368ec
13 changed files with 466 additions and 79 deletions
|
@ -73,7 +73,7 @@ public final class XPipeConnection extends BeaconConnection {
|
||||||
} catch (InterruptedException ignored) {
|
} catch (InterruptedException ignored) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var s = BeaconClient.tryConnect();
|
var s = BeaconClient.tryConnect(BeaconClient.ApiClientInformation.builder().version("?").language("Java").build());
|
||||||
if (s.isPresent()) {
|
if (s.isPresent()) {
|
||||||
return s;
|
return s;
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ public final class XPipeConnection extends BeaconConnection {
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
beaconClient = new BeaconClient();
|
beaconClient = BeaconClient.connect(BeaconClient.ApiClientInformation.builder().version("?").language("Java").build());
|
||||||
} catch (Exception ex) {
|
} catch (Exception ex) {
|
||||||
throw new BeaconException("Unable to connect to running xpipe daemon", ex);
|
throw new BeaconException("Unable to connect to running xpipe daemon", ex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,7 @@ public class XPipeDaemonController {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var client = new BeaconClient();
|
var client = BeaconClient.connect(BeaconClient.ApiClientInformation.builder().version("?").language("Java API Test").build());
|
||||||
if (!BeaconServer.tryStop(client)) {
|
if (!BeaconServer.tryStop(client)) {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package io.xpipe.beacon;
|
package io.xpipe.beacon;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeInfo;
|
||||||
|
import com.fasterxml.jackson.annotation.JsonTypeName;
|
||||||
import com.fasterxml.jackson.core.JsonGenerator;
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
|
||||||
import com.fasterxml.jackson.databind.JsonNode;
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
@ -9,7 +11,12 @@ import com.fasterxml.jackson.databind.node.TextNode;
|
||||||
import io.xpipe.beacon.exchange.MessageExchanges;
|
import io.xpipe.beacon.exchange.MessageExchanges;
|
||||||
import io.xpipe.beacon.exchange.data.ClientErrorMessage;
|
import io.xpipe.beacon.exchange.data.ClientErrorMessage;
|
||||||
import io.xpipe.beacon.exchange.data.ServerErrorMessage;
|
import io.xpipe.beacon.exchange.data.ServerErrorMessage;
|
||||||
|
import io.xpipe.core.store.ProcessControl;
|
||||||
import io.xpipe.core.util.JacksonMapper;
|
import io.xpipe.core.util.JacksonMapper;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.Value;
|
||||||
|
import lombok.extern.jackson.Jacksonized;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
|
@ -23,27 +30,90 @@ import static io.xpipe.beacon.BeaconConfig.BODY_SEPARATOR;
|
||||||
|
|
||||||
public class BeaconClient implements AutoCloseable {
|
public class BeaconClient implements AutoCloseable {
|
||||||
|
|
||||||
|
@JsonTypeInfo(use = JsonTypeInfo.Id.NAME, property = "type")
|
||||||
|
public static abstract class ClientInformation {
|
||||||
|
|
||||||
|
public final CliClientInformation cli() {
|
||||||
|
return (CliClientInformation) this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract String toDisplayString();
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonTypeName("cli")
|
||||||
|
@Value
|
||||||
|
@Builder
|
||||||
|
@Jacksonized
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public static class CliClientInformation extends ClientInformation {
|
||||||
|
|
||||||
|
String version;
|
||||||
|
int consoleWidth;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toDisplayString() {
|
||||||
|
return "X-Pipe CLI " + version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonTypeName("gateway")
|
||||||
|
@Value
|
||||||
|
@Builder
|
||||||
|
@Jacksonized
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public static class GatewayClientInformation extends ClientInformation {
|
||||||
|
|
||||||
|
String version;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toDisplayString() {
|
||||||
|
return "X-Pipe Gateway " + version;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonTypeName("api")
|
||||||
|
@Value
|
||||||
|
@Builder
|
||||||
|
@Jacksonized
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public static class ApiClientInformation extends ClientInformation {
|
||||||
|
|
||||||
|
String version;
|
||||||
|
String language;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toDisplayString() {
|
||||||
|
return String.format("X-Pipe %s API v%s", language, version);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private final Closeable closeable;
|
private final Closeable closeable;
|
||||||
private final InputStream in;
|
private final InputStream in;
|
||||||
private final OutputStream out;
|
private final OutputStream out;
|
||||||
|
|
||||||
public BeaconClient() throws IOException {
|
private BeaconClient(Closeable closeable, InputStream in, OutputStream out) {
|
||||||
var socket = new Socket(InetAddress.getLoopbackAddress(), BeaconConfig.getUsedPort());
|
|
||||||
closeable = socket;
|
|
||||||
in = socket.getInputStream();
|
|
||||||
out = socket.getOutputStream();
|
|
||||||
}
|
|
||||||
|
|
||||||
public BeaconClient(Closeable closeable, InputStream in, OutputStream out) {
|
|
||||||
this.closeable = closeable;
|
this.closeable = closeable;
|
||||||
this.in = in;
|
this.in = in;
|
||||||
this.out = out;
|
this.out = out;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Optional<BeaconClient> tryConnect() {
|
public static BeaconClient connect(ClientInformation information) throws Exception {
|
||||||
|
var socket = new Socket(InetAddress.getLoopbackAddress(), BeaconConfig.getUsedPort());
|
||||||
|
var client = new BeaconClient(socket, socket.getInputStream(), socket.getOutputStream());
|
||||||
|
client.sendObject(JacksonMapper.newMapper().valueToTree(information));
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static BeaconClient connectGateway(ProcessControl control, GatewayClientInformation information) throws Exception {
|
||||||
|
var client = new BeaconClient(() -> {}, control.getStdout(), control.getStdin());
|
||||||
|
client.sendObject(JacksonMapper.newMapper().valueToTree(information));
|
||||||
|
return client;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Optional<BeaconClient> tryConnect(ClientInformation information) {
|
||||||
try {
|
try {
|
||||||
return Optional.of(new BeaconClient());
|
return Optional.of(connect(information));
|
||||||
} catch (IOException ex) {
|
} catch (Exception ex) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -95,10 +165,14 @@ public class BeaconClient implements AutoCloseable {
|
||||||
"Sending request to server of type " + req.getClass().getName());
|
"Sending request to server of type " + req.getClass().getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sendObject(msg);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sendObject(JsonNode node) throws ConnectorException {
|
||||||
var writer = new StringWriter();
|
var writer = new StringWriter();
|
||||||
var mapper = JacksonMapper.newMapper();
|
var mapper = JacksonMapper.newMapper();
|
||||||
try (JsonGenerator g = mapper.createGenerator(writer).setPrettyPrinter(new DefaultPrettyPrinter())) {
|
try (JsonGenerator g = mapper.createGenerator(writer).setPrettyPrinter(new DefaultPrettyPrinter())) {
|
||||||
g.writeTree(msg);
|
g.writeTree(node);
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
throw new ConnectorException("Couldn't serialize request", ex);
|
throw new ConnectorException("Couldn't serialize request", ex);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
package io.xpipe.beacon;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.databind.jsontype.NamedType;
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
|
||||||
|
public class BeaconJacksonModule extends SimpleModule {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setupModule(SetupContext context) {
|
||||||
|
context.registerSubtypes(
|
||||||
|
new NamedType(BeaconClient.ApiClientInformation.class),
|
||||||
|
new NamedType(BeaconClient.CliClientInformation.class),
|
||||||
|
new NamedType(BeaconClient.GatewayClientInformation.class));
|
||||||
|
}
|
||||||
|
}
|
|
@ -24,7 +24,7 @@ public class BeaconServer {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static boolean isRunning() {
|
public static boolean isRunning() {
|
||||||
try (var socket = new BeaconClient()) {
|
try (var socket = BeaconClient.connect(null)) {
|
||||||
return true;
|
return true;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
return false;
|
return false;
|
||||||
|
|
|
@ -1,3 +1,5 @@
|
||||||
|
import com.fasterxml.jackson.databind.Module;
|
||||||
|
import io.xpipe.beacon.BeaconJacksonModule;
|
||||||
import io.xpipe.beacon.exchange.*;
|
import io.xpipe.beacon.exchange.*;
|
||||||
import io.xpipe.beacon.exchange.api.QueryRawDataExchange;
|
import io.xpipe.beacon.exchange.api.QueryRawDataExchange;
|
||||||
import io.xpipe.beacon.exchange.api.QueryTableDataExchange;
|
import io.xpipe.beacon.exchange.api.QueryTableDataExchange;
|
||||||
|
@ -24,6 +26,7 @@ module io.xpipe.beacon {
|
||||||
|
|
||||||
uses MessageExchange;
|
uses MessageExchange;
|
||||||
|
|
||||||
|
provides Module with BeaconJacksonModule;
|
||||||
provides io.xpipe.beacon.exchange.MessageExchange with
|
provides io.xpipe.beacon.exchange.MessageExchange with
|
||||||
ForwardExchange,
|
ForwardExchange,
|
||||||
InstanceExchange,
|
InstanceExchange,
|
||||||
|
|
|
@ -16,6 +16,7 @@ public abstract class ProcessControl {
|
||||||
pc.discardErr();
|
pc.discardErr();
|
||||||
var bytes = pc.getStdout().readAllBytes();
|
var bytes = pc.getStdout().readAllBytes();
|
||||||
var string = new String(bytes, pc.getCharset());
|
var string = new String(bytes, pc.getCharset());
|
||||||
|
pc.waitFor();
|
||||||
return string;
|
return string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,6 +28,15 @@ public abstract class ProcessControl {
|
||||||
pc.waitFor();
|
pc.waitFor();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean executeAndCheckStatus() {
|
||||||
|
try {
|
||||||
|
executeOrThrow();
|
||||||
|
return true;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Optional<String> executeAndReadStderrIfPresent() throws Exception {
|
public Optional<String> executeAndReadStderrIfPresent() throws Exception {
|
||||||
var pc = this;
|
var pc = this;
|
||||||
pc.start();
|
pc.start();
|
||||||
|
|
|
@ -0,0 +1,48 @@
|
||||||
|
package io.xpipe.extension;
|
||||||
|
|
||||||
|
import io.xpipe.core.store.DataStore;
|
||||||
|
import io.xpipe.extension.event.ErrorEvent;
|
||||||
|
import javafx.scene.layout.Region;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.ServiceLoader;
|
||||||
|
|
||||||
|
public interface DataStoreActionProvider<T extends DataStore> {
|
||||||
|
|
||||||
|
static List<DataStoreActionProvider<?>> ALL = new ArrayList<>();
|
||||||
|
|
||||||
|
public static void init(ModuleLayer layer) {
|
||||||
|
if (ALL.size() == 0) {
|
||||||
|
ALL.addAll(ServiceLoader.load(layer, DataStoreActionProvider.class).stream()
|
||||||
|
.map(p -> (DataStoreActionProvider<?>) p.get())
|
||||||
|
.filter(provider -> {
|
||||||
|
try {
|
||||||
|
return provider.isActive();
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorEvent.fromThrowable(e).handle();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Class<T> getApplicableClass();
|
||||||
|
|
||||||
|
default boolean isActive() throws Exception {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default boolean isApplicable(T o) throws Exception {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
default void applyToRegion(T store, Region region) {}
|
||||||
|
|
||||||
|
String getName(T store);
|
||||||
|
|
||||||
|
String getIcon(T store);
|
||||||
|
|
||||||
|
default void execute(T store) throws Exception {}
|
||||||
|
}
|
|
@ -6,17 +6,204 @@ import io.xpipe.fxcomps.CompStructure;
|
||||||
import io.xpipe.fxcomps.augment.Augment;
|
import io.xpipe.fxcomps.augment.Augment;
|
||||||
import io.xpipe.fxcomps.util.PlatformThread;
|
import io.xpipe.fxcomps.util.PlatformThread;
|
||||||
import io.xpipe.fxcomps.util.Shortcuts;
|
import io.xpipe.fxcomps.util.Shortcuts;
|
||||||
|
import javafx.animation.KeyFrame;
|
||||||
|
import javafx.animation.Timeline;
|
||||||
import javafx.beans.value.ObservableValue;
|
import javafx.beans.value.ObservableValue;
|
||||||
|
import javafx.event.EventHandler;
|
||||||
|
import javafx.event.WeakEventHandler;
|
||||||
|
import javafx.geometry.NodeOrientation;
|
||||||
import javafx.scene.Node;
|
import javafx.scene.Node;
|
||||||
|
import javafx.scene.Scene;
|
||||||
|
import javafx.scene.input.MouseEvent;
|
||||||
import javafx.scene.layout.Region;
|
import javafx.scene.layout.Region;
|
||||||
import javafx.stage.Window;
|
import javafx.stage.Window;
|
||||||
import javafx.util.Duration;
|
import javafx.util.Duration;
|
||||||
|
|
||||||
public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<S> {
|
public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<S> {
|
||||||
|
|
||||||
static {
|
private static final TooltipBehavior BEHAVIOR = new TooltipBehavior(Duration.millis(400), Duration.INDEFINITE, Duration.millis(100));
|
||||||
JFXTooltip.setHoverDelay(Duration.millis(400));
|
|
||||||
JFXTooltip.setVisibleDuration(Duration.INDEFINITE);
|
private static class TooltipBehavior {
|
||||||
|
|
||||||
|
private static String TOOLTIP_PROP = "jfoenix-tooltip";
|
||||||
|
private Timeline hoverTimer = new Timeline();
|
||||||
|
private Timeline visibleTimer = new Timeline();
|
||||||
|
private Timeline leftTimer = new Timeline();
|
||||||
|
/**
|
||||||
|
* the currently hovered node
|
||||||
|
*/
|
||||||
|
private Node hoveredNode;
|
||||||
|
/**
|
||||||
|
* the next tooltip to be shown
|
||||||
|
*/
|
||||||
|
private JFXTooltip nextTooltip;
|
||||||
|
/**
|
||||||
|
* the current showing tooltip
|
||||||
|
*/
|
||||||
|
private JFXTooltip currentTooltip;
|
||||||
|
|
||||||
|
private TooltipBehavior(Duration hoverDelay, Duration visibleDuration, Duration leftDelay) {
|
||||||
|
setHoverDelay(hoverDelay);
|
||||||
|
hoverTimer.setOnFinished(event -> {
|
||||||
|
ensureHoveredNodeIsVisible(() -> {
|
||||||
|
// set tooltip orientation
|
||||||
|
NodeOrientation nodeOrientation = hoveredNode.getEffectiveNodeOrientation();
|
||||||
|
nextTooltip.getScene().setNodeOrientation(nodeOrientation);
|
||||||
|
//show tooltip
|
||||||
|
showTooltip(nextTooltip);
|
||||||
|
currentTooltip = nextTooltip;
|
||||||
|
hoveredNode = null;
|
||||||
|
// start visible timer
|
||||||
|
visibleTimer.playFromStart();
|
||||||
|
});
|
||||||
|
// clear next tooltip
|
||||||
|
nextTooltip = null;
|
||||||
|
});
|
||||||
|
setVisibleDuration(visibleDuration);
|
||||||
|
visibleTimer.setOnFinished(event -> hideCurrentTooltip());
|
||||||
|
setLeftDelay(leftDelay);
|
||||||
|
leftTimer.setOnFinished(event -> hideCurrentTooltip());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setHoverDelay(Duration duration) {
|
||||||
|
hoverTimer.getKeyFrames().setAll(new KeyFrame(duration));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setVisibleDuration(Duration duration) {
|
||||||
|
visibleTimer.getKeyFrames().setAll(new KeyFrame(duration));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setLeftDelay(Duration duration) {
|
||||||
|
leftTimer.getKeyFrames().setAll(new KeyFrame(duration));
|
||||||
|
}
|
||||||
|
|
||||||
|
private void hideCurrentTooltip() {
|
||||||
|
currentTooltip.hide();
|
||||||
|
currentTooltip = null;
|
||||||
|
hoveredNode = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void showTooltip(JFXTooltip tooltip) {
|
||||||
|
// anchors are computed differently for each tooltip
|
||||||
|
tooltip.show(hoveredNode, -1, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private EventHandler<MouseEvent> moveHandler = (MouseEvent event) -> {
|
||||||
|
// if tool tip is already showing, do nothing
|
||||||
|
if (visibleTimer.getStatus() == Timeline.Status.RUNNING) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hoveredNode = (Node) event.getSource();
|
||||||
|
Object property = hoveredNode.getProperties().get(TOOLTIP_PROP);
|
||||||
|
if (property instanceof JFXTooltip) {
|
||||||
|
JFXTooltip tooltip = (JFXTooltip) property;
|
||||||
|
ensureHoveredNodeIsVisible(() -> {
|
||||||
|
// if a tooltip is already showing then show this tooltip immediately
|
||||||
|
if (leftTimer.getStatus() == Timeline.Status.RUNNING) {
|
||||||
|
if (currentTooltip != null) {
|
||||||
|
currentTooltip.hide();
|
||||||
|
}
|
||||||
|
currentTooltip = tooltip;
|
||||||
|
// show the tooltip
|
||||||
|
showTooltip(tooltip);
|
||||||
|
// stop left timer and start the visible timer to hide the tooltip
|
||||||
|
// once finished
|
||||||
|
leftTimer.stop();
|
||||||
|
visibleTimer.playFromStart();
|
||||||
|
} else {
|
||||||
|
// else mark the tooltip as the next tooltip to be shown once the hover
|
||||||
|
// timer is finished (restart the timer)
|
||||||
|
// t.setActivated(true);
|
||||||
|
nextTooltip = tooltip;
|
||||||
|
hoverTimer.stop();
|
||||||
|
hoverTimer.playFromStart();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
uninstall(hoveredNode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private WeakEventHandler<MouseEvent> weakMoveHandler = new WeakEventHandler<>(moveHandler);
|
||||||
|
|
||||||
|
private EventHandler<MouseEvent> exitHandler = (MouseEvent event) -> {
|
||||||
|
// stop running hover timer as the mouse exited the node
|
||||||
|
if (hoverTimer.getStatus() == Timeline.Status.RUNNING) {
|
||||||
|
hoverTimer.stop();
|
||||||
|
} else if (visibleTimer.getStatus() == Timeline.Status.RUNNING) {
|
||||||
|
// if tool tip was already showing, stop the visible timer
|
||||||
|
// and start the left timer to hide the current tooltip
|
||||||
|
visibleTimer.stop();
|
||||||
|
leftTimer.playFromStart();
|
||||||
|
}
|
||||||
|
hoveredNode = null;
|
||||||
|
nextTooltip = null;
|
||||||
|
};
|
||||||
|
private WeakEventHandler<MouseEvent> weakExitHandler = new WeakEventHandler<>(exitHandler);
|
||||||
|
|
||||||
|
// if mouse is pressed then stop all timers / clear all fields
|
||||||
|
private EventHandler<MouseEvent> pressedHandler = (MouseEvent event) -> {
|
||||||
|
// stop timers
|
||||||
|
hoverTimer.stop();
|
||||||
|
visibleTimer.stop();
|
||||||
|
leftTimer.stop();
|
||||||
|
// hide current tooltip
|
||||||
|
if (currentTooltip != null) {
|
||||||
|
currentTooltip.hide();
|
||||||
|
}
|
||||||
|
// clear fields
|
||||||
|
hoveredNode = null;
|
||||||
|
currentTooltip = null;
|
||||||
|
nextTooltip = null;
|
||||||
|
};
|
||||||
|
private WeakEventHandler<MouseEvent> weakPressedHandler = new WeakEventHandler<>(pressedHandler);
|
||||||
|
|
||||||
|
private void install(Node node, JFXTooltip tooltip) {
|
||||||
|
if (node == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (tooltip == null) {
|
||||||
|
uninstall(node);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.removeEventHandler(MouseEvent.MOUSE_MOVED, weakMoveHandler);
|
||||||
|
node.removeEventHandler(MouseEvent.MOUSE_EXITED, weakExitHandler);
|
||||||
|
node.removeEventHandler(MouseEvent.MOUSE_PRESSED, weakPressedHandler);
|
||||||
|
node.addEventHandler(MouseEvent.MOUSE_MOVED, weakMoveHandler);
|
||||||
|
node.addEventHandler(MouseEvent.MOUSE_EXITED, weakExitHandler);
|
||||||
|
node.addEventHandler(MouseEvent.MOUSE_PRESSED, weakPressedHandler);
|
||||||
|
node.getProperties().put(TOOLTIP_PROP, tooltip);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void uninstall(Node node) {
|
||||||
|
if (node == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
node.removeEventHandler(MouseEvent.MOUSE_MOVED, weakMoveHandler);
|
||||||
|
node.removeEventHandler(MouseEvent.MOUSE_EXITED, weakExitHandler);
|
||||||
|
node.removeEventHandler(MouseEvent.MOUSE_PRESSED, weakPressedHandler);
|
||||||
|
Object tooltip = node.getProperties().get(TOOLTIP_PROP);
|
||||||
|
if (tooltip != null) {
|
||||||
|
node.getProperties().remove(TOOLTIP_PROP);
|
||||||
|
if (tooltip.equals(currentTooltip) || tooltip.equals(nextTooltip)) {
|
||||||
|
weakPressedHandler.handle(null);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureHoveredNodeIsVisible(Runnable visibleRunnable) {
|
||||||
|
final Window owner = getWindow(hoveredNode);
|
||||||
|
if (owner != null && owner.isShowing()) {
|
||||||
|
final boolean treeVisible = true; // NodeHelper.isTreeVisible(hoveredNode);
|
||||||
|
if (treeVisible && owner.isFocused()) {
|
||||||
|
visibleRunnable.run();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Window getWindow(final Node node) {
|
||||||
|
final Scene scene = node == null ? null : node.getScene();
|
||||||
|
return scene == null ? null : scene.getWindow();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ObservableValue<String> text;
|
private final ObservableValue<String> text;
|
||||||
|
@ -31,21 +218,11 @@ public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void augment(S struc) {
|
public void augment(S struc) {
|
||||||
var tt = new FocusTooltip();
|
augment(struc.get());
|
||||||
var toDisplay = text.getValue();
|
|
||||||
if (Shortcuts.getShortcut(struc.get()) != null) {
|
|
||||||
toDisplay = toDisplay + " (" + Shortcuts.getShortcut(struc.get()).getDisplayText() + ")";
|
|
||||||
}
|
|
||||||
tt.textProperty().setValue(toDisplay);
|
|
||||||
tt.setStyle("-fx-font-size: 11pt;");
|
|
||||||
JFXTooltip.install(struc.get(), tt);
|
|
||||||
tt.setWrapText(true);
|
|
||||||
tt.setMaxWidth(400);
|
|
||||||
tt.getStyleClass().add("fancy-tooltip");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void augment(Node region) {
|
public void augment(Node region) {
|
||||||
var tt = new FocusTooltip();
|
var tt = new JFXTooltip();
|
||||||
var toDisplay = text.getValue();
|
var toDisplay = text.getValue();
|
||||||
if (Shortcuts.getShortcut((Region) region) != null) {
|
if (Shortcuts.getShortcut((Region) region) != null) {
|
||||||
toDisplay =
|
toDisplay =
|
||||||
|
@ -53,57 +230,10 @@ public class FancyTooltipAugment<S extends CompStructure<?>> implements Augment<
|
||||||
}
|
}
|
||||||
tt.textProperty().setValue(toDisplay);
|
tt.textProperty().setValue(toDisplay);
|
||||||
tt.setStyle("-fx-font-size: 11pt;");
|
tt.setStyle("-fx-font-size: 11pt;");
|
||||||
JFXTooltip.install(region, tt);
|
|
||||||
tt.setWrapText(true);
|
tt.setWrapText(true);
|
||||||
tt.setMaxWidth(400);
|
tt.setMaxWidth(400);
|
||||||
tt.getStyleClass().add("fancy-tooltip");
|
tt.getStyleClass().add("fancy-tooltip");
|
||||||
}
|
|
||||||
|
|
||||||
private static class FocusTooltip extends JFXTooltip {
|
BEHAVIOR.install(region, tt);
|
||||||
|
|
||||||
public FocusTooltip() {}
|
|
||||||
|
|
||||||
public FocusTooltip(String string) {
|
|
||||||
super(string);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected void show() {
|
|
||||||
Window owner = getOwnerWindow();
|
|
||||||
if (owner == null || owner.isFocused()) {
|
|
||||||
super.show();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void show(Node ownerNode, double anchorX, double anchorY) {
|
|
||||||
Window owner = getOwnerWindow();
|
|
||||||
if (owner == null || owner.isFocused()) {
|
|
||||||
super.show(ownerNode, anchorX, anchorY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void showOnAnchors(Node ownerNode, double anchorX, double anchorY) {
|
|
||||||
Window owner = getOwnerWindow();
|
|
||||||
if (owner == null || owner.isFocused()) {
|
|
||||||
super.showOnAnchors(ownerNode, anchorX, anchorY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void show(Window owner) {
|
|
||||||
if (owner == null || owner.isFocused()) {
|
|
||||||
super.show(owner);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void show(Window ownerWindow, double anchorX, double anchorY) {
|
|
||||||
Window owner = getOwnerWindow();
|
|
||||||
if (owner == null || owner.isFocused()) {
|
|
||||||
super.show(ownerWindow, anchorX, anchorY);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,62 @@
|
||||||
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
|
import io.xpipe.extension.event.ErrorEvent;
|
||||||
|
import org.apache.commons.lang3.SystemUtils;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
|
||||||
|
public class OsHelper {
|
||||||
|
|
||||||
|
public static String getFileSystemCompatibleName(String name) {
|
||||||
|
return name.replaceAll("[\\\\/:*?\"<>|]", "_");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Path getUserDocumentsPath() {
|
||||||
|
if (SystemUtils.IS_OS_WINDOWS) {
|
||||||
|
return Paths.get(System.getProperty("user.home"));
|
||||||
|
} else {
|
||||||
|
return Paths.get(System.getProperty("user.home"), ".local", "share");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void browseFile(Path file) {
|
||||||
|
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadHelper.run(() -> {
|
||||||
|
try {
|
||||||
|
Desktop.getDesktop().open(file.toFile());
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorEvent.fromThrowable(e).omit().handle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void browseFileInDirectory(Path file) {
|
||||||
|
if (!Desktop.getDesktop().isSupported(Desktop.Action.BROWSE_FILE_DIR)) {
|
||||||
|
if (!Desktop.getDesktop().isSupported(Desktop.Action.OPEN)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadHelper.run(() -> {
|
||||||
|
try {
|
||||||
|
Desktop.getDesktop().open(file.getParent().toFile());
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorEvent.fromThrowable(e).omit().handle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ThreadHelper.run(() -> {
|
||||||
|
try {
|
||||||
|
Desktop.getDesktop().browseFileDirectory(file.toFile());
|
||||||
|
} catch (Exception e) {
|
||||||
|
ErrorEvent.fromThrowable(e).omit().handle();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,42 @@
|
||||||
|
package io.xpipe.extension.util;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
|
public class ThreadHelper {
|
||||||
|
|
||||||
|
public static Thread run(Runnable r) {
|
||||||
|
var t = new Thread(r);
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.start();
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T run(Supplier<T> r) {
|
||||||
|
AtomicReference<T> ret = new AtomicReference<>();
|
||||||
|
var t = new Thread(() -> ret.set(r.get()));
|
||||||
|
t.setDaemon(true);
|
||||||
|
t.start();
|
||||||
|
try {
|
||||||
|
t.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return ret.get();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Thread create(String name, boolean daemon, Runnable r) {
|
||||||
|
var t = new Thread(r);
|
||||||
|
t.setDaemon(daemon);
|
||||||
|
t.setName(name);
|
||||||
|
return t;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void sleep(long ms) {
|
||||||
|
try {
|
||||||
|
Thread.sleep(ms);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,5 @@
|
||||||
import io.xpipe.extension.DataSourceProvider;
|
import io.xpipe.extension.DataSourceProvider;
|
||||||
|
import io.xpipe.extension.DataStoreActionProvider;
|
||||||
import io.xpipe.extension.SupportedApplicationProvider;
|
import io.xpipe.extension.SupportedApplicationProvider;
|
||||||
import io.xpipe.extension.util.XPipeDaemon;
|
import io.xpipe.extension.util.XPipeDaemon;
|
||||||
|
|
||||||
|
@ -31,6 +32,7 @@ open module io.xpipe.extension {
|
||||||
|
|
||||||
uses DataSourceProvider;
|
uses DataSourceProvider;
|
||||||
uses SupportedApplicationProvider;
|
uses SupportedApplicationProvider;
|
||||||
|
uses DataStoreActionProvider;
|
||||||
uses io.xpipe.extension.I18n;
|
uses io.xpipe.extension.I18n;
|
||||||
uses io.xpipe.extension.event.EventHandler;
|
uses io.xpipe.extension.event.EventHandler;
|
||||||
uses io.xpipe.extension.prefs.PrefsProvider;
|
uses io.xpipe.extension.prefs.PrefsProvider;
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
io.xpipe.beacon.BeaconJacksonModule
|
Loading…
Reference in a new issue