!24 feat: #I7VAM0 实现自动检测文件编码
Merge pull request !24 from songdragon/feature-I7VAM0
This commit is contained in:
commit
5aea9e96a9
1
.gitignore
vendored
1
.gitignore
vendored
@ -37,3 +37,4 @@ build/
|
||||
/src/main/JNotepad.java
|
||||
/.idea/
|
||||
/project.txt
|
||||
logs/
|
||||
|
||||
43
libs/README.md
Normal file
43
libs/README.md
Normal file
@ -0,0 +1,43 @@
|
||||
# 解决jlink error指南
|
||||
|
||||
## 现象
|
||||
|
||||
jlink时,如果出现如下错误,参考本文档
|
||||
|
||||
```
|
||||
"automatic module cannot be used with jlink"
|
||||
```
|
||||
|
||||
## 解决方法:
|
||||
|
||||
1. 为jar生成module-info.class
|
||||
|
||||
```shell
|
||||
jdeps --ignore-missing-deps --module-path <jar_dir_path> --add-modules <module_name --generate-module-info <out_dir_path> <jar_path>
|
||||
javac --patch-module <module_name>=<jar_path> <module-info.java>
|
||||
jar uf <jar_path> -C <module_name> <module-info.class>
|
||||
```
|
||||
|
||||
以本次icu4j为例,先将依赖的jar包copy到libs目录,然后执行:
|
||||
|
||||
```shell
|
||||
jdeps --ignore-missing-deps --module-path libs --add-modules com.ibm.icu --generate-module-info libs/tmpOut libs/icu4j-73.2.jar
|
||||
javac --patch-module com.ibm.icu=libs/icu4j-73.2.jar libs/tmpOut/com.ibm.icu/module-info.java
|
||||
jar uf libs/icu4j-73.2.jar -C libs/tmpOut/com.ibm.icu module-info.class
|
||||
```
|
||||
|
||||
2. pom中添加依赖
|
||||
|
||||
```xml
|
||||
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
<version>73.2</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/libs/icu4j-73.2.jar</systemPath>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
## Reference
|
||||
1. [java_jlink_automatic_module_cannot_be_used_with_jlink](https://tacosteemers.com/articles/java_jlink_automatic_module_cannot_be_used_with_jlink.html)
|
||||
BIN
libs/icu4j-73.2.jar
Normal file
BIN
libs/icu4j-73.2.jar
Normal file
Binary file not shown.
7
pom.xml
7
pom.xml
@ -55,6 +55,13 @@
|
||||
<artifactId>logback-classic</artifactId>
|
||||
<version>1.4.11</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.ibm.icu</groupId>
|
||||
<artifactId>icu4j</artifactId>
|
||||
<version>73.2</version>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/libs/icu4j-73.2.jar</systemPath>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@ -9,6 +9,8 @@ module org.jcnc.jnotepad {
|
||||
requires org.slf4j;
|
||||
requires ch.qos.logback.core;
|
||||
requires ch.qos.logback.classic;
|
||||
requires com.ibm.icu;
|
||||
|
||||
exports org.jcnc.jnotepad;
|
||||
exports org.jcnc.jnotepad.tool;
|
||||
exports org.jcnc.jnotepad.Interface;
|
||||
|
||||
@ -1,7 +1,5 @@
|
||||
package org.jcnc.jnotepad.Interface;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import org.jcnc.jnotepad.ui.LineNumberTextArea;
|
||||
|
||||
import java.io.File;
|
||||
@ -21,43 +19,6 @@ public interface ControllerInterface {
|
||||
*/
|
||||
LineNumberTextArea openAssociatedFileAndCreateTextArea(List<String> rawParameters);
|
||||
|
||||
/**
|
||||
* 获取新建文件处理事件处理程序
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
* @return 新建文件处理事件处理程序
|
||||
*/
|
||||
EventHandler<ActionEvent> getNewFileEventHandler(LineNumberTextArea textArea);
|
||||
|
||||
/**
|
||||
* 获取打开文件处理事件处理程序
|
||||
*
|
||||
* @return 打开文件处理事件处理程序
|
||||
*/
|
||||
EventHandler<ActionEvent> getOpenFileEventHandler();
|
||||
|
||||
/**
|
||||
* 获取另存为文件处理事件处理程序
|
||||
*
|
||||
* @return 另存为文件处理事件处理程序
|
||||
*/
|
||||
EventHandler<ActionEvent> getSaveAsFileEventHandler();
|
||||
|
||||
|
||||
/**
|
||||
* 自动保存
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
*/
|
||||
void autoSave(LineNumberTextArea textArea);
|
||||
|
||||
/**
|
||||
* 更新状态标签
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
*/
|
||||
void updateStatusLabel(LineNumberTextArea textArea);
|
||||
|
||||
/**
|
||||
* 打开关联文件
|
||||
*
|
||||
@ -72,36 +33,6 @@ public interface ControllerInterface {
|
||||
*/
|
||||
LineNumberTextArea getText(File file);
|
||||
|
||||
/**
|
||||
* 更新编码标签
|
||||
*
|
||||
* @param text 编码标签文本
|
||||
*/
|
||||
void upDateEncodingLabel(String text);
|
||||
|
||||
/**
|
||||
* 获取光标所在行号
|
||||
*
|
||||
* @param caretPosition 光标位置
|
||||
* @param text 文本内容
|
||||
* @return 行号
|
||||
*/
|
||||
int getRow(int caretPosition, String text);
|
||||
|
||||
/**
|
||||
* 获取光标所在列号
|
||||
*
|
||||
* @param caretPosition 光标位置
|
||||
* @param text 文本内容
|
||||
* @return 列号
|
||||
*/
|
||||
int getColumn(int caretPosition, String text);
|
||||
|
||||
/**
|
||||
* 初始化 TabPane
|
||||
*/
|
||||
void initTabPane();
|
||||
|
||||
/**
|
||||
* 更新UI和标签页
|
||||
*
|
||||
|
||||
@ -65,8 +65,7 @@ public class LunchApp extends Application {
|
||||
primaryStage.show();
|
||||
ViewManager viewManager = ViewManager.getInstance(scene);
|
||||
viewManager.initScreen(scene);
|
||||
// 初始化菜单项和标签栏
|
||||
view.initTabPane();
|
||||
// 初始化快捷键
|
||||
view.initShortcutKey();
|
||||
|
||||
// 使用线程池加载关联文件并创建文本区域
|
||||
|
||||
@ -1,32 +0,0 @@
|
||||
package org.jcnc.jnotepad.controller.event.handler;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import org.jcnc.jnotepad.app.config.GlobalConfig;
|
||||
|
||||
/**
|
||||
* 换行事件处理,针对当前选中tab进行格式化。<br/>
|
||||
* 配置变更时:<br/>
|
||||
* 1. 更新内存全局配置<br/>
|
||||
* 2. 对当前tab生效配置。每次tab切换,根据全局配置设置进行变更<br/>
|
||||
* <p>
|
||||
* 用于在文本区域中插入一个换行符。
|
||||
* @see GlobalConfig
|
||||
*
|
||||
* @deprecated 事件处理将使用item的listener实现
|
||||
*
|
||||
* @author 许轲
|
||||
*/
|
||||
@Deprecated
|
||||
public class LineFeed implements EventHandler<ActionEvent> {
|
||||
/**
|
||||
* 处理事件的方法,将一个换行符插入到文本区域的末尾。
|
||||
*
|
||||
* @param event 触发的事件对象
|
||||
*/
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
//
|
||||
|
||||
}
|
||||
}
|
||||
@ -2,8 +2,8 @@ package org.jcnc.jnotepad.controller.event.handler;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import org.jcnc.jnotepad.controller.manager.Controller;
|
||||
import org.jcnc.jnotepad.ui.LineNumberTextArea;
|
||||
import org.jcnc.jnotepad.ui.status.JNotepadStatusBox;
|
||||
import org.jcnc.jnotepad.ui.tab.JNotepadTab;
|
||||
import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
|
||||
import org.jcnc.jnotepad.view.manager.ViewManager;
|
||||
@ -24,29 +24,18 @@ public class NewFile implements EventHandler<ActionEvent> {
|
||||
*/
|
||||
@Override
|
||||
public void handle(ActionEvent event) {
|
||||
// 获取控制器
|
||||
Controller controller = Controller.getInstance();
|
||||
|
||||
// 创建一个新的文本编辑区
|
||||
LineNumberTextArea textArea = new LineNumberTextArea();
|
||||
|
||||
textArea.setStyle(
|
||||
"-fx-border-color:white ;-fx-background-color:white;"
|
||||
);
|
||||
|
||||
// TODO: refactor:统一TextArea新建、绑定监听器入口
|
||||
// 增加autoSave监听器绑定
|
||||
controller.autoSave(textArea);
|
||||
ViewManager viewManager = ViewManager.getInstance();
|
||||
// 将Tab页添加到TabPane中
|
||||
JNotepadTabPane.getInstance().addNewTab(new JNotepadTab("新建文本 "
|
||||
+ viewManager.selfIncreaseAndGetTabIndex(),
|
||||
textArea));
|
||||
|
||||
// 更新状态标签
|
||||
controller.updateStatusLabel(textArea);
|
||||
|
||||
// 更新编码信息
|
||||
controller.upDateEncodingLabel(textArea.getMainTextArea().getText());
|
||||
JNotepadStatusBox.getInstance().updateEncodingLabel();
|
||||
}
|
||||
}
|
||||
@ -5,8 +5,6 @@ import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jcnc.jnotepad.controller.manager.Controller;
|
||||
import org.jcnc.jnotepad.ui.LineNumberTextArea;
|
||||
import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
@ -15,6 +13,7 @@ import java.io.File;
|
||||
* 打开文件的事件处理程序。
|
||||
* <p>
|
||||
* 当用户选择打开文件时,将创建一个新的文本编辑区,并在Tab页中显示。
|
||||
*
|
||||
* @author 许轲
|
||||
*/
|
||||
public class OpenFile implements EventHandler<ActionEvent> {
|
||||
@ -38,8 +37,6 @@ public class OpenFile implements EventHandler<ActionEvent> {
|
||||
protected Void call() {
|
||||
// 调用控制器的getText方法,读取文件内容
|
||||
controller.getText(file);
|
||||
// 更新编码标签
|
||||
controller.upDateEncodingLabel(((LineNumberTextArea) JNotepadTabPane.getInstance().getSelected().getContent()).getMainTextArea().getText());
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@ -2,10 +2,9 @@ package org.jcnc.jnotepad.controller.event.handler;
|
||||
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.Tab;
|
||||
import org.jcnc.jnotepad.controller.manager.Controller;
|
||||
import org.jcnc.jnotepad.tool.LogUtil;
|
||||
import org.jcnc.jnotepad.ui.LineNumberTextArea;
|
||||
import org.jcnc.jnotepad.ui.tab.JNotepadTab;
|
||||
import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
|
||||
import org.slf4j.Logger;
|
||||
|
||||
@ -28,7 +27,7 @@ public class SaveFile implements EventHandler<ActionEvent> {
|
||||
@Override
|
||||
public void handle(ActionEvent actionEvent) {
|
||||
// 获取当前tab页
|
||||
Tab selectedTab = JNotepadTabPane.getInstance().getSelected();
|
||||
JNotepadTab selectedTab = JNotepadTabPane.getInstance().getSelected();
|
||||
if (selectedTab == null) {
|
||||
return;
|
||||
}
|
||||
@ -40,10 +39,8 @@ public class SaveFile implements EventHandler<ActionEvent> {
|
||||
saveTab(this.getClass());
|
||||
} else {
|
||||
logger.info("当前保存文件为关联打开文件,调用自动保存方法");
|
||||
// 打开的是关联文件
|
||||
Controller controller = Controller.getInstance();
|
||||
// 自动保存
|
||||
controller.autoSave(textArea);
|
||||
// 调用tab保存
|
||||
selectedTab.save();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,13 +2,7 @@ package org.jcnc.jnotepad.controller.manager;
|
||||
|
||||
import javafx.application.Platform;
|
||||
import javafx.concurrent.Task;
|
||||
import javafx.event.ActionEvent;
|
||||
import javafx.event.EventHandler;
|
||||
import javafx.scene.control.Tab;
|
||||
import org.jcnc.jnotepad.Interface.ControllerInterface;
|
||||
import org.jcnc.jnotepad.controller.event.handler.NewFile;
|
||||
import org.jcnc.jnotepad.controller.event.handler.OpenFile;
|
||||
import org.jcnc.jnotepad.controller.event.handler.SaveAsFile;
|
||||
import org.jcnc.jnotepad.tool.EncodingDetector;
|
||||
import org.jcnc.jnotepad.tool.LogUtil;
|
||||
import org.jcnc.jnotepad.ui.LineNumberTextArea;
|
||||
@ -16,8 +10,11 @@ import org.jcnc.jnotepad.ui.tab.JNotepadTab;
|
||||
import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
|
||||
import org.jcnc.jnotepad.view.manager.ViewManager;
|
||||
|
||||
import java.io.*;
|
||||
import java.util.ArrayList;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
@ -57,74 +54,6 @@ public class Controller implements ControllerInterface {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取新建文件事件处理程序。
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
* @return 新建文件事件处理程序
|
||||
*/
|
||||
@Override
|
||||
public EventHandler<ActionEvent> getNewFileEventHandler(LineNumberTextArea textArea) {
|
||||
return new NewFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取打开文件事件处理程序。
|
||||
*
|
||||
* @return 打开文件事件处理程序
|
||||
*/
|
||||
@Override
|
||||
public EventHandler<ActionEvent> getOpenFileEventHandler() {
|
||||
return new OpenFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* 自动保存文本内容。
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
*/
|
||||
@Override
|
||||
public void autoSave(LineNumberTextArea textArea) {
|
||||
textArea.getMainTextArea().textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
Tab tab = JNotepadTabPane.getInstance().getSelected();
|
||||
if (tab != null) {
|
||||
File file = (File) tab.getUserData();
|
||||
if (file != null) {
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
|
||||
writer.write(newValue);
|
||||
LogUtil.getLogger(this.getClass()).info("正在自动保存---");
|
||||
} catch (IOException ignored) {
|
||||
LogUtil.getLogger(this.getClass()).info("已忽视IO异常!");
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取另存为文件事件处理程序。
|
||||
*
|
||||
* @return 另存为文件事件处理程序
|
||||
*/
|
||||
@Override
|
||||
public EventHandler<ActionEvent> getSaveAsFileEventHandler() {
|
||||
return new SaveAsFile();
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新状态标签。
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
*/
|
||||
@Override
|
||||
public void updateStatusLabel(LineNumberTextArea textArea) {
|
||||
int caretPosition = textArea.getMainTextArea().getCaretPosition();
|
||||
int row = getRow(caretPosition, textArea.getMainTextArea().getText());
|
||||
int column = getColumn(caretPosition, textArea.getMainTextArea().getText());
|
||||
int length = textArea.getMainTextArea().getLength();
|
||||
ViewManager.getInstance().getStatusLabel().setText("行: " + row + " \t列: " + column + " \t字数: " + length);
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开关联文件。
|
||||
*
|
||||
@ -148,7 +77,9 @@ public class Controller implements ControllerInterface {
|
||||
LineNumberTextArea textArea = createNewTextArea();
|
||||
// 设置当前标签页关联本地文件
|
||||
textArea.setRelevance(true);
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
||||
// 检测文件编码
|
||||
Charset encoding = EncodingDetector.detectEncodingCharset(file);
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(file, encoding))) {
|
||||
StringBuilder textBuilder = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
@ -158,11 +89,9 @@ public class Controller implements ControllerInterface {
|
||||
|
||||
Platform.runLater(() -> {
|
||||
textArea.getMainTextArea().setText(text);
|
||||
JNotepadTab tab = createNewTab(file.getName(), textArea);
|
||||
JNotepadTab tab = createNewTab(file.getName(), textArea, encoding);
|
||||
tab.setUserData(file);
|
||||
JNotepadTabPane.getInstance().addNewTab(tab);
|
||||
updateStatusLabel(textArea);
|
||||
autoSave(textArea);
|
||||
});
|
||||
|
||||
} catch (IOException ignored) {
|
||||
@ -171,74 +100,6 @@ public class Controller implements ControllerInterface {
|
||||
return textArea;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新编码标签。
|
||||
*
|
||||
* @param text 文本内容
|
||||
*/
|
||||
@Override
|
||||
public void upDateEncodingLabel(String text) {
|
||||
String encoding = EncodingDetector.detectEncoding(text);
|
||||
ViewManager.getInstance().getEnCodingLabel().setText("\t编码: " + encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取光标所在行号。
|
||||
*
|
||||
* @param caretPosition 光标位置
|
||||
* @param text 文本内容
|
||||
* @return 光标所在行号
|
||||
*/
|
||||
@Override
|
||||
public int getRow(int caretPosition, String text) {
|
||||
caretPosition = Math.min(caretPosition, text.length());
|
||||
String substring = text.substring(0, caretPosition);
|
||||
int count = 0;
|
||||
for (char c : substring.toCharArray()) {
|
||||
if (c == '\n') {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取光标所在列号。
|
||||
*
|
||||
* @param caretPosition 光标位置
|
||||
* @param text 文本内容
|
||||
* @return 光标所在列号
|
||||
*/
|
||||
@Override
|
||||
public int getColumn(int caretPosition, String text) {
|
||||
return caretPosition - text.lastIndexOf("\n", caretPosition - 1);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化标签面板。
|
||||
*/
|
||||
@Override
|
||||
public void initTabPane() {
|
||||
JNotepadTabPane.getInstance().getSelectionModel().selectedItemProperty().addListener((observable, oldTab, newTab) -> {
|
||||
LineNumberTextArea textArea;
|
||||
if (newTab != null) {
|
||||
// 获取新选定的标签页并关联的文本区域
|
||||
textArea = (LineNumberTextArea) newTab.getContent();
|
||||
} else {
|
||||
// 刷新状态
|
||||
textArea = openAssociatedFileAndCreateTextArea(new ArrayList<>());
|
||||
}
|
||||
// 更新状态标签
|
||||
INSTANCE.updateStatusLabel(textArea);
|
||||
|
||||
// 监听文本光标位置的变化,更新状态标签
|
||||
textArea.getMainTextArea().caretPositionProperty().addListener((caretObservable, oldPosition, newPosition) -> INSTANCE.updateStatusLabel(textArea));
|
||||
|
||||
// 更新编码标签
|
||||
INSTANCE.upDateEncodingLabel(textArea.getMainTextArea().getText());
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新UI和标签页
|
||||
*
|
||||
@ -251,7 +112,6 @@ public class Controller implements ControllerInterface {
|
||||
ViewManager viewManager = ViewManager.getInstance();
|
||||
String tabTitle = "新建文件 " + viewManager.selfIncreaseAndGetTabIndex();
|
||||
JNotepadTabPane.getInstance().addNewTab(new JNotepadTab(tabTitle, textArea));
|
||||
updateStatusLabel(textArea);
|
||||
}
|
||||
|
||||
|
||||
@ -262,12 +122,6 @@ public class Controller implements ControllerInterface {
|
||||
*/
|
||||
private void configureTextArea(LineNumberTextArea textArea) {
|
||||
textArea.getMainTextArea().setWrapText(true);
|
||||
upDateEncodingLabel(textArea.getMainTextArea().getText());
|
||||
updateStatusLabel(textArea);
|
||||
|
||||
textArea.textProperty().addListener((observable, oldValue, newValue) -> updateStatusLabel(textArea));
|
||||
|
||||
autoSave(textArea);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -276,12 +130,7 @@ public class Controller implements ControllerInterface {
|
||||
* @return 新的文本区域
|
||||
*/
|
||||
private LineNumberTextArea createNewTextArea() {
|
||||
LineNumberTextArea textArea = new LineNumberTextArea();
|
||||
textArea.setStyle(
|
||||
"-fx-border-color:white;" +
|
||||
"-fx-background-color:white"
|
||||
);
|
||||
return textArea;
|
||||
return new LineNumberTextArea();
|
||||
}
|
||||
|
||||
/**
|
||||
@ -291,8 +140,8 @@ public class Controller implements ControllerInterface {
|
||||
* @param textArea 文本区域
|
||||
* @return 新的标签页
|
||||
*/
|
||||
private JNotepadTab createNewTab(String tabName, LineNumberTextArea textArea) {
|
||||
return new JNotepadTab(tabName, textArea);
|
||||
private JNotepadTab createNewTab(String tabName, LineNumberTextArea textArea, Charset charset) {
|
||||
return new JNotepadTab(tabName, textArea, charset);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -305,7 +154,7 @@ public class Controller implements ControllerInterface {
|
||||
return new Task<>() {
|
||||
@Override
|
||||
protected Void call() {
|
||||
upDateEncodingLabel(getText(file).getMainTextArea().getText());
|
||||
getText(file);
|
||||
return null;
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,11 +1,21 @@
|
||||
package org.jcnc.jnotepad.tool;
|
||||
|
||||
|
||||
import com.ibm.icu.text.CharsetDetector;
|
||||
import com.ibm.icu.text.CharsetMatch;
|
||||
import javafx.application.Platform;
|
||||
import org.jcnc.jnotepad.ui.LineNumberTextArea;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
|
||||
/**
|
||||
* 编码检测工具类
|
||||
@ -13,67 +23,46 @@ import java.nio.charset.StandardCharsets;
|
||||
* @author 许轲
|
||||
*/
|
||||
public class EncodingDetector {
|
||||
private EncodingDetector() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测 TextArea 中的文本编码
|
||||
*
|
||||
* @param textArea 文本区域
|
||||
* @return 字符串表示的编码,如果检测失败则返回 "UNKNOWN"
|
||||
*/
|
||||
public static String detectEncoding(LineNumberTextArea textArea) {
|
||||
String text = textArea.getMainTextArea().getText();
|
||||
return detectEncoding(text);
|
||||
private static final Logger LOG = LoggerFactory.getLogger(EncodingDetector.class);
|
||||
public static final String UNKNOWN = "UNKNOWN";
|
||||
|
||||
private EncodingDetector() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 检测文本编码
|
||||
*
|
||||
* @param text 要检测的文本
|
||||
* @param file 要检测的文件
|
||||
* @return 字符串表示的编码,如果检测失败则返回 "UNKNOWN"
|
||||
*/
|
||||
public static String detectEncoding(String text) {
|
||||
// 尝试常见的编码
|
||||
for (Charset charset : commonCharsets()) {
|
||||
if (isValidEncoding(text, charset)) {
|
||||
Platform.runLater(() -> LogUtil.getLogger(EncodingDetector.class).info("编码监测结果:{}", isValidEncoding(text, charset)));
|
||||
return charset.name();
|
||||
public static String detectEncoding(File file) {
|
||||
CharsetDetector charsetDetector = new CharsetDetector();
|
||||
try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file.getPath()))) {
|
||||
charsetDetector.setText(inputStream);
|
||||
CharsetMatch match = charsetDetector.detect();
|
||||
LOG.debug(match.getName() + " " + match.getConfidence());
|
||||
if (match.getConfidence() > 50) {
|
||||
return match.getName();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
LOG.error("", e);
|
||||
}
|
||||
|
||||
return "UNKNOWN";
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取常见的字符集数组
|
||||
* 检测文本编码
|
||||
*
|
||||
* @return 常见字符集数组
|
||||
* @param file 文件
|
||||
* @return Charset编码
|
||||
*/
|
||||
private static Charset[] commonCharsets() {
|
||||
return new Charset[]{
|
||||
StandardCharsets.UTF_8,
|
||||
StandardCharsets.UTF_16,
|
||||
StandardCharsets.UTF_16LE,
|
||||
StandardCharsets.UTF_16BE,
|
||||
StandardCharsets.ISO_8859_1
|
||||
};
|
||||
public static Charset detectEncodingCharset(File file) {
|
||||
String charset = detectEncoding(file);
|
||||
if (charset.equals(UNKNOWN)) {
|
||||
return StandardCharsets.UTF_8;
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查指定编码是否能够正确解码文本
|
||||
*
|
||||
* @param text 要检测的文本
|
||||
* @param encoding 要尝试的编码
|
||||
* @return 如果指定编码能够正确解码文本,则返回 true,否则返回 false
|
||||
*/
|
||||
private static boolean isValidEncoding(String text, Charset encoding) {
|
||||
// 尝试使用指定编码解码
|
||||
byte[] bytes = text.getBytes(encoding);
|
||||
String decoded = new String(bytes, encoding);
|
||||
|
||||
// 解码后的文本相同表示编码有效
|
||||
return text.equals(decoded);
|
||||
return Charset.forName(charset);
|
||||
}
|
||||
|
||||
}
|
||||
@ -3,6 +3,7 @@ package org.jcnc.jnotepad.tool;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.stage.FileChooser;
|
||||
import org.jcnc.jnotepad.ui.LineNumberTextArea;
|
||||
import org.jcnc.jnotepad.ui.tab.JNotepadTab;
|
||||
import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
|
||||
|
||||
import java.io.*;
|
||||
@ -48,7 +49,7 @@ public class FileUtil {
|
||||
* @see LogUtil
|
||||
*/
|
||||
public static void saveTab(Class<?> currentClass) {
|
||||
Tab selectedTab = JNotepadTabPane.getInstance().getSelected();
|
||||
JNotepadTab selectedTab = JNotepadTabPane.getInstance().getSelected();
|
||||
if (selectedTab != null) {
|
||||
// 创建一个文件窗口
|
||||
FileChooser fileChooser = new FileChooser();
|
||||
@ -58,24 +59,13 @@ public class FileUtil {
|
||||
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文档", "*.txt"));
|
||||
File file = fileChooser.showSaveDialog(null);
|
||||
if (file != null) {
|
||||
try (
|
||||
BufferedWriter writer = new BufferedWriter(new FileWriter(file))
|
||||
) {
|
||||
// 获取当前Tab页的文本编辑区
|
||||
LineNumberTextArea textArea = (LineNumberTextArea) selectedTab.getContent();
|
||||
selectedTab.save();
|
||||
// 将保存后的文件设置为已关联
|
||||
textArea.setRelevance(true);
|
||||
String text = textArea.getMainTextArea().getText();
|
||||
// 写入文件内容
|
||||
writer.write(text);
|
||||
writer.flush();
|
||||
selectedTab.getLineNumberTextArea().setRelevance(true);
|
||||
// 更新Tab页标签上的文件名
|
||||
selectedTab.setText(file.getName());
|
||||
// 将文件对象保存到Tab页的UserData中
|
||||
selectedTab.setUserData(file);
|
||||
} catch (IOException ignored) {
|
||||
LogUtil.getLogger(currentClass).info("已忽视IO异常!");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,8 +1,18 @@
|
||||
package org.jcnc.jnotepad.ui;
|
||||
|
||||
import javafx.beans.property.StringProperty;
|
||||
import javafx.scene.control.Tab;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.layout.BorderPane;
|
||||
import org.jcnc.jnotepad.tool.LogUtil;
|
||||
import org.jcnc.jnotepad.ui.status.JNotepadStatusBox;
|
||||
import org.jcnc.jnotepad.ui.tab.JNotepadTab;
|
||||
import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
|
||||
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* @author 许轲
|
||||
@ -28,16 +38,50 @@ public class LineNumberTextArea extends BorderPane {
|
||||
// 设定自定义样式
|
||||
lineNumberArea.getStyleClass().add("text-line-number");
|
||||
mainTextArea.getStyleClass().add("main-text-area");
|
||||
lineNumberArea.textProperty().addListener((observable, oldValue, newValue) -> updateLineNumberWidth());
|
||||
mainTextArea.textProperty().addListener((observable, oldValue, newValue) -> updateLineNumberArea());
|
||||
this.setStyle(
|
||||
"-fx-border-color:white;" +
|
||||
"-fx-background-color:white"
|
||||
);
|
||||
setCenter(mainTextArea);
|
||||
setLeft(lineNumberArea);
|
||||
|
||||
initListeners();
|
||||
}
|
||||
|
||||
private void initListeners() {
|
||||
// 当主要文本区域的垂直滚动位置发生变化时,使行号文本区域的滚动位置保持一致
|
||||
mainTextArea.scrollTopProperty().addListener((observable, oldValue, newValue) -> lineNumberArea.setScrollTop(mainTextArea.getScrollTop()));
|
||||
|
||||
// 当行号文本区域的垂直滚动位置发生变化时,使主要文本区域的滚动位置保持一致
|
||||
lineNumberArea.scrollTopProperty().addListener((observable, oldValue, newValue) -> mainTextArea.setScrollTop(lineNumberArea.getScrollTop()));
|
||||
setCenter(mainTextArea);
|
||||
setLeft(lineNumberArea);
|
||||
lineNumberArea.textProperty().addListener((observable, oldValue, newValue) -> updateLineNumberWidth());
|
||||
|
||||
this.mainTextArea.caretPositionProperty().addListener((caretObservable, oldPosition, newPosition) -> JNotepadStatusBox.getInstance().updateWordCountStatusLabel());
|
||||
this.textProperty().addListener((observable, oldValue, newValue) -> {
|
||||
// 更新行号
|
||||
updateLineNumberArea();
|
||||
// 更新状态栏
|
||||
JNotepadStatusBox.getInstance().updateWordCountStatusLabel();
|
||||
// 自动保存
|
||||
save();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 以原文件编码格式写回文件
|
||||
*/
|
||||
public void save() {
|
||||
JNotepadTab tab = JNotepadTabPane.getInstance().getSelected();
|
||||
File file = (File) tab.getUserData();
|
||||
String newValue = this.mainTextArea.getText();
|
||||
if (file != null) {
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file, tab.getCharset()))) {
|
||||
writer.write(newValue);
|
||||
LogUtil.getLogger(this.getClass()).info("正在自动保存---");
|
||||
} catch (IOException ignored) {
|
||||
LogUtil.getLogger(this.getClass()).info("已忽视IO异常!");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean isRelevance() {
|
||||
@ -91,5 +135,6 @@ public class LineNumberTextArea extends BorderPane {
|
||||
public TextArea getMainTextArea() {
|
||||
return mainTextArea;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@ -148,7 +148,6 @@ public class JNotepadMenuBar extends MenuBar {
|
||||
openItem.setOnAction(new OpenFile());
|
||||
saveItem.setOnAction(new SaveFile());
|
||||
saveAsItem.setOnAction(new SaveAsFile());
|
||||
lineFeedItem.setOnAction(new LineFeed());
|
||||
lineFeedItem.selectedProperty().addListener((observableValue, before, after) -> {
|
||||
// 1. 更新全局配置
|
||||
GlobalConfig.getConfig().setAutoLineConfig(after);
|
||||
|
||||
115
src/main/java/org/jcnc/jnotepad/ui/status/JNotepadStatusBox.java
Normal file
115
src/main/java/org/jcnc/jnotepad/ui/status/JNotepadStatusBox.java
Normal file
@ -0,0 +1,115 @@
|
||||
package org.jcnc.jnotepad.ui.status;
|
||||
|
||||
import javafx.geometry.Insets;
|
||||
import javafx.scene.control.Label;
|
||||
import javafx.scene.control.TextArea;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 状态栏组件封装。
|
||||
* 1. 文字统计
|
||||
* 2. 编码
|
||||
*
|
||||
* @author songdragon
|
||||
*/
|
||||
public class JNotepadStatusBox extends HBox {
|
||||
|
||||
private static final JNotepadStatusBox STATUS_BOX = new JNotepadStatusBox();
|
||||
|
||||
/**
|
||||
* 字数统计及光标
|
||||
*/
|
||||
private final Label statusLabel;
|
||||
|
||||
/**
|
||||
* 显示文本编码
|
||||
*/
|
||||
private final Label enCodingLabel;
|
||||
|
||||
private JNotepadStatusBox() {
|
||||
// 创建状态栏
|
||||
statusLabel = new Label("行数:1 \t列数:1 \t字数:0 ");
|
||||
// 创建新的标签以显示编码信息
|
||||
enCodingLabel = new Label();
|
||||
|
||||
this.getChildren().add(statusLabel);
|
||||
this.getChildren().add(enCodingLabel);
|
||||
this.getProperties().put("borderpane-margin", new Insets(5, 10, 5, 10));
|
||||
|
||||
}
|
||||
|
||||
public static JNotepadStatusBox getInstance() {
|
||||
return STATUS_BOX;
|
||||
}
|
||||
|
||||
public void updateEncodingLabel() {
|
||||
updateEncodingLabel(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新编码展示
|
||||
* @param encoding 文件编码
|
||||
*/
|
||||
public void updateEncodingLabel(String encoding) {
|
||||
if (encoding == null) {
|
||||
encoding = Charset.defaultCharset().name();
|
||||
}
|
||||
this.enCodingLabel.setText("\t编码: " + encoding);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新字数统计
|
||||
*/
|
||||
public void updateWordCountStatusLabel() {
|
||||
TextArea textArea = JNotepadTabPane.getInstance().getSelected().getLineNumberTextArea().getMainTextArea();
|
||||
int caretPosition = textArea.getCaretPosition();
|
||||
int row = getRow(caretPosition, textArea.getText());
|
||||
int column = getColumn(caretPosition, textArea.getText());
|
||||
int length = textArea.getLength();
|
||||
this.statusLabel.setText("行: " + row + " \t列: " + column + " \t字数: " + length);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tab选中时,更新状态栏
|
||||
* 1. 状态栏更新当前选中tab的数字统计
|
||||
* 2. 状态栏更新当前选中tab的字符编码
|
||||
*/
|
||||
public void updateWhenTabSelected() {
|
||||
updateWordCountStatusLabel();
|
||||
updateEncodingLabel(JNotepadTabPane.getInstance().getSelected().getCharset().name());
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取光标所在行号。
|
||||
*
|
||||
* @param caretPosition 光标位置
|
||||
* @param text 文本内容
|
||||
* @return 光标所在行号
|
||||
*/
|
||||
public int getRow(int caretPosition, String text) {
|
||||
caretPosition = Math.min(caretPosition, text.length());
|
||||
String substring = text.substring(0, caretPosition);
|
||||
int count = 0;
|
||||
for (char c : substring.toCharArray()) {
|
||||
if (c == '\n') {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
return count + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取光标所在列号。
|
||||
*
|
||||
* @param caretPosition 光标位置
|
||||
* @param text 文本内容
|
||||
* @return 光标所在列号
|
||||
*/
|
||||
public int getColumn(int caretPosition, String text) {
|
||||
return caretPosition - text.lastIndexOf("\n", caretPosition - 1);
|
||||
}
|
||||
|
||||
}
|
||||
@ -4,6 +4,8 @@ import javafx.scene.control.Tab;
|
||||
import org.jcnc.jnotepad.app.config.GlobalConfig;
|
||||
import org.jcnc.jnotepad.ui.LineNumberTextArea;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
|
||||
/**
|
||||
* 封装标签页组件,增加属于标签页的属性,例如:自动换行开关。
|
||||
* 每个Tab关联一个LineNumberTextArea。
|
||||
@ -17,12 +19,17 @@ public class JNotepadTab extends Tab {
|
||||
*/
|
||||
private boolean autoLine = false;
|
||||
private final LineNumberTextArea lineNumberTextArea;
|
||||
private Charset charset = Charset.defaultCharset();
|
||||
|
||||
public JNotepadTab(String tabTitle) {
|
||||
this(tabTitle, new LineNumberTextArea());
|
||||
}
|
||||
|
||||
public JNotepadTab(String tabTitle, LineNumberTextArea textArea) {
|
||||
this(tabTitle, textArea, Charset.defaultCharset());
|
||||
}
|
||||
|
||||
public JNotepadTab(String tabTitle, LineNumberTextArea textArea, Charset charset) {
|
||||
super(tabTitle);
|
||||
lineNumberTextArea = textArea;
|
||||
this.setContent(lineNumberTextArea);
|
||||
@ -41,4 +48,16 @@ public class JNotepadTab extends Tab {
|
||||
public LineNumberTextArea getLineNumberTextArea() {
|
||||
return lineNumberTextArea;
|
||||
}
|
||||
|
||||
public Charset getCharset() {
|
||||
return charset;
|
||||
}
|
||||
|
||||
public void setCharset(Charset charset) {
|
||||
this.charset = charset;
|
||||
}
|
||||
|
||||
public void save() {
|
||||
this.lineNumberTextArea.save();
|
||||
}
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ package org.jcnc.jnotepad.ui.tab;
|
||||
import javafx.scene.control.TabPane;
|
||||
import org.jcnc.jnotepad.app.config.GlobalConfig;
|
||||
import org.jcnc.jnotepad.ui.menu.JNotepadMenuBar;
|
||||
import org.jcnc.jnotepad.ui.status.JNotepadStatusBox;
|
||||
|
||||
/**
|
||||
* 标签页布局组件封装。
|
||||
@ -25,8 +26,11 @@ public class JNotepadTabPane extends TabPane {
|
||||
this.getSelectionModel().selectedItemProperty().addListener(
|
||||
(ov, from, to) -> {
|
||||
if (to != null) {
|
||||
// 更新菜单栏中与tab相关设置
|
||||
JNotepadMenuBar.getMenuBar().updateMenuStatusBySelectedTab();
|
||||
}
|
||||
// 更新状态标签
|
||||
JNotepadStatusBox.getInstance().updateWordCountStatusLabel();
|
||||
}
|
||||
);
|
||||
}
|
||||
@ -65,5 +69,6 @@ public class JNotepadTabPane extends TabPane {
|
||||
public void fireTabSelected() {
|
||||
JNotepadTab selectedTab = getSelected();
|
||||
selectedTab.setAutoLine(GlobalConfig.getConfig().getAutoLineConfig());
|
||||
JNotepadStatusBox.getInstance().updateWhenTabSelected();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
package org.jcnc.jnotepad.view.init;
|
||||
|
||||
import org.jcnc.jnotepad.controller.manager.Controller;
|
||||
import org.jcnc.jnotepad.controller.manager.ShortcutKey;
|
||||
|
||||
|
||||
@ -9,10 +8,6 @@ import org.jcnc.jnotepad.controller.manager.ShortcutKey;
|
||||
*/
|
||||
public class View {
|
||||
|
||||
public void initTabPane() {
|
||||
Controller.getInstance().initTabPane();
|
||||
}
|
||||
|
||||
// 初始化快捷键
|
||||
public void initShortcutKey() {
|
||||
new ShortcutKey().createShortcutKeyByConfig();
|
||||
|
||||
@ -7,6 +7,7 @@ import javafx.scene.layout.BorderPane;
|
||||
import javafx.scene.layout.HBox;
|
||||
import org.jcnc.jnotepad.exception.AppException;
|
||||
import org.jcnc.jnotepad.ui.menu.JNotepadMenuBar;
|
||||
import org.jcnc.jnotepad.ui.status.JNotepadStatusBox;
|
||||
import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
|
||||
|
||||
|
||||
@ -17,11 +18,6 @@ import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
|
||||
*/
|
||||
public class ViewManager {
|
||||
|
||||
/**
|
||||
* 显示文本编码
|
||||
*/
|
||||
private Label enCodingLabel; // 显示文本编码
|
||||
|
||||
private int tabIndex = 0;
|
||||
|
||||
private Boolean line = true;
|
||||
@ -30,19 +26,13 @@ public class ViewManager {
|
||||
// 主界面布局
|
||||
private BorderPane root; // 主布局
|
||||
|
||||
// 状态栏
|
||||
private Label statusLabel;
|
||||
|
||||
private static ViewManager instance = null;
|
||||
|
||||
|
||||
public Label getEnCodingLabel() {
|
||||
return enCodingLabel;
|
||||
}
|
||||
/**
|
||||
* 自增并获取标签页索引
|
||||
*
|
||||
*
|
||||
* @return int 标签页索引
|
||||
* @apiNote ++tabIndex
|
||||
*/
|
||||
@ -59,10 +49,6 @@ public class ViewManager {
|
||||
return root;
|
||||
}
|
||||
|
||||
public Label getStatusLabel() {
|
||||
return statusLabel;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取ViewManager的实例。如果实例不存在,则创建一个新实例。
|
||||
@ -109,14 +95,7 @@ public class ViewManager {
|
||||
|
||||
// 创建标签页和文本编辑区域
|
||||
root.setCenter(JNotepadTabPane.getInstance());
|
||||
|
||||
// 创建状态栏
|
||||
statusLabel = new Label("行数:1 \t列数:1 \t字数:0 ");
|
||||
|
||||
enCodingLabel = new Label(); // 创建新的标签以显示编码信息
|
||||
HBox statusBox = new HBox(statusLabel, enCodingLabel); // 使用HBox放置状态标签和编码标签
|
||||
root.setBottom(statusBox);
|
||||
BorderPane.setMargin(statusBox, new Insets(5, 10, 5, 10));
|
||||
root.setBottom(JNotepadStatusBox.getInstance());
|
||||
|
||||
scene.setRoot(root);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user