commit
32689ddbb3
11
pom.xml
11
pom.xml
@ -16,16 +16,26 @@
|
|||||||
<javafx.version>20.0.2</javafx.version>
|
<javafx.version>20.0.2</javafx.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
<!-- https://mvnrepository.com/artifact/org.fxmisc.richtext/richtextfx -->
|
||||||
|
<!--JavaFX 的富文本区域-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.fxmisc.richtext</groupId>
|
||||||
|
<artifactId>richtextfx</artifactId>
|
||||||
|
<version>0.11.1</version>
|
||||||
|
</dependency>
|
||||||
|
<!--图标库主依赖-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.kordamp.ikonli</groupId>
|
<groupId>org.kordamp.ikonli</groupId>
|
||||||
<artifactId>ikonli-javafx</artifactId>
|
<artifactId>ikonli-javafx</artifactId>
|
||||||
<version>12.3.1</version>
|
<version>12.3.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!--蚂蚁图标库-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.kordamp.ikonli</groupId>
|
<groupId>org.kordamp.ikonli</groupId>
|
||||||
<artifactId>ikonli-antdesignicons-pack</artifactId>
|
<artifactId>ikonli-antdesignicons-pack</artifactId>
|
||||||
<version>12.3.1</version>
|
<version>12.3.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!--亚特兰大fx主题-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>io.github.mkpaz</groupId>
|
<groupId>io.github.mkpaz</groupId>
|
||||||
<artifactId>atlantafx-base</artifactId>
|
<artifactId>atlantafx-base</artifactId>
|
||||||
@ -66,6 +76,7 @@
|
|||||||
<artifactId>logback-classic</artifactId>
|
<artifactId>logback-classic</artifactId>
|
||||||
<version>1.4.11</version>
|
<version>1.4.11</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<!--国际化依赖-->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.ibm.icu</groupId>
|
<groupId>com.ibm.icu</groupId>
|
||||||
<artifactId>icu4j</artifactId>
|
<artifactId>icu4j</artifactId>
|
||||||
|
|||||||
@ -15,6 +15,7 @@ module org.jcnc.jnotepad {
|
|||||||
requires org.kordamp.ikonli.core;
|
requires org.kordamp.ikonli.core;
|
||||||
requires org.kordamp.ikonli.javafx;
|
requires org.kordamp.ikonli.javafx;
|
||||||
requires org.kordamp.ikonli.antdesignicons;
|
requires org.kordamp.ikonli.antdesignicons;
|
||||||
|
requires org.fxmisc.richtext;
|
||||||
exports org.jcnc.jnotepad;
|
exports org.jcnc.jnotepad;
|
||||||
exports org.jcnc.jnotepad.model.enums;
|
exports org.jcnc.jnotepad.model.enums;
|
||||||
exports org.jcnc.jnotepad.app.config;
|
exports org.jcnc.jnotepad.app.config;
|
||||||
@ -29,6 +30,7 @@ module org.jcnc.jnotepad {
|
|||||||
exports org.jcnc.jnotepad.common.interfaces;
|
exports org.jcnc.jnotepad.common.interfaces;
|
||||||
opens org.jcnc.jnotepad.app.config;
|
opens org.jcnc.jnotepad.app.config;
|
||||||
exports org.jcnc.jnotepad.views.root.center.main.bottom.status;
|
exports org.jcnc.jnotepad.views.root.center.main.bottom.status;
|
||||||
|
exports org.jcnc.jnotepad.plugin.interfaces;
|
||||||
exports org.jcnc.jnotepad.ui.dialog;
|
exports org.jcnc.jnotepad.ui.dialog;
|
||||||
exports org.jcnc.jnotepad.ui.dialog.interfaces;
|
exports org.jcnc.jnotepad.ui.dialog.interfaces;
|
||||||
exports org.jcnc.jnotepad.model.entity;
|
exports org.jcnc.jnotepad.model.entity;
|
||||||
|
|||||||
@ -121,7 +121,7 @@ public class OpenFile implements EventHandler<ActionEvent> {
|
|||||||
String text = textBuilder.toString();
|
String text = textBuilder.toString();
|
||||||
LogUtil.getLogger(this.getClass()).info("已调用读取文件功能");
|
LogUtil.getLogger(this.getClass()).info("已调用读取文件功能");
|
||||||
Platform.runLater(() -> {
|
Platform.runLater(() -> {
|
||||||
textArea.getMainTextArea().setText(text);
|
textArea.appendText(text);
|
||||||
CenterTab tab = createNewTab(file.getName(), textArea, encoding);
|
CenterTab tab = createNewTab(file.getName(), textArea, encoding);
|
||||||
// 设置当前标签页关联本地文件
|
// 设置当前标签页关联本地文件
|
||||||
tab.setRelevance(true);
|
tab.setRelevance(true);
|
||||||
|
|||||||
@ -9,7 +9,7 @@ public class PluginInfo {
|
|||||||
/**
|
/**
|
||||||
* 插件名称
|
* 插件名称
|
||||||
*/
|
*/
|
||||||
private String pluginName;
|
private String name;
|
||||||
/**
|
/**
|
||||||
* 插件版本
|
* 插件版本
|
||||||
*/
|
*/
|
||||||
@ -19,12 +19,17 @@ public class PluginInfo {
|
|||||||
*/
|
*/
|
||||||
private boolean enabled;
|
private boolean enabled;
|
||||||
|
|
||||||
public String getPluginName() {
|
/**
|
||||||
return pluginName;
|
* 主类名称
|
||||||
|
*/
|
||||||
|
private String mainClass;
|
||||||
|
|
||||||
|
public String getName() {
|
||||||
|
return name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPluginName(String pluginName) {
|
public void setName(String name) {
|
||||||
this.pluginName = pluginName;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getVersion() {
|
public String getVersion() {
|
||||||
@ -42,4 +47,12 @@ public class PluginInfo {
|
|||||||
public void setEnabled(boolean enabled) {
|
public void setEnabled(boolean enabled) {
|
||||||
this.enabled = enabled;
|
this.enabled = enabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getMainClass() {
|
||||||
|
return mainClass;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMainClass(String mainClass) {
|
||||||
|
this.mainClass = mainClass;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,58 +0,0 @@
|
|||||||
package org.jcnc.jnotepad.plugin;
|
|
||||||
|
|
||||||
import org.jcnc.jnotepad.plugin.interfaces.Plugin;
|
|
||||||
import org.jcnc.jnotepad.util.LogUtil;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新按钮插件
|
|
||||||
*
|
|
||||||
* @author luke
|
|
||||||
*/
|
|
||||||
public class ButtonPlugin implements Plugin {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getCategoryName() {
|
|
||||||
return "新按钮插件";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String getDisplayName() {
|
|
||||||
return "新按钮";
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 初始化插件
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void initialize() {
|
|
||||||
LogUtil.getLogger(this.getClass()).info("新按钮插件初始化了!");
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void execute() {
|
|
||||||
// 在这里实现新按钮插件的逻辑
|
|
||||||
LogUtil.getLogger(this.getClass()).info("新按钮插件执行了!");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取插件的配置参数
|
|
||||||
*
|
|
||||||
* @return 插件的配置参数
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public Map<String, Object> getConfig() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 设置插件的配置参数
|
|
||||||
*
|
|
||||||
* @param config 插件的配置参数
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public void setConfig(Map<String, Object> config) {
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -8,7 +8,9 @@ import javafx.stage.FileChooser;
|
|||||||
import javafx.stage.Stage;
|
import javafx.stage.Stage;
|
||||||
import org.jcnc.jnotepad.ui.dialog.factory.impl.BasicFileChooserFactory;
|
import org.jcnc.jnotepad.ui.dialog.factory.impl.BasicFileChooserFactory;
|
||||||
import org.jcnc.jnotepad.util.LogUtil;
|
import org.jcnc.jnotepad.util.LogUtil;
|
||||||
|
import org.jcnc.jnotepad.util.PopUpUtil;
|
||||||
import org.jcnc.jnotepad.util.UiUtil;
|
import org.jcnc.jnotepad.util.UiUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -22,6 +24,7 @@ import java.util.Map;
|
|||||||
* @author luke
|
* @author luke
|
||||||
*/
|
*/
|
||||||
public class PluginDemo {
|
public class PluginDemo {
|
||||||
|
Logger logger = LogUtil.getLogger(this.getClass());
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 启动插件演示界面
|
* 启动插件演示界面
|
||||||
@ -53,8 +56,8 @@ public class PluginDemo {
|
|||||||
/**
|
/**
|
||||||
* 创建加载插件的按钮
|
* 创建加载插件的按钮
|
||||||
*
|
*
|
||||||
* @param primaryStage JavaFX的主舞台
|
* @param primaryStage JavaFX的主舞台
|
||||||
* @param fileChooser 文件选择器
|
* @param fileChooser 文件选择器
|
||||||
* @param pluginManager 插件管理器
|
* @param pluginManager 插件管理器
|
||||||
* @return 加载插件的按钮
|
* @return 加载插件的按钮
|
||||||
*/
|
*/
|
||||||
@ -65,13 +68,16 @@ public class PluginDemo {
|
|||||||
File selectedFile = fileChooser.showOpenDialog(primaryStage);
|
File selectedFile = fileChooser.showOpenDialog(primaryStage);
|
||||||
if (selectedFile != null) {
|
if (selectedFile != null) {
|
||||||
String pluginFilePath = selectedFile.getAbsolutePath();
|
String pluginFilePath = selectedFile.getAbsolutePath();
|
||||||
pluginManager.loadPlugins(pluginFilePath);
|
PluginLoader.getInstance().loadPlugins(pluginFilePath);
|
||||||
|
|
||||||
// 更新插件信息显示
|
// 更新插件信息显示
|
||||||
displayPluginInfo(primaryStage, pluginManager);
|
displayPluginInfo(primaryStage, pluginManager);
|
||||||
|
} else {
|
||||||
|
PopUpUtil.infoAlert(null, null, "未找到插件!", null, null);
|
||||||
|
logger.info("未找到插件!");
|
||||||
}
|
}
|
||||||
} catch (Exception ignored) {
|
} catch (Exception e) {
|
||||||
LogUtil.getLogger(this.getClass()).info("未加载插件!");
|
logger.error("加载插件失败!", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return loadButton;
|
return loadButton;
|
||||||
|
|||||||
98
src/main/java/org/jcnc/jnotepad/plugin/PluginLoader.java
Normal file
98
src/main/java/org/jcnc/jnotepad/plugin/PluginLoader.java
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
package org.jcnc.jnotepad.plugin;
|
||||||
|
|
||||||
|
import org.jcnc.jnotepad.exception.AppException;
|
||||||
|
import org.jcnc.jnotepad.model.entity.PluginInfo;
|
||||||
|
import org.jcnc.jnotepad.plugin.interfaces.Plugin;
|
||||||
|
import org.jcnc.jnotepad.util.JsonUtil;
|
||||||
|
import org.jcnc.jnotepad.util.LogUtil;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.lang.reflect.InvocationTargetException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.net.URLClassLoader;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.jar.JarFile;
|
||||||
|
import java.util.zip.ZipEntry;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 插件加载类
|
||||||
|
*
|
||||||
|
* @author gewuyou
|
||||||
|
*/
|
||||||
|
public class PluginLoader {
|
||||||
|
private static final PluginLoader INSTANCE = new PluginLoader();
|
||||||
|
Logger logger = LogUtil.getLogger(this.getClass());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从插件jar包中读取 json 文件到 PluginInfo 类
|
||||||
|
*
|
||||||
|
* @param pluginJar jar 包
|
||||||
|
*/
|
||||||
|
private static PluginInfo readPlugin(File pluginJar) throws IOException {
|
||||||
|
InputStream is;
|
||||||
|
StringBuilder sb;
|
||||||
|
try (JarFile jarFile = new JarFile(pluginJar)) {
|
||||||
|
ZipEntry zipEntry = jarFile.getEntry("META-INF/plugin.json");
|
||||||
|
is = jarFile.getInputStream(zipEntry);
|
||||||
|
|
||||||
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(is))) {
|
||||||
|
String temp;
|
||||||
|
sb = new StringBuilder();
|
||||||
|
while ((temp = br.readLine()) != null) {
|
||||||
|
sb.append(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return JsonUtil.OBJECT_MAPPER.readValue(sb.toString(), PluginInfo.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PluginLoader getInstance() {
|
||||||
|
return INSTANCE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加载插件
|
||||||
|
*
|
||||||
|
* @param pluginFilePath 插件文件的路径
|
||||||
|
*/
|
||||||
|
public void loadPlugins(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);
|
||||||
|
if (file.exists() && file.isFile()) {
|
||||||
|
try {
|
||||||
|
PluginInfo pluginInfo = readPlugin(file);
|
||||||
|
pluginInfos.put(pluginInfo.getName(), pluginInfo);
|
||||||
|
// 创建URLClassLoader以加载Jar文件中的类
|
||||||
|
Class<?> pluginClass;
|
||||||
|
try (URLClassLoader classLoader = new URLClassLoader(new URL[]{file.toURI().toURL()})) {
|
||||||
|
pluginClass = classLoader.loadClass(pluginInfo.getMainClass());
|
||||||
|
}
|
||||||
|
if (pluginClass == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Plugin plugin;
|
||||||
|
plugin = (Plugin) pluginClass.getDeclaredConstructor().newInstance();
|
||||||
|
plugins.add(plugin);
|
||||||
|
// 添加插件到类别中
|
||||||
|
String categoryName = plugin.getCategoryName();
|
||||||
|
String displayName = plugin.getDisplayName();
|
||||||
|
categories.computeIfAbsent(categoryName, k -> new ArrayList<>()).add(displayName);
|
||||||
|
} catch (IOException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
|
||||||
|
throw new AppException(e);
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
logger.error("无法找到对应的插件类!", e);
|
||||||
|
} catch (NoSuchMethodException e) {
|
||||||
|
logger.error("插件类中没有找到指定方法!", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
LogUtil.getLogger(this.getClass()).info("PluginInfo file not found: {}", pluginFilePath);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,14 +1,10 @@
|
|||||||
package org.jcnc.jnotepad.plugin;
|
package org.jcnc.jnotepad.plugin;
|
||||||
|
|
||||||
|
import org.jcnc.jnotepad.model.entity.PluginInfo;
|
||||||
import org.jcnc.jnotepad.plugin.interfaces.Plugin;
|
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.net.MalformedURLException;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.net.URLClassLoader;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -33,6 +29,11 @@ public class PluginManager {
|
|||||||
* 插件类别
|
* 插件类别
|
||||||
*/
|
*/
|
||||||
private final Map<String, List<String>> categories = new HashMap<>();
|
private final Map<String, List<String>> categories = new HashMap<>();
|
||||||
|
/**
|
||||||
|
* 插件信息
|
||||||
|
*/
|
||||||
|
|
||||||
|
private final Map<String, PluginInfo> pluginInfos = new HashMap<>();
|
||||||
|
|
||||||
private PluginManager() {
|
private PluginManager() {
|
||||||
|
|
||||||
@ -42,47 +43,6 @@ public class PluginManager {
|
|||||||
return INSTANCE;
|
return INSTANCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 加载插件
|
|
||||||
*
|
|
||||||
* @param pluginFilePath 插件文件的路径
|
|
||||||
*/
|
|
||||||
public void loadPlugins(String pluginFilePath) {
|
|
||||||
File file = new File(pluginFilePath);
|
|
||||||
if (file.exists() && file.isFile()) {
|
|
||||||
// 创建URLClassLoader以加载Jar文件中的类
|
|
||||||
Class<?> pluginClass = null;
|
|
||||||
try (URLClassLoader classLoader = new URLClassLoader(new URL[]{file.toURI().toURL()})) {
|
|
||||||
pluginClass = classLoader.loadClass("org.jcnc.jnotepad.plugin.ButtonPlugin");
|
|
||||||
} catch (ClassNotFoundException e) {
|
|
||||||
logger.error("无法找到对应的插件类!", e);
|
|
||||||
} catch (MalformedURLException e) {
|
|
||||||
logger.error("无法创建URL格式错误!", e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.error("IO异常!", e);
|
|
||||||
}
|
|
||||||
if (pluginClass == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Plugin plugin = null;
|
|
||||||
try {
|
|
||||||
plugin = (Plugin) pluginClass.getDeclaredConstructor().newInstance();
|
|
||||||
} catch (Exception e) {
|
|
||||||
logger.error("发生异常!", e);
|
|
||||||
}
|
|
||||||
if (plugin == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
plugins.add(plugin);
|
|
||||||
|
|
||||||
// 添加插件到类别中
|
|
||||||
String categoryName = plugin.getCategoryName();
|
|
||||||
String displayName = plugin.getDisplayName();
|
|
||||||
categories.computeIfAbsent(categoryName, k -> new ArrayList<>()).add(displayName);
|
|
||||||
} else {
|
|
||||||
LogUtil.getLogger(this.getClass()).info("PluginInfo file not found: {}", pluginFilePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 卸载插件
|
* 卸载插件
|
||||||
@ -122,4 +82,12 @@ public class PluginManager {
|
|||||||
public Map<String, List<String>> getLoadedPluginsByCategory() {
|
public Map<String, List<String>> getLoadedPluginsByCategory() {
|
||||||
return categories;
|
return categories;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<Plugin> getPlugins() {
|
||||||
|
return plugins;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, PluginInfo> getPluginInfos() {
|
||||||
|
return pluginInfos;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,7 @@
|
|||||||
package org.jcnc.jnotepad.ui.module;
|
package org.jcnc.jnotepad.ui.module;
|
||||||
|
|
||||||
import javafx.beans.property.StringProperty;
|
import org.fxmisc.richtext.CodeArea;
|
||||||
import javafx.scene.control.TextArea;
|
import org.fxmisc.richtext.LineNumberFactory;
|
||||||
import javafx.scene.layout.BorderPane;
|
|
||||||
import org.jcnc.jnotepad.controller.config.AppConfigController;
|
|
||||||
import org.jcnc.jnotepad.util.LogUtil;
|
import org.jcnc.jnotepad.util.LogUtil;
|
||||||
import org.jcnc.jnotepad.views.root.center.main.bottom.status.BottomStatusBox;
|
import org.jcnc.jnotepad.views.root.center.main.bottom.status.BottomStatusBox;
|
||||||
import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTab;
|
import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTab;
|
||||||
@ -22,84 +20,36 @@ import java.io.IOException;
|
|||||||
*
|
*
|
||||||
* @author luke
|
* @author luke
|
||||||
*/
|
*/
|
||||||
public class LineNumberTextArea extends BorderPane {
|
public class LineNumberTextArea extends CodeArea {
|
||||||
|
|
||||||
/**
|
|
||||||
* 用于确定行号区域宽度的大小表格,每个元素表示不同的行数范围
|
|
||||||
*/
|
|
||||||
private static final int[] SIZE_TABLE = {9, 99, 999, 9999, 99999, 999999, 9999999,
|
|
||||||
99999999, 999999999, Integer.MAX_VALUE};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 用于记录日志的静态Logger对象
|
* 用于记录日志的静态Logger对象
|
||||||
*/
|
*/
|
||||||
private static final Logger logger = LogUtil.getLogger(LineNumberTextArea.class);
|
private static final Logger logger = LogUtil.getLogger(LineNumberTextArea.class);
|
||||||
|
|
||||||
/**
|
|
||||||
* 行号区域的最小宽度
|
|
||||||
*/
|
|
||||||
private static final int MIN_LINE_NUMBER_WIDTH = 30;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 主文本区域的TextArea实例
|
|
||||||
*/
|
|
||||||
private final TextArea mainTextArea = new TextArea();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 行号区域的TextArea实例
|
|
||||||
*/
|
|
||||||
private final TextArea lineNumberArea = new TextArea();
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
* <p>
|
* <p>
|
||||||
* 用于创建 LineNumberTextArea 对象
|
* 用于创建 LineNumberTextArea 对象
|
||||||
*/
|
*/
|
||||||
public LineNumberTextArea() {
|
public LineNumberTextArea() {
|
||||||
// 设置主文本区域是否自动换行,根据应用配置决定
|
|
||||||
mainTextArea.setWrapText(AppConfigController.getInstance().getAutoLineConfig());
|
|
||||||
|
|
||||||
// 设置行号区域不可编辑
|
|
||||||
lineNumberArea.setEditable(false);
|
|
||||||
|
|
||||||
// 设置行号区域的首选宽度和最小宽度为最小行号宽度
|
|
||||||
lineNumberArea.setPrefWidth(MIN_LINE_NUMBER_WIDTH);
|
|
||||||
lineNumberArea.setMinWidth(MIN_LINE_NUMBER_WIDTH);
|
|
||||||
|
|
||||||
// 为行号区域和主文本区域添加CSS样式类
|
|
||||||
lineNumberArea.getStyleClass().add("text-line-number");
|
|
||||||
mainTextArea.getStyleClass().add("main-text-area");
|
|
||||||
|
|
||||||
// 设置 LineNumberTextArea 的样式,包括边框和背景颜色
|
// 设置 LineNumberTextArea 的样式,包括边框和背景颜色
|
||||||
this.setStyle(
|
this.setStyle(
|
||||||
"-fx-border-color:white;" +
|
"-fx-border-color:white;" +
|
||||||
"-fx-background-color:white"
|
"-fx-background-color:white"
|
||||||
);
|
);
|
||||||
|
this.setParagraphGraphicFactory(LineNumberFactory.get(this));
|
||||||
// 初始化监听器,用于处理事件
|
|
||||||
initListeners();
|
initListeners();
|
||||||
// 将主文本区域设置为中央内容,将行号区域设置为左侧内容
|
|
||||||
setCenter(mainTextArea);
|
|
||||||
setLeft(lineNumberArea);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 初始化监听器方法
|
* 初始化监听器方法
|
||||||
*/
|
*/
|
||||||
private void initListeners() {
|
private void initListeners() {
|
||||||
// 监听主要文本区域的滚动位置变化
|
|
||||||
mainTextArea.scrollTopProperty().addListener((observable, oldValue, newValue) -> lineNumberArea.setScrollTop(mainTextArea.getScrollTop()));
|
|
||||||
// 监听行号文本区域的滚动位置变化
|
|
||||||
lineNumberArea.scrollTopProperty().addListener((observable, oldValue, newValue) -> mainTextArea.setScrollTop(lineNumberArea.getScrollTop()));
|
|
||||||
// 监听行号文本区域的文本变化
|
|
||||||
lineNumberArea.textProperty().addListener((observable, oldValue, newValue) -> updateLineNumberWidth());
|
|
||||||
// 监听主要文本区域的光标位置变化
|
|
||||||
this.mainTextArea.caretPositionProperty().addListener((caretObservable, oldPosition, newPosition) -> BottomStatusBox.getInstance().updateWordCountStatusLabel());
|
|
||||||
// 监听主要文本区域的文本变化
|
// 监听主要文本区域的文本变化
|
||||||
this.textProperty().addListener((observable, oldValue, newValue) -> {
|
this.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||||
updateLineNumberArea();
|
|
||||||
BottomStatusBox.getInstance().updateWordCountStatusLabel();
|
BottomStatusBox.getInstance().updateWordCountStatusLabel();
|
||||||
save();
|
save();
|
||||||
});
|
});
|
||||||
@ -121,7 +71,7 @@ public class LineNumberTextArea extends BorderPane {
|
|||||||
File file = (File) tab.getUserData();
|
File file = (File) tab.getUserData();
|
||||||
|
|
||||||
// 获取主文本区域中的文本内容
|
// 获取主文本区域中的文本内容
|
||||||
String newValue = this.mainTextArea.getText();
|
String newValue = this.getText();
|
||||||
|
|
||||||
// 如果文件对象为空,记录警告信息并返回,不执行保存操作
|
// 如果文件对象为空,记录警告信息并返回,不执行保存操作
|
||||||
if (file == null) {
|
if (file == null) {
|
||||||
@ -140,90 +90,4 @@ public class LineNumberTextArea extends BorderPane {
|
|||||||
LogUtil.getLogger(this.getClass()).info("已忽视IO异常!");
|
LogUtil.getLogger(this.getClass()).info("已忽视IO异常!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新行号宽度方法
|
|
||||||
*/
|
|
||||||
private void updateLineNumberWidth() {
|
|
||||||
// 获取主文本区域的段落数量,即文本的行数
|
|
||||||
int numOfLines = mainTextArea.getParagraphs().size();
|
|
||||||
|
|
||||||
// 初始化一个计数器,用于确定适合的行号宽度
|
|
||||||
int count = 1;
|
|
||||||
|
|
||||||
// 遍历行号宽度大小的表格
|
|
||||||
for (int i = 0; i < SIZE_TABLE.length; i++) {
|
|
||||||
// 检查文本行数是否在当前表格项的范围内
|
|
||||||
if (numOfLines <= SIZE_TABLE[i]) {
|
|
||||||
// 如果是,设置计数器为当前索引+1并退出循环
|
|
||||||
count = i + 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 计算实际的行号区域宽度,确保不小于一个最小宽度值
|
|
||||||
int actualWidth = Math.max(count * 10 + 11, MIN_LINE_NUMBER_WIDTH);
|
|
||||||
|
|
||||||
// 检查实际宽度是否与当前行号区域的宽度不同
|
|
||||||
if (actualWidth != lineNumberArea.getWidth()) {
|
|
||||||
// 如果不同,设置行号区域的首选宽度为实际宽度
|
|
||||||
lineNumberArea.setPrefWidth(actualWidth);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取主要文本区域的text属性
|
|
||||||
*
|
|
||||||
* @return 主要文本区域的text属性
|
|
||||||
*/
|
|
||||||
public StringProperty textProperty() {
|
|
||||||
return mainTextArea.textProperty();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新行号区域方法
|
|
||||||
*/
|
|
||||||
private void updateLineNumberArea() {
|
|
||||||
// 获取主文本区域的垂直滚动位置
|
|
||||||
double mainTextAreaScrollTop = mainTextArea.getScrollTop();
|
|
||||||
|
|
||||||
// 获取行号区域的垂直滚动位置
|
|
||||||
double lineNumberAreaScrollTop = lineNumberArea.getScrollTop();
|
|
||||||
|
|
||||||
// 获取主文本区域的段落数量,即文本的行数
|
|
||||||
int numOfLines = mainTextArea.getParagraphs().size();
|
|
||||||
|
|
||||||
// 用于构建行号文本的字符串构建器
|
|
||||||
StringBuilder lineNumberText = new StringBuilder();
|
|
||||||
|
|
||||||
// 循环迭代,生成行号文本,
|
|
||||||
for (int i = 1; i <= numOfLines; i++) {
|
|
||||||
// 将行号和换行符添加到字符串中
|
|
||||||
lineNumberText.append(i);
|
|
||||||
if (i != numOfLines) {
|
|
||||||
lineNumberText.append("\n");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 将生成的行号文本设置到 行号区域
|
|
||||||
lineNumberArea.setText(lineNumberText.toString());
|
|
||||||
|
|
||||||
// 恢复主文本区域的垂直滚动位置
|
|
||||||
mainTextArea.setScrollTop(mainTextAreaScrollTop);
|
|
||||||
|
|
||||||
// 恢复行号区域的垂直滚动位置
|
|
||||||
lineNumberArea.setScrollTop(lineNumberAreaScrollTop);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取主要文本区域
|
|
||||||
*
|
|
||||||
* @return 主要文本区域
|
|
||||||
*/
|
|
||||||
public TextArea getMainTextArea() {
|
|
||||||
return mainTextArea;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -3,10 +3,10 @@ package org.jcnc.jnotepad.views.root.center.main.bottom.status;
|
|||||||
import javafx.geometry.Insets;
|
import javafx.geometry.Insets;
|
||||||
import javafx.geometry.Pos;
|
import javafx.geometry.Pos;
|
||||||
import javafx.scene.control.Label;
|
import javafx.scene.control.Label;
|
||||||
import javafx.scene.control.TextArea;
|
|
||||||
import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
|
import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
|
||||||
import org.jcnc.jnotepad.common.constants.TextConstants;
|
import org.jcnc.jnotepad.common.constants.TextConstants;
|
||||||
import org.jcnc.jnotepad.ui.module.AbstractHorizontalBox;
|
import org.jcnc.jnotepad.ui.module.AbstractHorizontalBox;
|
||||||
|
import org.jcnc.jnotepad.ui.module.LineNumberTextArea;
|
||||||
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;
|
||||||
|
|
||||||
@ -94,7 +94,7 @@ public class BottomStatusBox extends AbstractHorizontalBox {
|
|||||||
if (instance.getSelected() == null) {
|
if (instance.getSelected() == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
TextArea textArea = instance.getSelected().getLineNumberTextArea().getMainTextArea();
|
LineNumberTextArea textArea = instance.getSelected().getLineNumberTextArea();
|
||||||
int caretPosition = textArea.getCaretPosition();
|
int caretPosition = textArea.getCaretPosition();
|
||||||
int row = getRow(caretPosition, textArea.getText());
|
int row = getRow(caretPosition, textArea.getText());
|
||||||
int column = getColumn(caretPosition, textArea.getText());
|
int column = getColumn(caretPosition, textArea.getText());
|
||||||
|
|||||||
@ -56,7 +56,7 @@ public class CenterTab extends Tab {
|
|||||||
|
|
||||||
public void setAutoLine(boolean autoLine) {
|
public void setAutoLine(boolean autoLine) {
|
||||||
this.autoLine = autoLine;
|
this.autoLine = autoLine;
|
||||||
lineNumberTextArea.getMainTextArea().setWrapText(autoLine);
|
lineNumberTextArea.setWrapText(autoLine);
|
||||||
}
|
}
|
||||||
|
|
||||||
public LineNumberTextArea getLineNumberTextArea() {
|
public LineNumberTextArea getLineNumberTextArea() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user