Cleanup and implement custom icons

This commit is contained in:
crschnick 2024-09-14 02:54:35 +00:00
parent a27205de38
commit 5333842e4d
2167 changed files with 730 additions and 5910 deletions

View file

@ -1,6 +1,6 @@
package io.xpipe.app.beacon;
import io.xpipe.app.core.AppResources;
import io.xpipe.app.resources.AppResources;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.util.MarkdownHelper;

View file

@ -1,6 +1,6 @@
package io.xpipe.app.browser.icon;
import io.xpipe.app.core.AppResources;
import io.xpipe.app.resources.AppResources;
import io.xpipe.core.store.FileEntry;
import io.xpipe.core.store.FileKind;
import io.xpipe.core.store.FileNames;

View file

@ -1,6 +1,6 @@
package io.xpipe.app.browser.icon;
import io.xpipe.app.core.AppResources;
import io.xpipe.app.resources.AppResources;
import io.xpipe.core.store.FileEntry;
import io.xpipe.core.store.FileKind;
import io.xpipe.core.store.FileNames;

View file

@ -1,7 +1,7 @@
package io.xpipe.app.browser.icon;
import io.xpipe.app.core.AppImages;
import io.xpipe.app.core.AppResources;
import io.xpipe.app.resources.AppImages;
import io.xpipe.app.resources.AppResources;
import io.xpipe.core.store.FileEntry;
import io.xpipe.core.store.FileKind;

View file

@ -1,7 +1,7 @@
package io.xpipe.app.comp.base;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.AppResources;
import io.xpipe.app.resources.AppResources;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.CompStructure;
import io.xpipe.app.fxcomps.SimpleCompStructure;

View file

@ -1,7 +1,7 @@
package io.xpipe.app.comp.base;
import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.core.AppResources;
import io.xpipe.app.resources.AppResources;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.PrettyImageComp;
import io.xpipe.app.fxcomps.impl.StackComp;

View file

@ -17,6 +17,7 @@ import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.app.fxcomps.util.DerivedObservableList;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.resources.AppResources;
import io.xpipe.app.storage.DataColor;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStoreEntry;

View file

@ -0,0 +1,129 @@
package io.xpipe.app.comp.store;
import atlantafx.base.theme.Tweaks;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.resources.SystemIcon;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.scene.control.*;
import javafx.scene.input.MouseButton;
import javafx.scene.layout.Region;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import static atlantafx.base.theme.Styles.TEXT_SMALL;
public class StoreIconChoiceComp extends SimpleComp {
private final Property<SystemIcon> selected;
private final List<SystemIcon> icons;
private final int columns;
private final SimpleStringProperty filter;
public StoreIconChoiceComp(Property<SystemIcon> selected, List<SystemIcon> icons, int columns, SimpleStringProperty filter) {
this.selected = selected;
this.icons = icons;
this.columns = columns;
this.filter = filter;
}
@Override
protected Region createSimple() {
var table = new TableView<List<SystemIcon>>();
initTable(table);
updateData(table, null);
filter.addListener((observable, oldValue, newValue) -> updateData(table, newValue));
return table;
}
private void initTable(TableView<List<SystemIcon>> table) {
for (int i = 0; i < columns; i++) {
var col = new TableColumn<List<SystemIcon>, SystemIcon>("col" + i);
final int colIndex = i;
col.setCellValueFactory(cb -> {
var row = cb.getValue();
var item = row.size() > colIndex ? row.get(colIndex) : null;
return new SimpleObjectProperty<>(item);
});
col.setCellFactory(cb -> new IconCell());
col.getStyleClass().add(Tweaks.ALIGN_CENTER);
table.getColumns().add(col);
}
table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY_ALL_COLUMNS);
table.getSelectionModel().setCellSelectionEnabled(true);
table.getStyleClass().add("icon-browser");
}
private void updateData(TableView<List<SystemIcon>> table, String filterString) {
var displayedIcons = filterString == null || filterString.isBlank() || filterString.length() < 2 ? icons : icons.stream().filter(
icon -> containsString(icon.getDisplayName(), filterString)).toList();
var data = partitionList(displayedIcons, columns);
table.getItems().setAll(data);
}
private <T> Collection<List<T>> partitionList(List<T> list, int size) {
List<List<T>> partitions = new ArrayList<>();
if (list.size() == 0) {
return partitions;
}
int length = list.size();
int numOfPartitions = length / size + ((length % size == 0) ? 0 : 1);
for (int i = 0; i < numOfPartitions; i++) {
int from = i * size;
int to = Math.min((i * size + size), length);
partitions.add(list.subList(from, to));
}
return partitions;
}
private boolean containsString(String s1, String s2) {
return s1.toLowerCase(Locale.ROOT).contains(s2.toLowerCase(Locale.ROOT));
}
public class IconCell extends TableCell<List<SystemIcon>, SystemIcon> {
private final Label root = new Label();
private final StringProperty image = new SimpleStringProperty();
private final Region imageView = PrettyImageHelper.ofFixedSize(image, 40, 40).createRegion();
public IconCell() {
super();
root.setContentDisplay(ContentDisplay.TOP);
root.setGraphic(imageView);
root.setGraphicTextGap(10);
root.getStyleClass().addAll("icon-label", TEXT_SMALL);
setOnMouseClicked(event -> {
if (event.getButton() == MouseButton.PRIMARY) {
selected.setValue(getItem());
}
});
}
@Override
protected void updateItem(SystemIcon icon, boolean empty) {
super.updateItem(icon, empty);
if (icon == null) {
setGraphic(null);
return;
}
root.setText(icon.getDisplayName());
image.set(icon.getIconName() + ".svg");
setGraphic(root);
}
}
}

View file

@ -0,0 +1,69 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.comp.base.DialogComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.window.AppWindowHelper;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.FilterComp;
import io.xpipe.app.resources.SystemIcon;
import io.xpipe.app.resources.SystemIcons;
import io.xpipe.app.storage.DataStoreEntry;
import javafx.beans.property.Property;
import javafx.beans.property.SimpleObjectProperty;
import javafx.beans.property.SimpleStringProperty;
import javafx.scene.layout.Region;
import javafx.stage.Stage;
public class StoreIconChoiceDialogComp extends SimpleComp {
public static void show(DataStoreEntry entry) {
SystemIcons.load();
var icon = new SimpleObjectProperty<>(SystemIcons.getForId(entry.getIcon()).orElse(null));
var window = AppWindowHelper.sideWindow(AppI18n.get("chooseCustomIcon"), stage -> new StoreIconChoiceDialogComp(icon, entry,stage),true,null);
window.show();
}
private final Property<SystemIcon> selected;
private final DataStoreEntry entry;
private final Stage dialogStage;
public StoreIconChoiceDialogComp(Property<SystemIcon> selected, DataStoreEntry entry, Stage dialogStage) {
this.selected = selected;
this.entry = entry;
this.dialogStage = dialogStage;
}
@Override
protected Region createSimple() {
var filterText = new SimpleStringProperty();
var table = new StoreIconChoiceComp(selected, SystemIcons.getSystemIcons(), 5, filterText);
var filter = new FilterComp(filterText).apply(struc -> {
dialogStage.setOnShowing(event -> {
struc.get().requestFocus();
event.consume();
});
});
var dialog = new DialogComp() {
@Override
protected void finish() {
var icon = selected.getValue().getIconName();
entry.setIcon(icon);
dialogStage.close();
}
@Override
public Comp<?> content() {
return table;
}
@Override
public Comp<?> bottom() {
return filter;
}
};
dialog.prefWidth(600);
dialog.prefHeight(600);
return dialog.createRegion();
}
}

View file

@ -1,27 +1,24 @@
package io.xpipe.app.comp.store;
import io.xpipe.app.core.AppImages;
import io.xpipe.app.core.AppResources;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.impl.PrettyImageHelper;
import io.xpipe.app.fxcomps.impl.TooltipAugment;
import io.xpipe.app.resources.SystemIcons;
import javafx.beans.binding.Bindings;
import javafx.geometry.Pos;
import javafx.scene.input.MouseButton;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.Region;
import javafx.scene.layout.StackPane;
import lombok.AllArgsConstructor;
import org.kordamp.ikonli.javafx.FontIcon;
import java.util.concurrent.atomic.AtomicBoolean;
@AllArgsConstructor
public class StoreIconComp extends SimpleComp {
private final StoreEntryWrapper wrapper;
private final int w;
private final int h;
private static final AtomicBoolean loaded = new AtomicBoolean();
@Override
protected Region createSimple() {
@ -49,6 +46,13 @@ public class StoreIconComp extends SimpleComp {
storeIcon.opacityProperty().bind(Bindings.createDoubleBinding(() -> {
return stack.isHover() ? 0.5 : 1.0;
}, stack.hoverProperty()));
stack.addEventFilter(MouseEvent.MOUSE_PRESSED,event -> {
if (event.getButton() == MouseButton.PRIMARY) {
StoreIconChoiceDialogComp.show(wrapper.getEntry());
event.consume();
}
});
return stack;
}
@ -65,12 +69,7 @@ public class StoreIconComp extends SimpleComp {
.getDisplayIconFileName(wrapper.getEntry().getStore());
}
synchronized (loaded) {
if (!loaded.get()) {
AppImages.loadDirectory(AppResources.XPIPE_MODULE, "img/apps", true, false);
}
loaded.set(true);
return "app:apps/" + wrapper.getIcon().getValue();
}
SystemIcons.load();
return "app:system/" + wrapper.getIcon().getValue() + ".svg";
}
}

View file

@ -3,6 +3,7 @@ package io.xpipe.app.core;
import io.xpipe.app.ext.ExtensionException;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.resources.AppResources;
import io.xpipe.core.process.OsType;
import io.xpipe.app.ext.ProcessControlProvider;
import io.xpipe.core.util.ModuleHelper;

View file

@ -1,6 +1,7 @@
package io.xpipe.app.core;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.resources.AppResources;
import io.xpipe.core.process.OsType;
import javafx.scene.Node;

View file

@ -5,6 +5,7 @@ import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.core.window.AppWindowHelper;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.resources.AppResources;
import javafx.beans.property.SimpleBooleanProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;

View file

@ -1,144 +0,0 @@
package io.xpipe.app.core;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import org.apache.commons.io.FilenameUtils;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
public class AppImages {
public static final Image DEFAULT_IMAGE = new WritableImage(1, 1);
private static final Map<String, Image> images = new HashMap<>();
private static final Map<String, String> svgImages = new HashMap<>();
public static void init() {
if (images.size() > 0 || svgImages.size() > 0) {
return;
}
TrackEvent.info("Loading images ...");
for (var module : AppExtensionManager.getInstance().getContentModules()) {
loadDirectory(module.getName(), "img", true, true);
}
}
public static void loadDirectory(String module, String dir, boolean loadImages, boolean loadSvgs) {
AppResources.with(module, dir, basePath -> {
if (!Files.exists(basePath)) {
return;
}
var simpleName = FilenameUtils.getExtension(module);
String defaultPrefix = simpleName + ":";
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
var relativeFileName = FilenameUtils.separatorsToUnix(
basePath.relativize(file).toString());
try {
if (FilenameUtils.getExtension(file.toString()).equals("svg") && loadSvgs) {
var s = Files.readString(file);
svgImages.put(defaultPrefix + relativeFileName, s);
} else if (loadImages) {
images.put(defaultPrefix + relativeFileName, loadImage(file));
}
} catch (IOException ex) {
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
}
return FileVisitResult.CONTINUE;
}
});
});
}
public static String svgImage(String file) {
if (file == null) {
return "";
}
var key = file.contains(":") ? file : "app:" + file;
if (svgImages.containsKey(key)) {
return svgImages.get(key);
}
TrackEvent.warn("Svg image " + key + " not found");
return "";
}
public static boolean hasNormalImage(String file) {
if (file == null) {
return false;
}
var key = file.contains(":") ? file : "app:" + file;
return images.containsKey(key);
}
public static boolean hasSvgImage(String file) {
if (file == null) {
return false;
}
var key = file.contains(":") ? file : "app:" + file;
return svgImages.containsKey(key);
}
public static Image image(String file) {
if (file == null) {
return DEFAULT_IMAGE;
}
var key = file.contains(":") ? file : "app:" + file;
if (images.containsKey(key)) {
return images.get(key);
}
TrackEvent.warn("Normal image " + key + " not found");
return DEFAULT_IMAGE;
}
public static BufferedImage toAwtImage(Image fxImage) {
BufferedImage img =
new BufferedImage((int) fxImage.getWidth(), (int) fxImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
for (int x = 0; x < fxImage.getWidth(); x++) {
for (int y = 0; y < fxImage.getHeight(); y++) {
int rgb = fxImage.getPixelReader().getArgb(x, y);
img.setRGB(x, y, rgb);
}
}
return img;
}
public static Image loadImage(Path p) {
if (p == null) {
return DEFAULT_IMAGE;
}
if (!Files.isRegularFile(p)) {
TrackEvent.error("Image file " + p + " not found.");
return DEFAULT_IMAGE;
}
try (var in = Files.newInputStream(p)) {
return new Image(in, -1, -1, true, true);
} catch (IOException e) {
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
return DEFAULT_IMAGE;
}
}
}

View file

@ -1,132 +0,0 @@
package io.xpipe.app.core;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.util.FailableConsumer;
import io.xpipe.modulefs.ModuleFileSystem;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class AppResources {
public static final String XPIPE_MODULE = "io.xpipe.app";
private static final Map<String, ModuleFileSystem> fileSystems = new ConcurrentHashMap<>();
public static void reset() {
fileSystems.forEach((s, moduleFileSystem) -> {
try {
moduleFileSystem.close();
} catch (IOException ignored) {
// Usually when updating, a SIGTERM is sent to this application.
// However, it takes a while to shut down but the installer is deleting files meanwhile.
// It can happen that the jar it does not exist anymore
}
});
fileSystems.clear();
}
private static ModuleFileSystem openFileSystemIfNeeded(String module) throws IOException {
var layer = AppExtensionManager.getInstance() != null
? AppExtensionManager.getInstance().getExtendedLayer()
: null;
// Only cache file systems with extended layer
if (layer != null && fileSystems.containsKey(module)) {
return fileSystems.get(module);
}
if (layer == null) {
layer = ModuleLayer.boot();
}
var fs = (ModuleFileSystem) FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer));
if (AppExtensionManager.getInstance() != null) {
fileSystems.put(module, fs);
}
return fs;
}
public static Optional<URL> getResourceURL(String module, String file) {
try {
var fs = openFileSystemIfNeeded(module);
var f = fs.getPath(module.replace('.', '/') + "/resources/" + file);
var url = f.getWrappedPath().toUri().toURL();
return Optional.of(url);
} catch (IOException e) {
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
return Optional.empty();
}
}
public static void with(String module, String file, FailableConsumer<Path, IOException> con) {
if (AppProperties.get() != null
&& !AppProperties.get().isImage()
&& AppProperties.get().isDeveloperMode()) {
// Check if resource was found. If we use external processed resources, we can't use local dev resources
if (withLocalDevResource(module, file, con)) {
return;
}
}
withResource(module, file, con);
}
public static void withResourceInLayer(
String module, String file, ModuleLayer layer, FailableConsumer<Path, IOException> con) {
try (var fs = FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer))) {
var f = fs.getPath(module.replace('.', '/') + "/resources/" + file);
con.accept(f);
} catch (IOException e) {
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
}
}
private static void withResource(String module, String file, FailableConsumer<Path, IOException> con) {
var path = module.startsWith("io.xpipe") ? module.replace('.', '/') + "/resources/" + file : file;
try {
var fs = openFileSystemIfNeeded(module);
var f = fs.getPath(path);
con.accept(f);
} catch (IOException e) {
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
}
}
private static boolean withLocalDevResource(String module, String file, FailableConsumer<Path, IOException> con) {
try {
var fs = openFileSystemIfNeeded(module);
var url = fs.getPath("").getWrappedPath().toUri().toURL();
if (!url.getProtocol().equals("jar")) {
return false;
}
JarURLConnection connection = (JarURLConnection) url.openConnection();
URL fileUrl = connection.getJarFileURL();
var jarFile = Path.of(fileUrl.toURI());
var resDir = jarFile.getParent()
.getParent()
.getParent()
.resolve("src")
.resolve("main")
.resolve("resources");
var f = resDir.resolve(module.replace('.', '/') + "/resources/" + file);
if (!Files.exists(f)) {
return false;
}
con.accept(f);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
}
return true;
}
}

View file

@ -4,6 +4,7 @@ import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.resources.AppResources;
import javafx.scene.Scene;
import java.io.IOException;

View file

@ -6,6 +6,7 @@ import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.resources.AppResources;
import io.xpipe.core.process.OsType;
import javafx.animation.Interpolator;

View file

@ -2,6 +2,8 @@ package io.xpipe.app.core;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.resources.AppImages;
import io.xpipe.app.resources.AppResources;
import io.xpipe.core.process.OsType;
import java.awt.*;

View file

@ -4,6 +4,8 @@ import io.xpipe.app.comp.base.MarkdownComp;
import io.xpipe.app.core.*;
import io.xpipe.app.core.mode.OperationMode;
import io.xpipe.app.core.window.AppWindowHelper;
import io.xpipe.app.resources.AppImages;
import io.xpipe.app.resources.AppResources;
import io.xpipe.app.util.PlatformState;
import io.xpipe.app.util.WindowsRegistry;
import io.xpipe.core.process.OsType;

View file

@ -10,6 +10,7 @@ import io.xpipe.app.ext.ActionProvider;
import io.xpipe.app.ext.DataStoreProviders;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.resources.AppResources;
import io.xpipe.app.storage.DataStorage;
import io.xpipe.app.storage.DataStorageSyncHandler;
import io.xpipe.app.update.XPipeDistributionType;

View file

@ -5,6 +5,8 @@ import io.xpipe.app.core.*;
import io.xpipe.app.core.check.AppFontLoadingCheck;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.resources.AppImages;
import io.xpipe.app.resources.SystemIcons;
import io.xpipe.app.update.UpdateAvailableAlert;
import io.xpipe.app.util.PlatformState;
import io.xpipe.app.util.ThreadHelper;
@ -33,6 +35,7 @@ public abstract class PlatformMode extends OperationMode {
AppTheme.init();
AppStyle.init();
AppImages.init();
SystemIcons.init();
AppLayoutModel.init();
TrackEvent.info("Finished essential component initialization before platform");

View file

@ -1,7 +1,7 @@
package io.xpipe.app.core.window;
import io.xpipe.app.core.AppCache;
import io.xpipe.app.core.AppImages;
import io.xpipe.app.resources.AppImages;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.core.AppTheme;
import io.xpipe.app.fxcomps.Comp;

View file

@ -5,6 +5,8 @@ import io.xpipe.app.core.*;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.issue.TrackEvent;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.app.resources.AppImages;
import io.xpipe.app.resources.AppResources;
import io.xpipe.app.util.InputHelper;
import io.xpipe.app.util.ThreadHelper;
import io.xpipe.core.process.OsType;

View file

@ -7,7 +7,7 @@ import io.xpipe.app.comp.store.StoreEntryWrapper;
import io.xpipe.app.comp.store.StoreSection;
import io.xpipe.app.comp.store.StoreSectionComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.AppImages;
import io.xpipe.app.resources.AppImages;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.storage.DataStoreEntry;

View file

@ -1,6 +1,6 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.core.AppImages;
import io.xpipe.app.resources.AppImages;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.issue.TrackEvent;

View file

@ -1,6 +1,6 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.core.AppImages;
import io.xpipe.app.resources.AppImages;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.util.BindingsHelper;
import io.xpipe.core.store.FileNames;

View file

@ -1,6 +1,6 @@
package io.xpipe.app.fxcomps.impl;
import io.xpipe.app.core.AppImages;
import io.xpipe.app.resources.AppImages;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.fxcomps.util.PlatformThread;
import io.xpipe.app.prefs.AppPrefs;

View file

@ -9,6 +9,7 @@ import io.xpipe.app.core.window.AppWindowHelper;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.fxcomps.SimpleComp;
import io.xpipe.app.resources.AppResources;
import javafx.beans.property.ListProperty;
import javafx.beans.property.SimpleListProperty;
import javafx.beans.property.SimpleStringProperty;

View file

@ -0,0 +1,55 @@
package io.xpipe.app.prefs;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.window.AppWindowHelper;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.impl.SecretFieldComp;
import io.xpipe.core.util.InPlaceSecretValue;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.layout.VBox;
import atlantafx.base.controls.Spacer;
import java.util.Objects;
public class LockChangeAlert {
public static void show() {
var prop1 = new SimpleObjectProperty<InPlaceSecretValue>();
var prop2 = new SimpleObjectProperty<InPlaceSecretValue>();
AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("lockCreationAlertTitle"));
alert.setHeaderText(AppI18n.get("lockCreationAlertHeader"));
alert.setAlertType(Alert.AlertType.CONFIRMATION);
var label1 = new LabelComp(AppI18n.observable("passphrase")).createRegion();
var p1 = new SecretFieldComp(prop1, false).createRegion();
p1.setStyle("-fx-border-width: 1px");
var label2 = new LabelComp(AppI18n.observable("repeatPassphrase")).createRegion();
var p2 = new SecretFieldComp(prop2, false).createRegion();
p1.setStyle("-fx-border-width: 1px");
var content = new VBox(label1, p1, new Spacer(15), label2, p2);
content.setSpacing(5);
alert.getDialogPane().setContent(content);
var button = alert.getDialogPane().lookupButton(ButtonType.OK);
button.disableProperty()
.bind(Bindings.createBooleanBinding(
() -> {
return !Objects.equals(prop1.getValue(), prop2.getValue());
},
prop1,
prop2));
})
.filter(b -> b.getButtonData().isDefaultButton())
.ifPresent(t -> {
AppPrefs.get().changeLock(prop1.getValue());
});
}
}

View file

@ -1,7 +1,7 @@
package io.xpipe.app.prefs;
import io.xpipe.app.core.AppExtensionManager;
import io.xpipe.app.core.AppResources;
import io.xpipe.app.resources.AppResources;
import org.apache.commons.io.FilenameUtils;

View file

@ -4,7 +4,6 @@ import io.xpipe.app.comp.base.ButtonComp;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.window.AppWindowHelper;
import io.xpipe.app.fxcomps.Comp;
import io.xpipe.app.util.LockChangeAlert;
import io.xpipe.app.util.OptionsBuilder;
import io.xpipe.app.util.Validator;
import io.xpipe.core.util.XPipeInstallation;

View file

@ -0,0 +1,145 @@
package io.xpipe.app.resources;
import io.xpipe.app.core.AppExtensionManager;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.app.issue.TrackEvent;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import org.apache.commons.io.FilenameUtils;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.HashMap;
import java.util.Map;
public class AppImages {
public static final Image DEFAULT_IMAGE = new WritableImage(1, 1);
private static final Map<String, Image> images = new HashMap<>();
private static final Map<String, String> svgImages = new HashMap<>();
public static void init() {
if (images.size() > 0 || svgImages.size() > 0) {
return;
}
TrackEvent.info("Loading images ...");
for (var module : AppExtensionManager.getInstance().getContentModules()) {
loadDirectory(module.getName(), "img", true, true);
}
}
public static void loadDirectory(String module, String dir, boolean loadImages, boolean loadSvgs) {
AppResources.with(module, dir, basePath -> {
if (!Files.exists(basePath)) {
return;
}
var simpleName = FilenameUtils.getExtension(module);
String defaultPrefix = simpleName + ":";
Files.walkFileTree(basePath, new SimpleFileVisitor<>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) {
var relativeFileName = FilenameUtils.separatorsToUnix(
basePath.relativize(file).toString());
try {
if (FilenameUtils.getExtension(file.toString()).equals("svg") && loadSvgs) {
var s = Files.readString(file);
svgImages.put(defaultPrefix + relativeFileName, s);
} else if (loadImages) {
images.put(defaultPrefix + relativeFileName, loadImage(file));
}
} catch (IOException ex) {
ErrorEvent.fromThrowable(ex).omitted(true).build().handle();
}
return FileVisitResult.CONTINUE;
}
});
});
}
public static String svgImage(String file) {
if (file == null) {
return "";
}
var key = file.contains(":") ? file : "app:" + file;
if (svgImages.containsKey(key)) {
return svgImages.get(key);
}
TrackEvent.warn("Svg image " + key + " not found");
return "";
}
public static boolean hasNormalImage(String file) {
if (file == null) {
return false;
}
var key = file.contains(":") ? file : "app:" + file;
return images.containsKey(key);
}
public static boolean hasSvgImage(String file) {
if (file == null) {
return false;
}
var key = file.contains(":") ? file : "app:" + file;
return svgImages.containsKey(key);
}
public static Image image(String file) {
if (file == null) {
return DEFAULT_IMAGE;
}
var key = file.contains(":") ? file : "app:" + file;
if (images.containsKey(key)) {
return images.get(key);
}
TrackEvent.warn("Normal image " + key + " not found");
return DEFAULT_IMAGE;
}
public static BufferedImage toAwtImage(Image fxImage) {
BufferedImage img =
new BufferedImage((int) fxImage.getWidth(), (int) fxImage.getHeight(), BufferedImage.TYPE_INT_ARGB);
for (int x = 0; x < fxImage.getWidth(); x++) {
for (int y = 0; y < fxImage.getHeight(); y++) {
int rgb = fxImage.getPixelReader().getArgb(x, y);
img.setRGB(x, y, rgb);
}
}
return img;
}
public static Image loadImage(Path p) {
if (p == null) {
return DEFAULT_IMAGE;
}
if (!Files.isRegularFile(p)) {
TrackEvent.error("Image file " + p + " not found.");
return DEFAULT_IMAGE;
}
try (var in = Files.newInputStream(p)) {
return new Image(in, -1, -1, true, true);
} catch (IOException e) {
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
return DEFAULT_IMAGE;
}
}
}

View file

@ -0,0 +1,134 @@
package io.xpipe.app.resources;
import io.xpipe.app.core.AppExtensionManager;
import io.xpipe.app.core.AppProperties;
import io.xpipe.app.issue.ErrorEvent;
import io.xpipe.core.util.FailableConsumer;
import io.xpipe.modulefs.ModuleFileSystem;
import java.io.IOException;
import java.net.JarURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
public class AppResources {
public static final String XPIPE_MODULE = "io.xpipe.app";
private static final Map<String, ModuleFileSystem> fileSystems = new ConcurrentHashMap<>();
public static void reset() {
fileSystems.forEach((s, moduleFileSystem) -> {
try {
moduleFileSystem.close();
} catch (IOException ignored) {
// Usually when updating, a SIGTERM is sent to this application.
// However, it takes a while to shut down but the installer is deleting files meanwhile.
// It can happen that the jar it does not exist anymore
}
});
fileSystems.clear();
}
private static ModuleFileSystem openFileSystemIfNeeded(String module) throws IOException {
var layer = AppExtensionManager.getInstance() != null
? AppExtensionManager.getInstance().getExtendedLayer()
: null;
// Only cache file systems with extended layer
if (layer != null && fileSystems.containsKey(module)) {
return fileSystems.get(module);
}
if (layer == null) {
layer = ModuleLayer.boot();
}
var fs = (ModuleFileSystem) FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer));
if (AppExtensionManager.getInstance() != null) {
fileSystems.put(module, fs);
}
return fs;
}
public static Optional<URL> getResourceURL(String module, String file) {
try {
var fs = openFileSystemIfNeeded(module);
var f = fs.getPath(module.replace('.', '/') + "/resources/" + file);
var url = f.getWrappedPath().toUri().toURL();
return Optional.of(url);
} catch (IOException e) {
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
return Optional.empty();
}
}
public static void with(String module, String file, FailableConsumer<Path, IOException> con) {
if (AppProperties.get() != null
&& !AppProperties.get().isImage()
&& AppProperties.get().isDeveloperMode()) {
// Check if resource was found. If we use external processed resources, we can't use local dev resources
if (withLocalDevResource(module, file, con)) {
return;
}
}
withResource(module, file, con);
}
public static void withResourceInLayer(
String module, String file, ModuleLayer layer, FailableConsumer<Path, IOException> con) {
try (var fs = FileSystems.newFileSystem(URI.create("module:/" + module), Map.of("layer", layer))) {
var f = fs.getPath(module.replace('.', '/') + "/resources/" + file);
con.accept(f);
} catch (IOException e) {
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
}
}
private static void withResource(String module, String file, FailableConsumer<Path, IOException> con) {
var path = module.startsWith("io.xpipe") ? module.replace('.', '/') + "/resources/" + file : file;
try {
var fs = openFileSystemIfNeeded(module);
var f = fs.getPath(path);
con.accept(f);
} catch (IOException e) {
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
}
}
private static boolean withLocalDevResource(String module, String file, FailableConsumer<Path, IOException> con) {
try {
var fs = openFileSystemIfNeeded(module);
var url = fs.getPath("").getWrappedPath().toUri().toURL();
if (!url.getProtocol().equals("jar")) {
return false;
}
JarURLConnection connection = (JarURLConnection) url.openConnection();
URL fileUrl = connection.getJarFileURL();
var jarFile = Path.of(fileUrl.toURI());
var resDir = jarFile.getParent()
.getParent()
.getParent()
.resolve("src")
.resolve("main")
.resolve("resources");
var f = resDir.resolve(module.replace('.', '/') + "/resources/" + file);
if (!Files.exists(f)) {
return false;
}
con.accept(f);
} catch (Exception e) {
ErrorEvent.fromThrowable(e).omitted(true).build().handle();
}
return true;
}
}

View file

@ -0,0 +1,23 @@
package io.xpipe.app.resources;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.util.FailableFunction;
import lombok.EqualsAndHashCode;
import lombok.Value;
@Value
@EqualsAndHashCode(callSuper=true)
public class AutoSystemIcon extends SystemIcon {
FailableFunction<ShellControl, Boolean, Exception> applicable;
public AutoSystemIcon(String iconName, String displayName, FailableFunction<ShellControl, Boolean, Exception> applicable) {
super(iconName, displayName);
this.applicable = applicable;
}
@Override
public boolean isApplicable(ShellControl sc) throws Exception {
return applicable.apply(sc);
}
}

View file

@ -0,0 +1,17 @@
package io.xpipe.app.resources;
import io.xpipe.core.process.ShellControl;
import lombok.Value;
import lombok.experimental.NonFinal;
@Value
@NonFinal
public class SystemIcon {
String iconName;
String displayName;
public boolean isApplicable(ShellControl sc) throws Exception {
return false;
}
}

View file

@ -0,0 +1,81 @@
package io.xpipe.app.resources;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.process.ShellDialects;
import org.apache.commons.io.FilenameUtils;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
public class SystemIcons {
private static final List<AutoSystemIcon> AUTO_SYSTEM_ICONS = List.of(new AutoSystemIcon("opnsense", "OpnSense",sc -> {
return sc.getOriginalShellDialect() == ShellDialects.OPNSENSE;
}));
private static final List<SystemIcon> SYSTEM_ICONS = new ArrayList<>();
private static boolean loaded = false;
public static synchronized void init() {
if (SYSTEM_ICONS.size() > 0) {
return;
}
SYSTEM_ICONS.addAll(AUTO_SYSTEM_ICONS);
AppResources.with(AppResources.XPIPE_MODULE, "img/system", path -> {
try (var stream = Files.list(path)) {
var all = stream.toList();
for (Path file : all) {
var name = FilenameUtils.getBaseName(file.getFileName().toString());
if (name.contains("-dark") || name.contains("-40")) {
continue;
}
var base = name.replaceAll("-24", "");
if (AUTO_SYSTEM_ICONS.stream().anyMatch(autoSystemIcon -> autoSystemIcon.getIconName().equals(base))) {
continue;
}
var displayName = base.replaceAll("-", " ");
SYSTEM_ICONS.add(new SystemIcon(base, displayName));
}
}
});
}
public static synchronized void load() {
if (loaded) {
return;
}
AppImages.loadDirectory(AppResources.XPIPE_MODULE,"img/system", true, false);
loaded = true;
}
public static Optional<SystemIcon> getForId(String id) {
if (id == null) {
return Optional.empty();
}
for (SystemIcon systemIcon : SYSTEM_ICONS) {
if (systemIcon.getIconName().equals(id)) {
return Optional.of(systemIcon);
}
}
return Optional.empty();
}
public static Optional<SystemIcon> detectForSystem(ShellControl sc) throws Exception {
for (AutoSystemIcon autoSystemIcon : AUTO_SYSTEM_ICONS) {
if (autoSystemIcon.isApplicable(sc)) {
return Optional.of(autoSystemIcon);
}
}
return Optional.empty();
}
public static List<SystemIcon> getSystemIcons() {
return SYSTEM_ICONS;
}
}

View file

@ -68,6 +68,7 @@ public class DataStoreEntry extends StorageElement {
@NonFinal
Order explicitOrder;
@NonFinal
String icon;
private DataStoreEntry(
@ -349,6 +350,14 @@ public class DataStoreEntry extends StorageElement {
return (T) storePersistentState;
}
public void setIcon(String icon) {
var changed = !Objects.equals(this.icon, icon);
this.icon = icon;
if (changed) {
notifyUpdate(false, true);
}
}
public void setStorePersistentState(DataStoreState value) {
var changed = !Objects.equals(storePersistentState, value);
this.storePersistentState = value;
@ -391,7 +400,7 @@ public class DataStoreEntry extends StorageElement {
obj.put("name", name);
obj.put("categoryUuid", categoryUuid.toString());
obj.set("color", mapper.valueToTree(color));
obj.set("icons", mapper.valueToTree(icon));
obj.set("icon", mapper.valueToTree(icon));
stateObj.put("lastUsed", lastUsed.toString());
stateObj.put("lastModified", lastModified.toString());
stateObj.set("persistentState", storePersistentStateNode);

View file

@ -1,106 +0,0 @@
package io.xpipe.app.util;
import javafx.animation.AnimationTimer;
import javafx.scene.Group;
import javafx.scene.Parent;
import javafx.scene.paint.Color;
import javafx.scene.shape.*;
public class Indicator {
private static final Color lColor = Color.rgb(0x66, 0x66, 0x66);
private static final Color rColor = Color.rgb(0x0f, 0x87, 0xc3);
private static final PathElement[] ELEMS = new PathElement[] {
new MoveTo(9.2362945, 19.934046),
new CubicCurveTo(-1.3360939, -0.28065, -1.9963146, -1.69366, -1.9796182, -2.95487),
new CubicCurveTo(-0.1152909, -1.41268, -0.5046634, -3.07081, -1.920768, -3.72287),
new CubicCurveTo(-1.4711631, -0.77284, -3.4574873, -0.11153, -4.69154031, -1.40244),
new CubicCurveTo(-1.30616123, -1.40422, -0.5308003, -4.1855799, 1.46313121, -4.4219799),
new CubicCurveTo(1.4290018, -0.25469, 3.1669517, -0.0875, 4.1676818, -1.36207),
new CubicCurveTo(0.9172241, -1.12206, 0.9594176, -2.63766, 1.0685793, -4.01259),
new CubicCurveTo(0.4020299, -1.95732999, 3.2823027, -2.72818999, 4.5638567, -1.15760999),
new CubicCurveTo(1.215789, 1.31824999, 0.738899, 3.90740999, -1.103778, 4.37267999),
new CubicCurveTo(-1.3972543, 0.40868, -3.0929979, 0.0413, -4.2208253, 1.16215),
new CubicCurveTo(-1.3524806, 1.26423, -1.3178578, 3.29187, -1.1086673, 4.9895199),
new CubicCurveTo(0.167826, 1.28946, 1.0091133, 2.5347, 2.3196964, 2.86608),
new CubicCurveTo(1.6253079, 0.53477, 3.4876372, 0.45004, 5.0294052, -0.30121),
new CubicCurveTo(1.335829, -0.81654, 1.666839, -2.49408, 1.717756, -3.9432),
new CubicCurveTo(0.08759, -1.1232899, 0.704887, -2.3061299, 1.871843, -2.5951699),
new CubicCurveTo(1.534558, -0.50726, 3.390804, 0.62784, 3.467269, 2.28631),
new CubicCurveTo(0.183147, 1.4285099, -0.949563, 2.9179999, -2.431156, 2.9383699),
new CubicCurveTo(-1.390597, 0.17337, -3.074035, 0.18128, -3.971365, 1.45069),
new CubicCurveTo(-0.99314, 1.271, -0.676157, 2.98683, -1.1715, 4.43018),
new CubicCurveTo(-0.518248, 1.11436, -1.909118, 1.63902, -3.0700005, 1.37803),
new ClosePath()
};
static {
for (int i = 1; i < ELEMS.length; ++i) {
ELEMS[i].setAbsolute(false);
}
}
private final Path left;
private final Path right;
private final Group g;
private final int steps;
private boolean fw = true;
private int step = 0;
public Indicator(int ticksPerCycle, double scale) {
this.steps = ticksPerCycle;
left = new Path(ELEMS);
right = new Path(ELEMS);
left.setScaleX(scale);
left.setScaleY(scale);
right.setScaleX(-1 * scale);
right.setScaleY(-1 * scale);
right.setTranslateX(7.266 * scale);
right.setOpacity(0.0);
left.setStroke(null);
right.setStroke(null);
left.setFill(lColor);
right.setFill(rColor);
g = new Group(left, right);
AnimationTimer timer = new AnimationTimer() {
@Override
public void handle(long l) {
step();
}
};
timer.start();
}
public Parent getNode() {
return g;
}
private void step() {
double lOpacity, rOpacity;
step += fw ? 1 : -1;
if (step == steps) {
fw = false;
lOpacity = 0.0;
rOpacity = 1.0;
} else if (step == 0) {
fw = true;
lOpacity = 1.0;
rOpacity = 0.0;
} else {
lOpacity = 1.0 * (steps - step) / steps;
rOpacity = 1.0 * step / steps;
}
left.setOpacity(lOpacity);
right.setOpacity(rOpacity);
}
}

View file

@ -1,56 +0,0 @@
package io.xpipe.app.util;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.window.AppWindowHelper;
import io.xpipe.app.fxcomps.impl.LabelComp;
import io.xpipe.app.fxcomps.impl.SecretFieldComp;
import io.xpipe.app.prefs.AppPrefs;
import io.xpipe.core.util.InPlaceSecretValue;
import javafx.beans.binding.Bindings;
import javafx.beans.property.SimpleObjectProperty;
import javafx.scene.control.Alert;
import javafx.scene.control.ButtonType;
import javafx.scene.layout.VBox;
import atlantafx.base.controls.Spacer;
import java.util.Objects;
public class LockChangeAlert {
public static void show() {
var prop1 = new SimpleObjectProperty<InPlaceSecretValue>();
var prop2 = new SimpleObjectProperty<InPlaceSecretValue>();
AppWindowHelper.showBlockingAlert(alert -> {
alert.setTitle(AppI18n.get("lockCreationAlertTitle"));
alert.setHeaderText(AppI18n.get("lockCreationAlertHeader"));
alert.setAlertType(Alert.AlertType.CONFIRMATION);
var label1 = new LabelComp(AppI18n.observable("passphrase")).createRegion();
var p1 = new SecretFieldComp(prop1, false).createRegion();
p1.setStyle("-fx-border-width: 1px");
var label2 = new LabelComp(AppI18n.observable("repeatPassphrase")).createRegion();
var p2 = new SecretFieldComp(prop2, false).createRegion();
p1.setStyle("-fx-border-width: 1px");
var content = new VBox(label1, p1, new Spacer(15), label2, p2);
content.setSpacing(5);
alert.getDialogPane().setContent(content);
var button = alert.getDialogPane().lookupButton(ButtonType.OK);
button.disableProperty()
.bind(Bindings.createBooleanBinding(
() -> {
return !Objects.equals(prop1.getValue(), prop2.getValue());
},
prop1,
prop2));
})
.filter(b -> b.getButtonData().isDefaultButton())
.ifPresent(t -> {
AppPrefs.get().changeLock(prop1.getValue());
});
}
}

View file

@ -1,62 +0,0 @@
package io.xpipe.app.util;
import io.xpipe.core.dialog.QueryConverter;
import lombok.Value;
import java.util.List;
@Value
public class NamedCharacter {
char character;
List<String> names;
String translationKey;
public static QueryConverter<Character> converter(List<NamedCharacter> chars, boolean allowOthers) {
return new QueryConverter<>() {
@Override
protected Character fromString(String s) {
if (s.length() == 0) {
throw new IllegalArgumentException("No character");
}
var byName = chars.stream()
.filter(nc -> nc.getNames().stream()
.anyMatch(n -> n.toLowerCase().contains(s.toLowerCase())))
.findFirst()
.orElse(null);
if (byName != null) {
return byName.getCharacter();
}
var byChar = chars.stream()
.filter(nc -> String.valueOf(nc.getCharacter()).equalsIgnoreCase(s))
.findFirst()
.orElse(null);
if (byChar != null) {
return byChar.getCharacter();
}
if (!allowOthers) {
throw new IllegalArgumentException("Unknown character: " + s);
}
return QueryConverter.CHARACTER.convertFromString(s);
}
@Override
protected String toString(Character value) {
var byChar = chars.stream()
.filter(nc -> value.equals(nc.getCharacter()))
.findFirst()
.orElse(null);
if (byChar != null) {
return byChar.getNames().getFirst();
}
return value.toString();
}
};
}
}

View file

@ -1,144 +0,0 @@
package io.xpipe.app.util;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Node;
import javafx.scene.control.ListView;
import javafx.scene.control.MultipleSelectionModel;
import javafx.scene.control.ScrollBar;
import java.util.Set;
public class PrettyListView<T> extends ListView<T> {
private final ScrollBar vBar = new ScrollBar();
private final ScrollBar hBar = new ScrollBar();
public PrettyListView() {
super();
skinProperty().addListener(it -> {
// first bind, then add new scrollbars, otherwise the new bars will be found
bindScrollBars();
getChildren().addAll(vBar, hBar);
});
vBar.setManaged(false);
vBar.setOrientation(Orientation.VERTICAL);
vBar.getStyleClass().add("pretty-scroll-bar");
// vBar.visibleProperty().bind(vBar.visibleAmountProperty().isNotEqualTo(0));
hBar.setManaged(false);
hBar.setOrientation(Orientation.HORIZONTAL);
hBar.getStyleClass().add("pretty-scroll-bar");
hBar.visibleProperty().setValue(false);
}
public void disableSelection() {
setSelectionModel(new NoSelectionModel<>());
}
private void bindScrollBars() {
final Set<Node> nodes = lookupAll("VirtualScrollBar");
for (Node node : nodes) {
if (node instanceof ScrollBar bar) {
if (bar.getOrientation().equals(Orientation.VERTICAL)) {
bindScrollBars(vBar, bar, true);
} else if (bar.getOrientation().equals(Orientation.HORIZONTAL)) {
bindScrollBars(hBar, bar, false);
}
}
}
}
private void bindScrollBars(ScrollBar scrollBarA, ScrollBar scrollBarB, boolean bindVisibility) {
scrollBarA.valueProperty().bindBidirectional(scrollBarB.valueProperty());
scrollBarA.minProperty().bindBidirectional(scrollBarB.minProperty());
scrollBarA.maxProperty().bindBidirectional(scrollBarB.maxProperty());
scrollBarA.visibleAmountProperty().bindBidirectional(scrollBarB.visibleAmountProperty());
scrollBarA.unitIncrementProperty().bindBidirectional(scrollBarB.unitIncrementProperty());
scrollBarA.blockIncrementProperty().bindBidirectional(scrollBarB.blockIncrementProperty());
if (bindVisibility) {
scrollBarA.visibleProperty().bind(scrollBarB.visibleProperty());
}
}
@Override
protected void layoutChildren() {
super.layoutChildren();
Insets insets = getInsets();
double w = getWidth();
double h = getHeight();
final double prefWidth = vBar.prefWidth(-1);
vBar.resizeRelocate(
w - prefWidth - insets.getRight(),
insets.getTop(),
prefWidth,
h - insets.getTop() - insets.getBottom());
final double prefHeight = hBar.prefHeight(-1);
hBar.resizeRelocate(
insets.getLeft(),
h - prefHeight - insets.getBottom(),
w - insets.getLeft() - insets.getRight(),
prefHeight);
}
public static class NoSelectionModel<T> extends MultipleSelectionModel<T> {
@Override
public ObservableList<Integer> getSelectedIndices() {
return FXCollections.emptyObservableList();
}
@Override
public ObservableList<T> getSelectedItems() {
return FXCollections.emptyObservableList();
}
@Override
public void selectIndices(int index, int... indices) {}
@Override
public void selectAll() {}
@Override
public void selectFirst() {}
@Override
public void selectLast() {}
@Override
public void clearAndSelect(int index) {}
@Override
public void select(int index) {}
@Override
public void select(T obj) {}
@Override
public void clearSelection(int index) {}
@Override
public void clearSelection() {}
@Override
public boolean isSelected(int index) {
return false;
}
@Override
public boolean isEmpty() {
return true;
}
@Override
public void selectPrevious() {}
@Override
public void selectNext() {}
}
}

View file

@ -1,35 +0,0 @@
package io.xpipe.app.util;
import io.xpipe.app.core.AppI18n;
import io.xpipe.app.core.window.AppWindowHelper;
import io.xpipe.core.process.ShellControl;
import io.xpipe.core.util.ProxyManagerProvider;
import javafx.scene.control.Alert;
import java.util.Optional;
public class ProxyManagerProviderImpl extends ProxyManagerProvider {
private static boolean showAlert() {
return AppWindowHelper.showBlockingAlert(alert -> {
alert.setAlertType(Alert.AlertType.CONFIRMATION);
alert.setTitle(AppI18n.get("connectorInstallationTitle"));
alert.setHeaderText(AppI18n.get("connectorInstallationHeader"));
alert.getDialogPane()
.setContent(AppWindowHelper.alertContentText(AppI18n.get("connectorInstallationContent")));
})
.filter(buttonType -> buttonType.getButtonData().isDefaultButton())
.isPresent();
}
@Override
public Optional<String> checkCompatibility(ShellControl s) {
return Optional.empty();
}
@Override
public boolean setup(ShellControl s) {
return true;
}
}

View file

@ -1,3 +0,0 @@
package io.xpipe.app.util;
public class UserConfig {}

View file

@ -1,6 +0,0 @@
package io.xpipe.app.util;
public interface Validatable {
Validator getValidator();
}

View file

@ -1,3 +1,4 @@
import com.fasterxml.jackson.databind.Module;
import io.xpipe.app.beacon.impl.*;
import io.xpipe.app.browser.action.BrowserAction;
import io.xpipe.app.core.AppLogs;
@ -7,15 +8,11 @@ import io.xpipe.app.issue.EventHandlerImpl;
import io.xpipe.app.storage.DataStateProviderImpl;
import io.xpipe.app.util.AppJacksonModule;
import io.xpipe.app.util.LicenseProvider;
import io.xpipe.app.util.ProxyManagerProviderImpl;
import io.xpipe.app.util.TerminalLauncher;
import io.xpipe.beacon.BeaconInterface;
import io.xpipe.core.util.DataStateProvider;
import io.xpipe.core.util.ModuleLayerLoader;
import io.xpipe.core.util.ProxyFunction;
import io.xpipe.core.util.ProxyManagerProvider;
import com.fasterxml.jackson.databind.Module;
import org.slf4j.spi.SLF4JServiceProvider;
open module io.xpipe.app {
@ -45,6 +42,7 @@ open module io.xpipe.app {
exports io.xpipe.app.browser.fs;
exports io.xpipe.app.browser.file;
exports io.xpipe.app.core.window;
exports io.xpipe.app.resources;
requires com.sun.jna;
requires com.sun.jna.platform;
@ -124,8 +122,6 @@ open module io.xpipe.app {
ScanProvider.Loader;
provides DataStateProvider with
DataStateProviderImpl;
provides ProxyManagerProvider with
ProxyManagerProviderImpl;
provides SLF4JServiceProvider with
AppLogs.Slf4jProvider;
provides EventHandler with

Binary file not shown.

After

Width:  |  Height:  |  Size: 325 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 376 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 398 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 461 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 702 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 866 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 871 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 741 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 945 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 688 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 649 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 790 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 799 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 981 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 754 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 952 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 658 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 674 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 597 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,002 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 850 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 692 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1,001 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 839 B

Some files were not shown because too many files have changed in this diff Show more