commit
850e5b509f
@ -10,8 +10,11 @@ import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
|
|||||||
import org.jcnc.jnotepad.common.constants.AppConstants;
|
import org.jcnc.jnotepad.common.constants.AppConstants;
|
||||||
import org.jcnc.jnotepad.common.constants.TextConstants;
|
import org.jcnc.jnotepad.common.constants.TextConstants;
|
||||||
import org.jcnc.jnotepad.common.manager.ThreadPoolManager;
|
import org.jcnc.jnotepad.common.manager.ThreadPoolManager;
|
||||||
|
import org.jcnc.jnotepad.controller.ResourceController;
|
||||||
|
import org.jcnc.jnotepad.controller.config.AppConfigController;
|
||||||
import org.jcnc.jnotepad.controller.i18n.LocalizationController;
|
import org.jcnc.jnotepad.controller.i18n.LocalizationController;
|
||||||
import org.jcnc.jnotepad.controller.manager.Controller;
|
import org.jcnc.jnotepad.controller.manager.Controller;
|
||||||
|
import org.jcnc.jnotepad.plugin.PluginManager;
|
||||||
import org.jcnc.jnotepad.util.UiUtil;
|
import org.jcnc.jnotepad.util.UiUtil;
|
||||||
import org.jcnc.jnotepad.views.manager.ViewManager;
|
import org.jcnc.jnotepad.views.manager.ViewManager;
|
||||||
|
|
||||||
@ -64,6 +67,7 @@ public class LunchApp extends Application {
|
|||||||
@Override
|
@Override
|
||||||
public void start(Stage primaryStage) {
|
public void start(Stage primaryStage) {
|
||||||
SCENE.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/css/styles.css")).toExternalForm());
|
SCENE.getStylesheets().add(Objects.requireNonNull(getClass().getResource("/css/styles.css")).toExternalForm());
|
||||||
|
// 初始化UI组件
|
||||||
initUiComponents();
|
initUiComponents();
|
||||||
UiResourceBundle.bindStringProperty(primaryStage.titleProperty(), TextConstants.TITLE);
|
UiResourceBundle.bindStringProperty(primaryStage.titleProperty(), TextConstants.TITLE);
|
||||||
|
|
||||||
@ -83,7 +87,8 @@ public class LunchApp extends Application {
|
|||||||
// 2. 加载组件
|
// 2. 加载组件
|
||||||
ViewManager viewManager = ViewManager.getInstance(SCENE);
|
ViewManager viewManager = ViewManager.getInstance(SCENE);
|
||||||
viewManager.initScreen(SCENE);
|
viewManager.initScreen(SCENE);
|
||||||
|
// 3. 加载资源
|
||||||
|
ResourceController.getInstance().loadResources();
|
||||||
// 使用线程池加载关联文件并创建文本区域
|
// 使用线程池加载关联文件并创建文本区域
|
||||||
List<String> rawParameters = getParameters().getRaw();
|
List<String> rawParameters = getParameters().getRaw();
|
||||||
Controller.getInstance().openAssociatedFileAndCreateTextArea(rawParameters);
|
Controller.getInstance().openAssociatedFileAndCreateTextArea(rawParameters);
|
||||||
@ -91,6 +96,10 @@ public class LunchApp extends Application {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
|
AppConfigController instance = AppConfigController.getInstance();
|
||||||
|
// 刷新插件配置文件
|
||||||
|
instance.getAppConfig().setPlugins(PluginManager.getInstance().getPluginInfos());
|
||||||
|
instance.writeAppConfig();
|
||||||
// 关闭线程池
|
// 关闭线程池
|
||||||
threadPool.shutdownNow();
|
threadPool.shutdownNow();
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package org.jcnc.jnotepad.app.config;
|
package org.jcnc.jnotepad.app.config;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import org.jcnc.jnotepad.model.entity.PluginInfo;
|
||||||
import org.jcnc.jnotepad.model.entity.ShortcutKey;
|
import org.jcnc.jnotepad.model.entity.ShortcutKey;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -24,6 +25,8 @@ public class AppConfig {
|
|||||||
private boolean textWrap;
|
private boolean textWrap;
|
||||||
private List<ShortcutKey> shortcutKey;
|
private List<ShortcutKey> shortcutKey;
|
||||||
|
|
||||||
|
private List<PluginInfo> plugins;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 生成默认应用配置对象。
|
* 生成默认应用配置对象。
|
||||||
*
|
*
|
||||||
@ -43,11 +46,19 @@ public class AppConfig {
|
|||||||
shortcutKeys.add(createShortcutKey("openConfigItem", ALT_S));
|
shortcutKeys.add(createShortcutKey("openConfigItem", ALT_S));
|
||||||
shortcutKeys.add(createShortcutKey("addItem", ""));
|
shortcutKeys.add(createShortcutKey("addItem", ""));
|
||||||
shortcutKeys.add(createShortcutKey("countItem", ""));
|
shortcutKeys.add(createShortcutKey("countItem", ""));
|
||||||
|
|
||||||
myData.setShortcutKey(shortcutKeys);
|
myData.setShortcutKey(shortcutKeys);
|
||||||
|
myData.setPlugins(new ArrayList<>());
|
||||||
return myData;
|
return myData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<PluginInfo> getPlugins() {
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlugins(List<PluginInfo> plugins) {
|
||||||
|
this.plugins = plugins;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 创建 ShortcutKey 对象。
|
* 创建 ShortcutKey 对象。
|
||||||
*
|
*
|
||||||
|
|||||||
@ -0,0 +1,70 @@
|
|||||||
|
package org.jcnc.jnotepad.controller;
|
||||||
|
|
||||||
|
import org.jcnc.jnotepad.controller.config.AppConfigController;
|
||||||
|
import org.jcnc.jnotepad.exception.AppException;
|
||||||
|
import org.jcnc.jnotepad.plugin.PluginLoader;
|
||||||
|
import org.jcnc.jnotepad.util.LogUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 资源控制器:用于加载程序所需的资源
|
||||||
|
*
|
||||||
|
* @author gewuyou
|
||||||
|
*/
|
||||||
|
public class ResourceController {
|
||||||
|
private static final ResourceController INSTANCE = new ResourceController();
|
||||||
|
Logger logger = LogUtil.getLogger(this.getClass());
|
||||||
|
|
||||||
|
private ResourceController() {
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static ResourceController getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadResources() {
|
||||||
|
loadPlugins();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 装载插件
|
||||||
|
*
|
||||||
|
* @since 2023/9/15 21:39
|
||||||
|
*/
|
||||||
|
public void loadPlugins() {
|
||||||
|
// 扫描并装载插件
|
||||||
|
scanLoadPlugins(AppConfigController.getInstance().getPlungsPath());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扫描插件
|
||||||
|
*
|
||||||
|
* @param pluginsPath 插件路径
|
||||||
|
* @apiNote 扫描所有插件,更新配置文件中的插件信息
|
||||||
|
* @since 2023/9/16 0:21
|
||||||
|
*/
|
||||||
|
|
||||||
|
private void scanLoadPlugins(Path pluginsPath) {
|
||||||
|
if (!Files.isDirectory(pluginsPath)) {
|
||||||
|
try {
|
||||||
|
Files.createDirectory(pluginsPath);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new AppException("这不是一个有效的路径!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 获取插件加载器
|
||||||
|
PluginLoader pluginLoader = PluginLoader.getInstance();
|
||||||
|
try (Stream<Path> pathStream = Files.walk(pluginsPath)) {
|
||||||
|
pathStream.filter(path -> path.toString().endsWith(".jar")).forEach(path -> pluginLoader.loadPluginByPath(path.toString()));
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -32,10 +32,13 @@ public class AppConfigController {
|
|||||||
private static final Logger logger = LogUtil.getLogger(AppConfigController.class);
|
private static final Logger logger = LogUtil.getLogger(AppConfigController.class);
|
||||||
private static final AppConfigController INSTANCE = new AppConfigController();
|
private static final AppConfigController INSTANCE = new AppConfigController();
|
||||||
private AppConfig appConfig;
|
private AppConfig appConfig;
|
||||||
private String dir;
|
private static final String DEFAULT_PROPERTY = "user.home";
|
||||||
|
private String appConfigDir;
|
||||||
|
private String pluginsDir;
|
||||||
|
|
||||||
private AppConfigController() {
|
private AppConfigController() {
|
||||||
setDir(Paths.get(System.getProperty("user.home"), ".jnotepad").toString());
|
setAppConfigDir(Paths.get(System.getProperty(DEFAULT_PROPERTY), ".jnotepad").toString());
|
||||||
|
setPluginsDir(Paths.get(System.getProperty(DEFAULT_PROPERTY), ".jnotepad", "plugins").toString());
|
||||||
loadConfig();
|
loadConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -54,7 +57,6 @@ public class AppConfigController {
|
|||||||
public void loadConfig() {
|
public void loadConfig() {
|
||||||
createConfigIfNotExists();
|
createConfigIfNotExists();
|
||||||
Path configPath = getConfigPath();
|
Path configPath = getConfigPath();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
logger.info("正在加载配置文件...");
|
logger.info("正在加载配置文件...");
|
||||||
// 存在则加载
|
// 存在则加载
|
||||||
@ -82,7 +84,7 @@ public class AppConfigController {
|
|||||||
private void writeAppConfig(AppConfig appConfig) {
|
private void writeAppConfig(AppConfig appConfig) {
|
||||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(getConfigPath().toString()))) {
|
try (BufferedWriter writer = new BufferedWriter(new FileWriter(getConfigPath().toString()))) {
|
||||||
if (appConfig == null) {
|
if (appConfig == null) {
|
||||||
appConfig = createShortcutKeyJson();
|
appConfig = createConfigJson();
|
||||||
}
|
}
|
||||||
writer.write(JsonUtil.toJsonString(appConfig));
|
writer.write(JsonUtil.toJsonString(appConfig));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@ -99,7 +101,7 @@ public class AppConfigController {
|
|||||||
if (configPath.toFile().exists()) {
|
if (configPath.toFile().exists()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
File directory = new File(dir);
|
File directory = new File(appConfigDir);
|
||||||
if (!directory.exists()) {
|
if (!directory.exists()) {
|
||||||
directory.mkdirs();
|
directory.mkdirs();
|
||||||
}
|
}
|
||||||
@ -112,10 +114,14 @@ public class AppConfigController {
|
|||||||
* @return 配置文件的路径
|
* @return 配置文件的路径
|
||||||
*/
|
*/
|
||||||
public Path getConfigPath() {
|
public Path getConfigPath() {
|
||||||
return Paths.get(getDir(), CONFIG_NAME);
|
return Paths.get(getAppConfigDir(), CONFIG_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppConfig createShortcutKeyJson() {
|
public Path getPlungsPath() {
|
||||||
|
return Paths.get(getPluginsDir());
|
||||||
|
}
|
||||||
|
|
||||||
|
private AppConfig createConfigJson() {
|
||||||
return AppConfig.generateDefaultAppConfig();
|
return AppConfig.generateDefaultAppConfig();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,15 +175,23 @@ public class AppConfigController {
|
|||||||
*
|
*
|
||||||
* @return 所在目录
|
* @return 所在目录
|
||||||
*/
|
*/
|
||||||
public String getDir() {
|
public String getAppConfigDir() {
|
||||||
return dir;
|
return appConfigDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDir(String dir) {
|
public void setAppConfigDir(String appConfigDir) {
|
||||||
this.dir = dir;
|
this.appConfigDir = appConfigDir;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AppConfig getAppConfig() {
|
public String getPluginsDir() {
|
||||||
|
return pluginsDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPluginsDir(String pluginsDir) {
|
||||||
|
this.pluginsDir = pluginsDir;
|
||||||
|
}
|
||||||
|
|
||||||
|
public AppConfig getAppConfig() {
|
||||||
return appConfig;
|
return appConfig;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,8 @@
|
|||||||
package org.jcnc.jnotepad.model.entity;
|
package org.jcnc.jnotepad.model.entity;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import org.jcnc.jnotepad.plugin.interfaces.Plugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插件信息
|
* 插件信息
|
||||||
*
|
*
|
||||||
@ -18,11 +21,24 @@ public class PluginInfo {
|
|||||||
* 启用状态
|
* 启用状态
|
||||||
*/
|
*/
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
|
/**
|
||||||
|
* 作者名称
|
||||||
|
*/
|
||||||
|
private String author;
|
||||||
|
/**
|
||||||
|
* 类别
|
||||||
|
*/
|
||||||
|
private String category;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 主类名称
|
* 主类名称
|
||||||
*/
|
*/
|
||||||
private String mainClass;
|
private String mainClass;
|
||||||
|
/**
|
||||||
|
* 插件类
|
||||||
|
*/
|
||||||
|
@JsonIgnore
|
||||||
|
private Plugin plugin;
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return name;
|
return name;
|
||||||
@ -55,4 +71,28 @@ public class PluginInfo {
|
|||||||
public void setMainClass(String mainClass) {
|
public void setMainClass(String mainClass) {
|
||||||
this.mainClass = mainClass;
|
this.mainClass = mainClass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getAuthor() {
|
||||||
|
return author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAuthor(String author) {
|
||||||
|
this.author = author;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCategory() {
|
||||||
|
return category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setCategory(String category) {
|
||||||
|
this.category = category;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Plugin getPlugin() {
|
||||||
|
return plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPlugin(Plugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
package org.jcnc.jnotepad.plugin;
|
package org.jcnc.jnotepad.plugin;
|
||||||
|
|
||||||
|
import org.jcnc.jnotepad.controller.config.AppConfigController;
|
||||||
import org.jcnc.jnotepad.exception.AppException;
|
import org.jcnc.jnotepad.exception.AppException;
|
||||||
import org.jcnc.jnotepad.model.entity.PluginInfo;
|
import org.jcnc.jnotepad.model.entity.PluginInfo;
|
||||||
import org.jcnc.jnotepad.plugin.interfaces.Plugin;
|
import org.jcnc.jnotepad.plugin.interfaces.Plugin;
|
||||||
@ -12,6 +13,7 @@ import java.lang.reflect.InvocationTargetException;
|
|||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLClassLoader;
|
import java.net.URLClassLoader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Iterator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.jar.JarFile;
|
import java.util.jar.JarFile;
|
||||||
@ -31,7 +33,7 @@ public class PluginLoader {
|
|||||||
*
|
*
|
||||||
* @param pluginJar jar 包
|
* @param pluginJar jar 包
|
||||||
*/
|
*/
|
||||||
private static PluginInfo readPlugin(File pluginJar) throws IOException {
|
public static PluginInfo readPlugin(File pluginJar) throws IOException {
|
||||||
InputStream is;
|
InputStream is;
|
||||||
StringBuilder sb;
|
StringBuilder sb;
|
||||||
try (JarFile jarFile = new JarFile(pluginJar)) {
|
try (JarFile jarFile = new JarFile(pluginJar)) {
|
||||||
@ -53,24 +55,103 @@ public class PluginLoader {
|
|||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 检查插件
|
||||||
|
*
|
||||||
|
* @param configPluginInfos 配置文件插件信息
|
||||||
|
* @param pluginInfo 插件信息类
|
||||||
|
* @param pluginInfos 插件信息集合
|
||||||
|
* @return boolean 是否检查通过
|
||||||
|
* @apiNote
|
||||||
|
* @since 2023/9/16 14:04
|
||||||
|
*/
|
||||||
|
private static boolean checkPlugin(List<PluginInfo> configPluginInfos, PluginInfo pluginInfo, List<PluginInfo> pluginInfos) {
|
||||||
|
// 如果应用程序配置文件为空则默认插件被禁用
|
||||||
|
if (configPluginInfos.isEmpty()) {
|
||||||
|
return disabledByDefault(configPluginInfos, pluginInfo, pluginInfos);
|
||||||
|
}
|
||||||
|
// 如果应用程序配置文件中该插件禁用则不加载
|
||||||
|
for (PluginInfo configPluginInfo : configPluginInfos) {
|
||||||
|
if (disableDoNotLoad(pluginInfo, pluginInfos, configPluginInfo)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 判断该插件是否已经加载
|
||||||
|
return loaded(pluginInfo, pluginInfos);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean loaded(PluginInfo pluginInfo, List<PluginInfo> pluginInfos) {
|
||||||
|
Iterator<PluginInfo> iterator = pluginInfos.iterator();
|
||||||
|
while (iterator.hasNext()) {
|
||||||
|
PluginInfo plugin = iterator.next();
|
||||||
|
if ((plugin.getName() + plugin.getAuthor()).equals(pluginInfo.getName() + pluginInfo.getAuthor())) {
|
||||||
|
if (plugin.getVersion().equals(pluginInfo.getVersion())) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// 如果当前插件版本更低则更新
|
||||||
|
if (plugin.getVersion().compareTo(pluginInfo.getVersion()) < 0) {
|
||||||
|
// 删除当前的插件
|
||||||
|
iterator.remove();
|
||||||
|
} else {
|
||||||
|
throw new AppException("当前加载的插件版本低于本地版本!");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean disableDoNotLoad(PluginInfo pluginInfo, List<PluginInfo> pluginInfos, PluginInfo configPluginInfo) {
|
||||||
|
if ((configPluginInfo.getName() + configPluginInfo.getAuthor()).equals(pluginInfo.getName() + pluginInfo.getAuthor()) && !configPluginInfo.isEnabled()) {
|
||||||
|
pluginInfo.setEnabled(false);
|
||||||
|
pluginInfos.add(pluginInfo);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean disabledByDefault(List<PluginInfo> configPluginInfos, PluginInfo pluginInfo, List<PluginInfo> pluginInfos) {
|
||||||
|
pluginInfo.setEnabled(false);
|
||||||
|
pluginInfos.add(pluginInfo);
|
||||||
|
configPluginInfos.add(pluginInfo);
|
||||||
|
AppConfigController.getInstance().writeAppConfig();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加载插件
|
* 加载插件
|
||||||
*
|
*
|
||||||
* @param pluginFilePath 插件文件的路径
|
* @param pluginFilePath 插件文件的路径
|
||||||
*/
|
*/
|
||||||
public void loadPlugins(String pluginFilePath) {
|
public void loadPluginByPath(String pluginFilePath) {
|
||||||
PluginManager pluginManager = PluginManager.getInstance();
|
|
||||||
List<Plugin> plugins = pluginManager.getPlugins();
|
|
||||||
Map<String, List<String>> categories = pluginManager.getLoadedPluginsByCategory();
|
|
||||||
Map<String, PluginInfo> pluginInfos = pluginManager.getPluginInfos();
|
|
||||||
File file = new File(pluginFilePath);
|
File file = new File(pluginFilePath);
|
||||||
if (file.exists() && file.isFile()) {
|
loadPluginByFile(file, AppConfigController.getInstance().getAppConfig().getPlugins());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 根据文件加载插件
|
||||||
|
*
|
||||||
|
* @param pluginJar 插件jar包
|
||||||
|
* @param configPluginInfos 配置文件插件信息集合
|
||||||
|
* @apiNote
|
||||||
|
* @since 2023/9/16 14:05
|
||||||
|
*/
|
||||||
|
public void loadPluginByFile(File pluginJar, List<PluginInfo> configPluginInfos) {
|
||||||
|
PluginManager pluginManager = PluginManager.getInstance();
|
||||||
|
Map<String, List<String>> categories = pluginManager.getLoadedPluginsByCategory();
|
||||||
|
List<PluginInfo> pluginInfos = pluginManager.getPluginInfos();
|
||||||
|
if (pluginJar.exists() && pluginJar.isFile()) {
|
||||||
try {
|
try {
|
||||||
PluginInfo pluginInfo = readPlugin(file);
|
PluginInfo pluginInfo = readPlugin(pluginJar);
|
||||||
pluginInfos.put(pluginInfo.getName(), pluginInfo);
|
// 检查插件状态
|
||||||
|
if (checkPlugin(configPluginInfos, pluginInfo, pluginInfos)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pluginInfo.setEnabled(true);
|
||||||
|
pluginInfos.add(pluginInfo);
|
||||||
// 创建URLClassLoader以加载Jar文件中的类
|
// 创建URLClassLoader以加载Jar文件中的类
|
||||||
Class<?> pluginClass;
|
Class<?> pluginClass;
|
||||||
try (URLClassLoader classLoader = new URLClassLoader(new URL[]{file.toURI().toURL()})) {
|
try (URLClassLoader classLoader = new URLClassLoader(new URL[]{pluginJar.toURI().toURL()})) {
|
||||||
|
logger.info("{}", pluginInfo.getMainClass());
|
||||||
pluginClass = classLoader.loadClass(pluginInfo.getMainClass());
|
pluginClass = classLoader.loadClass(pluginInfo.getMainClass());
|
||||||
}
|
}
|
||||||
if (pluginClass == null) {
|
if (pluginClass == null) {
|
||||||
@ -78,11 +159,8 @@ public class PluginLoader {
|
|||||||
}
|
}
|
||||||
Plugin plugin;
|
Plugin plugin;
|
||||||
plugin = (Plugin) pluginClass.getDeclaredConstructor().newInstance();
|
plugin = (Plugin) pluginClass.getDeclaredConstructor().newInstance();
|
||||||
plugins.add(plugin);
|
pluginInfo.setPlugin(plugin);
|
||||||
// 添加插件到类别中
|
categories.computeIfAbsent(pluginInfo.getCategory(), k -> new ArrayList<>()).add(pluginInfo.getName());
|
||||||
String categoryName = plugin.getCategoryName();
|
|
||||||
String displayName = plugin.getDisplayName();
|
|
||||||
categories.computeIfAbsent(categoryName, k -> new ArrayList<>()).add(displayName);
|
|
||||||
} catch (IOException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
|
} catch (IOException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
|
||||||
throw new AppException(e);
|
throw new AppException(e);
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
@ -92,7 +170,7 @@ public class PluginLoader {
|
|||||||
}
|
}
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
LogUtil.getLogger(this.getClass()).info("PluginInfo file not found: {}", pluginFilePath);
|
LogUtil.getLogger(this.getClass()).info("PluginInfo file not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,14 +1,22 @@
|
|||||||
package org.jcnc.jnotepad.plugin;
|
package org.jcnc.jnotepad.plugin;
|
||||||
|
|
||||||
|
import org.jcnc.jnotepad.common.manager.ThreadPoolManager;
|
||||||
|
import org.jcnc.jnotepad.controller.config.AppConfigController;
|
||||||
import org.jcnc.jnotepad.model.entity.PluginInfo;
|
import org.jcnc.jnotepad.model.entity.PluginInfo;
|
||||||
import org.jcnc.jnotepad.plugin.interfaces.Plugin;
|
|
||||||
import org.jcnc.jnotepad.util.LogUtil;
|
import org.jcnc.jnotepad.util.LogUtil;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static org.jcnc.jnotepad.plugin.PluginLoader.readPlugin;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插件管理器
|
* 插件管理器
|
||||||
@ -21,10 +29,6 @@ import java.util.Map;
|
|||||||
public class PluginManager {
|
public class PluginManager {
|
||||||
private static final PluginManager INSTANCE = new PluginManager();
|
private static final PluginManager INSTANCE = new PluginManager();
|
||||||
Logger logger = LogUtil.getLogger(this.getClass());
|
Logger logger = LogUtil.getLogger(this.getClass());
|
||||||
/**
|
|
||||||
* 插件集合
|
|
||||||
*/
|
|
||||||
private final List<Plugin> plugins = new ArrayList<>();
|
|
||||||
/**
|
/**
|
||||||
* 插件类别
|
* 插件类别
|
||||||
*/
|
*/
|
||||||
@ -32,8 +36,7 @@ public class PluginManager {
|
|||||||
/**
|
/**
|
||||||
* 插件信息
|
* 插件信息
|
||||||
*/
|
*/
|
||||||
|
private final List<PluginInfo> pluginInfos = new ArrayList<>();
|
||||||
private final Map<String, PluginInfo> pluginInfos = new HashMap<>();
|
|
||||||
|
|
||||||
private PluginManager() {
|
private PluginManager() {
|
||||||
|
|
||||||
@ -47,30 +50,89 @@ public class PluginManager {
|
|||||||
/**
|
/**
|
||||||
* 卸载插件
|
* 卸载插件
|
||||||
*
|
*
|
||||||
* @param pluginClassName 插件类名
|
* @param pluginInfo 插件信息类
|
||||||
* @since 2023/9/11 12:28
|
* @since 2023/9/11 12:28
|
||||||
*/
|
*/
|
||||||
public void unloadPlugin(String pluginClassName) {
|
public void unloadPlugin(PluginInfo pluginInfo) {
|
||||||
//todo Unload the plugin and remove it from the list
|
// 删除集合中的插件信息
|
||||||
|
pluginInfos.remove(pluginInfo);
|
||||||
|
AppConfigController instance = AppConfigController.getInstance();
|
||||||
|
instance.getAppConfig().getPlugins().remove(pluginInfo);
|
||||||
|
// 刷新配置
|
||||||
|
instance.writeAppConfig();
|
||||||
|
// 删除本地插件jar包
|
||||||
|
Path plungsPath = instance.getPlungsPath();
|
||||||
|
try (Stream<Path> pathStream = Files.walk(plungsPath)) {
|
||||||
|
pathStream.filter(path -> path.toString().endsWith(".jar")).forEach(path -> {
|
||||||
|
try {
|
||||||
|
File pluginJar = new File(path.toString());
|
||||||
|
PluginInfo temp = readPlugin(pluginJar);
|
||||||
|
if ((temp.getName() + temp.getAuthor()).equals(pluginInfo.getName() + pluginInfo.getAuthor())) {
|
||||||
|
Files.delete(pluginJar.toPath());
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error(e.getMessage(), e);
|
||||||
|
}
|
||||||
|
ThreadPoolManager.threadContSelfSubtracting();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 禁用插件
|
* 禁用插件
|
||||||
*
|
*
|
||||||
* @param pluginClassName 禁用某个插件
|
* @param pluginInfo 需要禁用的某个插件的插件类
|
||||||
* @apiNote
|
* @apiNote
|
||||||
* @since 2023/9/11 12:34
|
* @since 2023/9/11 12:34
|
||||||
*/
|
*/
|
||||||
public void disablePlugIn(String pluginClassName) {
|
public void disablePlugIn(PluginInfo pluginInfo) {
|
||||||
//todo Disable the plugin
|
pluginInfo.setEnabled(false);
|
||||||
|
pluginInfo.setPlugin(null);
|
||||||
|
ThreadPoolManager.getThreadPool().submit(() -> {
|
||||||
|
AppConfigController instance = AppConfigController.getInstance();
|
||||||
|
instance.getAppConfig().getPlugins().forEach(plugin -> {
|
||||||
|
if ((pluginInfo.getName() + pluginInfo.getAuthor()).equals(plugin.getName() + plugin.getAuthor())) {
|
||||||
|
plugin.setEnabled(false);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
instance.writeAppConfig();
|
||||||
|
ThreadPoolManager.threadContSelfSubtracting();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化所有启用的插件
|
||||||
|
*/
|
||||||
|
public void initializePlugins() {
|
||||||
|
for (PluginInfo pluginInfo : pluginInfos) {
|
||||||
|
if (pluginInfo.isEnabled()) {
|
||||||
|
pluginInfo.getPlugin().initialize();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 执行插件
|
||||||
|
*
|
||||||
|
* @param pluginInfo 需要执行的插件的信息类
|
||||||
|
* @apiNote
|
||||||
|
* @since 2023/9/16 14:58
|
||||||
|
*/
|
||||||
|
public void executePlugin(PluginInfo pluginInfo) {
|
||||||
|
pluginInfo.getPlugin().execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行加载的插件
|
* 执行加载的插件
|
||||||
|
* todo 待移除
|
||||||
*/
|
*/
|
||||||
public void executePlugins() {
|
public void executePlugins() {
|
||||||
for (Plugin plugin : plugins) {
|
for (PluginInfo pluginInfo : pluginInfos) {
|
||||||
plugin.execute();
|
if (pluginInfo.isEnabled()) {
|
||||||
|
pluginInfo.getPlugin().execute();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,11 +145,7 @@ public class PluginManager {
|
|||||||
return categories;
|
return categories;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<Plugin> getPlugins() {
|
public List<PluginInfo> getPluginInfos() {
|
||||||
return plugins;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Map<String, PluginInfo> getPluginInfos() {
|
|
||||||
return pluginInfos;
|
return pluginInfos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -17,13 +17,14 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 插件演示类
|
* 插件管理界面
|
||||||
* <p>
|
* <p>
|
||||||
* 用于演示插件加载和执行的界面。
|
* 用于演示插件加载和执行的界面。
|
||||||
*
|
*
|
||||||
* @author luke
|
* @author luke gewuyou
|
||||||
*/
|
*/
|
||||||
public class PluginDemo {
|
public class PluginManagerInterface {
|
||||||
|
private static final PluginManagerInterface INSTANCE = new PluginManagerInterface();
|
||||||
Logger logger = LogUtil.getLogger(this.getClass());
|
Logger logger = LogUtil.getLogger(this.getClass());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -53,34 +54,8 @@ public class PluginDemo {
|
|||||||
primaryStage.show();
|
primaryStage.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
public static PluginManagerInterface getInstance() {
|
||||||
* 创建加载插件的按钮
|
return INSTANCE;
|
||||||
*
|
|
||||||
* @param primaryStage JavaFX的主舞台
|
|
||||||
* @param fileChooser 文件选择器
|
|
||||||
* @param pluginManager 插件管理器
|
|
||||||
* @return 加载插件的按钮
|
|
||||||
*/
|
|
||||||
private Button createLoadButton(Stage primaryStage, FileChooser fileChooser, PluginManager pluginManager) {
|
|
||||||
Button loadButton = new Button("加载插件");
|
|
||||||
loadButton.setOnAction(event -> {
|
|
||||||
try {
|
|
||||||
File selectedFile = fileChooser.showOpenDialog(primaryStage);
|
|
||||||
if (selectedFile != null) {
|
|
||||||
String pluginFilePath = selectedFile.getAbsolutePath();
|
|
||||||
PluginLoader.getInstance().loadPlugins(pluginFilePath);
|
|
||||||
|
|
||||||
// 更新插件信息显示
|
|
||||||
displayPluginInfo(primaryStage, pluginManager);
|
|
||||||
} else {
|
|
||||||
PopUpUtil.infoAlert(null, null, "未找到插件!", null, null);
|
|
||||||
logger.info("未找到插件!");
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("加载插件失败!", e);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return loadButton;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -109,4 +84,34 @@ public class PluginDemo {
|
|||||||
infoStage.initOwner(primaryStage);
|
infoStage.initOwner(primaryStage);
|
||||||
infoStage.show();
|
infoStage.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建加载插件的按钮
|
||||||
|
*
|
||||||
|
* @param primaryStage JavaFX的主舞台
|
||||||
|
* @param fileChooser 文件选择器
|
||||||
|
* @param pluginManager 插件管理器
|
||||||
|
* @return 加载插件的按钮
|
||||||
|
*/
|
||||||
|
private Button createLoadButton(Stage primaryStage, FileChooser fileChooser, PluginManager pluginManager) {
|
||||||
|
Button loadButton = new Button("加载插件");
|
||||||
|
loadButton.setOnAction(event -> {
|
||||||
|
try {
|
||||||
|
File selectedFile = fileChooser.showOpenDialog(primaryStage);
|
||||||
|
if (selectedFile != null) {
|
||||||
|
String pluginFilePath = selectedFile.getAbsolutePath();
|
||||||
|
PluginLoader.getInstance().loadPluginByPath(pluginFilePath);
|
||||||
|
|
||||||
|
// 更新插件信息显示
|
||||||
|
displayPluginInfo(primaryStage, pluginManager);
|
||||||
|
} else {
|
||||||
|
PopUpUtil.infoAlert(null, null, "未找到插件!", null, null);
|
||||||
|
logger.info("未找到插件!");
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.error("加载插件失败!", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return loadButton;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -8,18 +8,9 @@ import java.util.Map;
|
|||||||
* <p>
|
* <p>
|
||||||
* 描述插件的基本功能。
|
* 描述插件的基本功能。
|
||||||
*
|
*
|
||||||
* @author luke
|
* @author luke gewuyou
|
||||||
*/
|
*/
|
||||||
public interface Plugin extends PluginCategory {
|
public interface Plugin {
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取插件的显示名称
|
|
||||||
*
|
|
||||||
* @return 插件的显示名称
|
|
||||||
*/
|
|
||||||
String getDisplayName();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化插件
|
* 初始化插件
|
||||||
*/
|
*/
|
||||||
|
|||||||
@ -1,18 +0,0 @@
|
|||||||
package org.jcnc.jnotepad.plugin.interfaces;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 插件类别接口
|
|
||||||
* <p>
|
|
||||||
* 描述插件的类别信息。
|
|
||||||
*
|
|
||||||
* @author luke
|
|
||||||
*/
|
|
||||||
public interface PluginCategory {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取插件类别的名称
|
|
||||||
*
|
|
||||||
* @return 插件类别的名称
|
|
||||||
*/
|
|
||||||
String getCategoryName();
|
|
||||||
}
|
|
||||||
@ -12,6 +12,7 @@ import javafx.scene.layout.StackPane;
|
|||||||
import javafx.scene.layout.VBox;
|
import javafx.scene.layout.VBox;
|
||||||
import javafx.scene.paint.Color;
|
import javafx.scene.paint.Color;
|
||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
|
import org.jcnc.jnotepad.plugin.PluginManagerInterface;
|
||||||
import org.jcnc.jnotepad.ui.module.CustomSetButton;
|
import org.jcnc.jnotepad.ui.module.CustomSetButton;
|
||||||
import org.jcnc.jnotepad.ui.module.SettingsComponent;
|
import org.jcnc.jnotepad.ui.module.SettingsComponent;
|
||||||
import org.jcnc.jnotepad.util.UiUtil;
|
import org.jcnc.jnotepad.util.UiUtil;
|
||||||
@ -31,6 +32,7 @@ public class SetStage extends Stage {
|
|||||||
public static final String GENERAL_SETTING_2 = "常规设置项2";
|
public static final String GENERAL_SETTING_2 = "常规设置项2";
|
||||||
public static final String APPEARANCE_SETTING_1 = "外观设置项1";
|
public static final String APPEARANCE_SETTING_1 = "外观设置项1";
|
||||||
public static final String APPEARANCE_SETTING_2 = "外观设置项2";
|
public static final String APPEARANCE_SETTING_2 = "外观设置项2";
|
||||||
|
public static final String PLUGINS = "插件";
|
||||||
public static final String SECURITY_SETTING_1 = "安全设置项1";
|
public static final String SECURITY_SETTING_1 = "安全设置项1";
|
||||||
public static final String SECURITY_SETTING_2 = "安全设置项2";
|
public static final String SECURITY_SETTING_2 = "安全设置项2";
|
||||||
|
|
||||||
@ -92,7 +94,6 @@ public class SetStage extends Stage {
|
|||||||
applicationButton.getStyleClass().addAll(Styles.SMALL);
|
applicationButton.getStyleClass().addAll(Styles.SMALL);
|
||||||
bottomBox.getChildren().addAll(confirmButton, cancelButton, applicationButton);
|
bottomBox.getChildren().addAll(confirmButton, cancelButton, applicationButton);
|
||||||
|
|
||||||
|
|
||||||
BorderPane root = new BorderPane();
|
BorderPane root = new BorderPane();
|
||||||
root.setCenter(splitPane);
|
root.setCenter(splitPane);
|
||||||
root.setBottom(bottomBox);
|
root.setBottom(bottomBox);
|
||||||
@ -136,6 +137,9 @@ public class SetStage extends Stage {
|
|||||||
|
|
||||||
TreeItem<String> securityItem1 = new TreeItem<>("安全设置项1");
|
TreeItem<String> securityItem1 = new TreeItem<>("安全设置项1");
|
||||||
TreeItem<String> securityItem2 = new TreeItem<>("安全设置项2");
|
TreeItem<String> securityItem2 = new TreeItem<>("安全设置项2");
|
||||||
|
|
||||||
|
TreeItem<String> pluginsItem = new TreeItem<>(PLUGINS);
|
||||||
|
|
||||||
securityItem.getChildren().add(securityItem1);
|
securityItem.getChildren().add(securityItem1);
|
||||||
securityItem.getChildren().add(securityItem2);
|
securityItem.getChildren().add(securityItem2);
|
||||||
|
|
||||||
@ -147,6 +151,7 @@ public class SetStage extends Stage {
|
|||||||
root.getChildren().add(appearanceItem);
|
root.getChildren().add(appearanceItem);
|
||||||
root.getChildren().add(securityItem);
|
root.getChildren().add(securityItem);
|
||||||
root.getChildren().add(developerItem);
|
root.getChildren().add(developerItem);
|
||||||
|
root.getChildren().add(pluginsItem);
|
||||||
TreeView<String> treeView = new TreeView<>(root);
|
TreeView<String> treeView = new TreeView<>(root);
|
||||||
treeView.setShowRoot(false);
|
treeView.setShowRoot(false);
|
||||||
|
|
||||||
@ -170,10 +175,21 @@ public class SetStage extends Stage {
|
|||||||
case SECURITY_SETTING_1 -> createSecuritySettingsLayout1();
|
case SECURITY_SETTING_1 -> createSecuritySettingsLayout1();
|
||||||
case SECURITY_SETTING_2 -> createSecuritySettingsLayout2();
|
case SECURITY_SETTING_2 -> createSecuritySettingsLayout2();
|
||||||
case DEVELOPER_DEBUG_PAGE -> createDevelopersDebugPageLayouts();
|
case DEVELOPER_DEBUG_PAGE -> createDevelopersDebugPageLayouts();
|
||||||
|
case PLUGINS -> createPluginsLayout();
|
||||||
default -> null;
|
default -> null;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Node createPluginsLayout() {
|
||||||
|
VBox generalLayout = new VBox(10);
|
||||||
|
generalLayout.setPadding(new Insets(25));
|
||||||
|
PluginManagerInterface pluginManagerInterface = new PluginManagerInterface();
|
||||||
|
Stage stage = new Stage();
|
||||||
|
stage.getIcons().add(UiUtil.getAppIcon());
|
||||||
|
pluginManagerInterface.start(stage);
|
||||||
|
return generalLayout;
|
||||||
|
}
|
||||||
|
|
||||||
private Node createDevelopersDebugPageLayouts() {
|
private Node createDevelopersDebugPageLayouts() {
|
||||||
VBox generalLayout = new VBox(10);
|
VBox generalLayout = new VBox(10);
|
||||||
generalLayout.setPadding(new Insets(25));
|
generalLayout.setPadding(new Insets(25));
|
||||||
|
|||||||
@ -10,8 +10,9 @@ import org.jcnc.jnotepad.controller.event.handler.menubar.*;
|
|||||||
import org.jcnc.jnotepad.controller.event.handler.util.SetBtn;
|
import org.jcnc.jnotepad.controller.event.handler.util.SetBtn;
|
||||||
import org.jcnc.jnotepad.controller.i18n.LocalizationController;
|
import org.jcnc.jnotepad.controller.i18n.LocalizationController;
|
||||||
import org.jcnc.jnotepad.model.entity.ShortcutKey;
|
import org.jcnc.jnotepad.model.entity.ShortcutKey;
|
||||||
import org.jcnc.jnotepad.plugin.PluginDemo;
|
import org.jcnc.jnotepad.plugin.PluginManagerInterface;
|
||||||
import org.jcnc.jnotepad.util.LogUtil;
|
import org.jcnc.jnotepad.util.LogUtil;
|
||||||
|
import org.jcnc.jnotepad.util.UiUtil;
|
||||||
import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTab;
|
import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTab;
|
||||||
import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTabPane;
|
import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTabPane;
|
||||||
import org.jcnc.jnotepad.views.root.left.sidebar.tools.SidebarToolBar;
|
import org.jcnc.jnotepad.views.root.left.sidebar.tools.SidebarToolBar;
|
||||||
@ -267,8 +268,10 @@ public class TopMenuBar extends MenuBar {
|
|||||||
|
|
||||||
addItem = new MenuItem();
|
addItem = new MenuItem();
|
||||||
addItem.setOnAction(event -> {
|
addItem.setOnAction(event -> {
|
||||||
PluginDemo pluginDemo = new PluginDemo();
|
PluginManagerInterface pluginManagerInterface = PluginManagerInterface.getInstance();
|
||||||
pluginDemo.start(new Stage());
|
Stage stage = new Stage();
|
||||||
|
stage.getIcons().add(UiUtil.getAppIcon());
|
||||||
|
pluginManagerInterface.start(stage);
|
||||||
});
|
});
|
||||||
UiResourceBundle.bindStringProperty(addItem.textProperty(), ADD_PLUGIN);
|
UiResourceBundle.bindStringProperty(addItem.textProperty(), ADD_PLUGIN);
|
||||||
itemMap.put("addItem", addItem);
|
itemMap.put("addItem", addItem);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user