feat: 实现侦测文件内容编码,并使用侦测成功的编码打开

This commit is contained in:
songdragon 2023-08-24 00:12:46 +08:00
parent 58cd858643
commit 2410f6e213
17 changed files with 263 additions and 356 deletions

1
.gitignore vendored
View File

@ -37,3 +37,4 @@ build/
/src/main/JNotepad.java /src/main/JNotepad.java
/.idea/ /.idea/
/project.txt /project.txt
logs/

View File

@ -55,6 +55,11 @@
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
<version>1.4.11</version> <version>1.4.11</version>
</dependency> </dependency>
<dependency>
<groupId>com.ibm.icu</groupId>
<artifactId>icu4j</artifactId>
<version>68.1</version>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -9,6 +9,8 @@ module org.jcnc.jnotepad {
requires org.slf4j; requires org.slf4j;
requires ch.qos.logback.core; requires ch.qos.logback.core;
requires ch.qos.logback.classic; requires ch.qos.logback.classic;
requires com.ibm.icu;
exports org.jcnc.jnotepad; exports org.jcnc.jnotepad;
exports org.jcnc.jnotepad.tool; exports org.jcnc.jnotepad.tool;
exports org.jcnc.jnotepad.Interface; exports org.jcnc.jnotepad.Interface;

View File

@ -1,7 +1,5 @@
package org.jcnc.jnotepad.Interface; package org.jcnc.jnotepad.Interface;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import org.jcnc.jnotepad.ui.LineNumberTextArea; import org.jcnc.jnotepad.ui.LineNumberTextArea;
import java.io.File; import java.io.File;
@ -21,43 +19,6 @@ public interface ControllerInterface {
*/ */
LineNumberTextArea openAssociatedFileAndCreateTextArea(List<String> rawParameters); 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); 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和标签页 * 更新UI和标签页
* *

View File

@ -65,8 +65,7 @@ public class LunchApp extends Application {
primaryStage.show(); primaryStage.show();
ViewManager viewManager = ViewManager.getInstance(scene); ViewManager viewManager = ViewManager.getInstance(scene);
viewManager.initScreen(scene); viewManager.initScreen(scene);
// 初始化菜单项和标签栏 // 初始化快捷键
view.initTabPane();
view.initShortcutKey(); view.initShortcutKey();
// 使用线程池加载关联文件并创建文本区域 // 使用线程池加载关联文件并创建文本区域

View File

@ -2,8 +2,8 @@ package org.jcnc.jnotepad.controller.event.handler;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import org.jcnc.jnotepad.controller.manager.Controller;
import org.jcnc.jnotepad.ui.LineNumberTextArea; 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.JNotepadTab;
import org.jcnc.jnotepad.ui.tab.JNotepadTabPane; import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
import org.jcnc.jnotepad.view.manager.ViewManager; import org.jcnc.jnotepad.view.manager.ViewManager;
@ -24,29 +24,18 @@ public class NewFile implements EventHandler<ActionEvent> {
*/ */
@Override @Override
public void handle(ActionEvent event) { public void handle(ActionEvent event) {
// 获取控制器
Controller controller = Controller.getInstance();
// 创建一个新的文本编辑区 // 创建一个新的文本编辑区
LineNumberTextArea textArea = new LineNumberTextArea(); LineNumberTextArea textArea = new LineNumberTextArea();
textArea.setStyle(
"-fx-border-color:white ;-fx-background-color:white;"
);
// TODO: refactor统一TextArea新建绑定监听器入口 // TODO: refactor统一TextArea新建绑定监听器入口
// 增加autoSave监听器绑定
controller.autoSave(textArea);
ViewManager viewManager = ViewManager.getInstance(); ViewManager viewManager = ViewManager.getInstance();
// 将Tab页添加到TabPane中 // 将Tab页添加到TabPane中
JNotepadTabPane.getInstance().addNewTab(new JNotepadTab("新建文本 " JNotepadTabPane.getInstance().addNewTab(new JNotepadTab("新建文本 "
+ viewManager.selfIncreaseAndGetTabIndex(), + viewManager.selfIncreaseAndGetTabIndex(),
textArea)); textArea));
// 更新状态标签
controller.updateStatusLabel(textArea);
// 更新编码信息 // 更新编码信息
controller.upDateEncodingLabel(textArea.getMainTextArea().getText()); JNotepadStatusBox.getInstance().updateEncodingLabel();
} }
} }

View File

@ -5,8 +5,6 @@ import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import org.jcnc.jnotepad.controller.manager.Controller; import org.jcnc.jnotepad.controller.manager.Controller;
import org.jcnc.jnotepad.ui.LineNumberTextArea;
import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
import java.io.File; import java.io.File;
@ -15,6 +13,7 @@ import java.io.File;
* 打开文件的事件处理程序 * 打开文件的事件处理程序
* <p> * <p>
* 当用户选择打开文件时将创建一个新的文本编辑区并在Tab页中显示 * 当用户选择打开文件时将创建一个新的文本编辑区并在Tab页中显示
*
* @author 许轲 * @author 许轲
*/ */
public class OpenFile implements EventHandler<ActionEvent> { public class OpenFile implements EventHandler<ActionEvent> {
@ -38,8 +37,6 @@ public class OpenFile implements EventHandler<ActionEvent> {
protected Void call() { protected Void call() {
// 调用控制器的getText方法读取文件内容 // 调用控制器的getText方法读取文件内容
controller.getText(file); controller.getText(file);
// 更新编码标签
controller.upDateEncodingLabel(((LineNumberTextArea) JNotepadTabPane.getInstance().getSelected().getContent()).getMainTextArea().getText());
return null; return null;
} }
}; };

View File

@ -2,10 +2,9 @@ package org.jcnc.jnotepad.controller.event.handler;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; 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.tool.LogUtil;
import org.jcnc.jnotepad.ui.LineNumberTextArea; import org.jcnc.jnotepad.ui.LineNumberTextArea;
import org.jcnc.jnotepad.ui.tab.JNotepadTab;
import org.jcnc.jnotepad.ui.tab.JNotepadTabPane; import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
import org.slf4j.Logger; import org.slf4j.Logger;
@ -28,7 +27,7 @@ public class SaveFile implements EventHandler<ActionEvent> {
@Override @Override
public void handle(ActionEvent actionEvent) { public void handle(ActionEvent actionEvent) {
// 获取当前tab页 // 获取当前tab页
Tab selectedTab = JNotepadTabPane.getInstance().getSelected(); JNotepadTab selectedTab = JNotepadTabPane.getInstance().getSelected();
if (selectedTab == null) { if (selectedTab == null) {
return; return;
} }
@ -40,10 +39,8 @@ public class SaveFile implements EventHandler<ActionEvent> {
saveTab(this.getClass()); saveTab(this.getClass());
} else { } else {
logger.info("当前保存文件为关联打开文件,调用自动保存方法"); logger.info("当前保存文件为关联打开文件,调用自动保存方法");
// 打开的是关联文件 // 调用tab保存
Controller controller = Controller.getInstance(); selectedTab.save();
// 自动保存
controller.autoSave(textArea);
} }
} }
} }

View File

@ -2,13 +2,7 @@ package org.jcnc.jnotepad.controller.manager;
import javafx.application.Platform; import javafx.application.Platform;
import javafx.concurrent.Task; 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.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.EncodingDetector;
import org.jcnc.jnotepad.tool.LogUtil; import org.jcnc.jnotepad.tool.LogUtil;
import org.jcnc.jnotepad.ui.LineNumberTextArea; 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.ui.tab.JNotepadTabPane;
import org.jcnc.jnotepad.view.manager.ViewManager; import org.jcnc.jnotepad.view.manager.ViewManager;
import java.io.*; import java.io.BufferedReader;
import java.util.ArrayList; import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.List; 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(); LineNumberTextArea textArea = createNewTextArea();
// 设置当前标签页关联本地文件 // 设置当前标签页关联本地文件
textArea.setRelevance(true); 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(); StringBuilder textBuilder = new StringBuilder();
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
@ -158,11 +89,9 @@ public class Controller implements ControllerInterface {
Platform.runLater(() -> { Platform.runLater(() -> {
textArea.getMainTextArea().setText(text); textArea.getMainTextArea().setText(text);
JNotepadTab tab = createNewTab(file.getName(), textArea); JNotepadTab tab = createNewTab(file.getName(), textArea, encoding);
tab.setUserData(file); tab.setUserData(file);
JNotepadTabPane.getInstance().addNewTab(tab); JNotepadTabPane.getInstance().addNewTab(tab);
updateStatusLabel(textArea);
autoSave(textArea);
}); });
} catch (IOException ignored) { } catch (IOException ignored) {
@ -171,74 +100,6 @@ public class Controller implements ControllerInterface {
return textArea; 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和标签页 * 更新UI和标签页
* *
@ -251,7 +112,6 @@ public class Controller implements ControllerInterface {
ViewManager viewManager = ViewManager.getInstance(); ViewManager viewManager = ViewManager.getInstance();
String tabTitle = "新建文件 " + viewManager.selfIncreaseAndGetTabIndex(); String tabTitle = "新建文件 " + viewManager.selfIncreaseAndGetTabIndex();
JNotepadTabPane.getInstance().addNewTab(new JNotepadTab(tabTitle, textArea)); JNotepadTabPane.getInstance().addNewTab(new JNotepadTab(tabTitle, textArea));
updateStatusLabel(textArea);
} }
@ -262,12 +122,6 @@ public class Controller implements ControllerInterface {
*/ */
private void configureTextArea(LineNumberTextArea textArea) { private void configureTextArea(LineNumberTextArea textArea) {
textArea.getMainTextArea().setWrapText(true); 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 新的文本区域 * @return 新的文本区域
*/ */
private LineNumberTextArea createNewTextArea() { private LineNumberTextArea createNewTextArea() {
LineNumberTextArea textArea = new LineNumberTextArea(); return new LineNumberTextArea();
textArea.setStyle(
"-fx-border-color:white;" +
"-fx-background-color:white"
);
return textArea;
} }
/** /**
@ -291,8 +140,8 @@ public class Controller implements ControllerInterface {
* @param textArea 文本区域 * @param textArea 文本区域
* @return 新的标签页 * @return 新的标签页
*/ */
private JNotepadTab createNewTab(String tabName, LineNumberTextArea textArea) { private JNotepadTab createNewTab(String tabName, LineNumberTextArea textArea, Charset charset) {
return new JNotepadTab(tabName, textArea); return new JNotepadTab(tabName, textArea, charset);
} }
/** /**
@ -305,7 +154,7 @@ public class Controller implements ControllerInterface {
return new Task<>() { return new Task<>() {
@Override @Override
protected Void call() { protected Void call() {
upDateEncodingLabel(getText(file).getMainTextArea().getText()); getText(file);
return null; return null;
} }
}; };

View File

@ -1,11 +1,21 @@
package org.jcnc.jnotepad.tool; package org.jcnc.jnotepad.tool;
import com.ibm.icu.text.CharsetDetector;
import com.ibm.icu.text.CharsetMatch;
import javafx.application.Platform; import javafx.application.Platform;
import org.jcnc.jnotepad.ui.LineNumberTextArea; 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.Charset;
import java.nio.charset.StandardCharsets; 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 许轲 * @author 许轲
*/ */
public class EncodingDetector { public class EncodingDetector {
private EncodingDetector() {
}
/** private static final Logger LOG = LoggerFactory.getLogger(EncodingDetector.class);
* 检测 TextArea 中的文本编码 public static final String UNKNOWN = "UNKNOWN";
*
* @param textArea 文本区域 private EncodingDetector() {
* @return 字符串表示的编码如果检测失败则返回 "UNKNOWN"
*/
public static String detectEncoding(LineNumberTextArea textArea) {
String text = textArea.getMainTextArea().getText();
return detectEncoding(text);
} }
/** /**
* 检测文本编码 * 检测文本编码
* *
* @param text 要检测的文本 * @param file 要检测的文件
* @return 字符串表示的编码如果检测失败则返回 "UNKNOWN" * @return 字符串表示的编码如果检测失败则返回 "UNKNOWN"
*/ */
public static String detectEncoding(String text) { public static String detectEncoding(File file) {
// 尝试常见的编码 CharsetDetector charsetDetector = new CharsetDetector();
for (Charset charset : commonCharsets()) { try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file.getPath()))) {
if (isValidEncoding(text, charset)) { charsetDetector.setText(inputStream);
Platform.runLater(() -> LogUtil.getLogger(EncodingDetector.class).info("编码监测结果:{}", isValidEncoding(text, charset))); CharsetMatch match = charsetDetector.detect();
return charset.name(); 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() { public static Charset detectEncodingCharset(File file) {
return new Charset[]{ String charset = detectEncoding(file);
StandardCharsets.UTF_8, if (charset.equals(UNKNOWN)) {
StandardCharsets.UTF_16, return StandardCharsets.UTF_8;
StandardCharsets.UTF_16LE,
StandardCharsets.UTF_16BE,
StandardCharsets.ISO_8859_1
};
} }
return Charset.forName(charset);
/**
* 检查指定编码是否能够正确解码文本
*
* @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);
} }
} }

View File

@ -3,6 +3,7 @@ package org.jcnc.jnotepad.tool;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import org.jcnc.jnotepad.ui.LineNumberTextArea; import org.jcnc.jnotepad.ui.LineNumberTextArea;
import org.jcnc.jnotepad.ui.tab.JNotepadTab;
import org.jcnc.jnotepad.ui.tab.JNotepadTabPane; import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
import java.io.*; import java.io.*;
@ -48,7 +49,7 @@ public class FileUtil {
* @see LogUtil * @see LogUtil
*/ */
public static void saveTab(Class<?> currentClass) { public static void saveTab(Class<?> currentClass) {
Tab selectedTab = JNotepadTabPane.getInstance().getSelected(); JNotepadTab selectedTab = JNotepadTabPane.getInstance().getSelected();
if (selectedTab != null) { if (selectedTab != null) {
// 创建一个文件窗口 // 创建一个文件窗口
FileChooser fileChooser = new FileChooser(); FileChooser fileChooser = new FileChooser();
@ -58,24 +59,13 @@ public class FileUtil {
fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文档", "*.txt")); fileChooser.getExtensionFilters().add(new FileChooser.ExtensionFilter("文本文档", "*.txt"));
File file = fileChooser.showSaveDialog(null); File file = fileChooser.showSaveDialog(null);
if (file != null) { if (file != null) {
try ( selectedTab.save();
BufferedWriter writer = new BufferedWriter(new FileWriter(file))
) {
// 获取当前Tab页的文本编辑区
LineNumberTextArea textArea = (LineNumberTextArea) selectedTab.getContent();
// 将保存后的文件设置为已关联 // 将保存后的文件设置为已关联
textArea.setRelevance(true); selectedTab.getLineNumberTextArea().setRelevance(true);
String text = textArea.getMainTextArea().getText();
// 写入文件内容
writer.write(text);
writer.flush();
// 更新Tab页标签上的文件名 // 更新Tab页标签上的文件名
selectedTab.setText(file.getName()); selectedTab.setText(file.getName());
// 将文件对象保存到Tab页的UserData中 // 将文件对象保存到Tab页的UserData中
selectedTab.setUserData(file); selectedTab.setUserData(file);
} catch (IOException ignored) {
LogUtil.getLogger(currentClass).info("已忽视IO异常!");
}
} }
} }
} }

View File

@ -1,8 +1,18 @@
package org.jcnc.jnotepad.ui; package org.jcnc.jnotepad.ui;
import javafx.beans.property.StringProperty; import javafx.beans.property.StringProperty;
import javafx.scene.control.Tab;
import javafx.scene.control.TextArea; import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane; 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 许轲 * @author 许轲
@ -28,16 +38,50 @@ public class LineNumberTextArea extends BorderPane {
// 设定自定义样式 // 设定自定义样式
lineNumberArea.getStyleClass().add("text-line-number"); lineNumberArea.getStyleClass().add("text-line-number");
mainTextArea.getStyleClass().add("main-text-area"); mainTextArea.getStyleClass().add("main-text-area");
lineNumberArea.textProperty().addListener((observable, oldValue, newValue) -> updateLineNumberWidth()); this.setStyle(
mainTextArea.textProperty().addListener((observable, oldValue, newValue) -> updateLineNumberArea()); "-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())); mainTextArea.scrollTopProperty().addListener((observable, oldValue, newValue) -> lineNumberArea.setScrollTop(mainTextArea.getScrollTop()));
// 当行号文本区域的垂直滚动位置发生变化时使主要文本区域的滚动位置保持一致 // 当行号文本区域的垂直滚动位置发生变化时使主要文本区域的滚动位置保持一致
lineNumberArea.scrollTopProperty().addListener((observable, oldValue, newValue) -> mainTextArea.setScrollTop(lineNumberArea.getScrollTop())); lineNumberArea.scrollTopProperty().addListener((observable, oldValue, newValue) -> mainTextArea.setScrollTop(lineNumberArea.getScrollTop()));
setCenter(mainTextArea); lineNumberArea.textProperty().addListener((observable, oldValue, newValue) -> updateLineNumberWidth());
setLeft(lineNumberArea);
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() { public boolean isRelevance() {
@ -91,5 +135,6 @@ public class LineNumberTextArea extends BorderPane {
public TextArea getMainTextArea() { public TextArea getMainTextArea() {
return mainTextArea; return mainTextArea;
} }
} }

View 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);
}
}

View File

@ -4,6 +4,8 @@ import javafx.scene.control.Tab;
import org.jcnc.jnotepad.app.config.GlobalConfig; import org.jcnc.jnotepad.app.config.GlobalConfig;
import org.jcnc.jnotepad.ui.LineNumberTextArea; import org.jcnc.jnotepad.ui.LineNumberTextArea;
import java.nio.charset.Charset;
/** /**
* 封装标签页组件增加属于标签页的属性例如自动换行开关 * 封装标签页组件增加属于标签页的属性例如自动换行开关
* 每个Tab关联一个LineNumberTextArea * 每个Tab关联一个LineNumberTextArea
@ -17,12 +19,17 @@ public class JNotepadTab extends Tab {
*/ */
private boolean autoLine = false; private boolean autoLine = false;
private final LineNumberTextArea lineNumberTextArea; private final LineNumberTextArea lineNumberTextArea;
private Charset charset = Charset.defaultCharset();
public JNotepadTab(String tabTitle) { public JNotepadTab(String tabTitle) {
this(tabTitle, new LineNumberTextArea()); this(tabTitle, new LineNumberTextArea());
} }
public JNotepadTab(String tabTitle, LineNumberTextArea textArea) { public JNotepadTab(String tabTitle, LineNumberTextArea textArea) {
this(tabTitle, textArea, Charset.defaultCharset());
}
public JNotepadTab(String tabTitle, LineNumberTextArea textArea, Charset charset) {
super(tabTitle); super(tabTitle);
lineNumberTextArea = textArea; lineNumberTextArea = textArea;
this.setContent(lineNumberTextArea); this.setContent(lineNumberTextArea);
@ -41,4 +48,16 @@ public class JNotepadTab extends Tab {
public LineNumberTextArea getLineNumberTextArea() { public LineNumberTextArea getLineNumberTextArea() {
return lineNumberTextArea; return lineNumberTextArea;
} }
public Charset getCharset() {
return charset;
}
public void setCharset(Charset charset) {
this.charset = charset;
}
public void save() {
this.lineNumberTextArea.save();
}
} }

View File

@ -3,6 +3,7 @@ package org.jcnc.jnotepad.ui.tab;
import javafx.scene.control.TabPane; import javafx.scene.control.TabPane;
import org.jcnc.jnotepad.app.config.GlobalConfig; import org.jcnc.jnotepad.app.config.GlobalConfig;
import org.jcnc.jnotepad.ui.menu.JNotepadMenuBar; 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( this.getSelectionModel().selectedItemProperty().addListener(
(ov, from, to) -> { (ov, from, to) -> {
if (to != null) { if (to != null) {
// 更新菜单栏中与tab相关设置
JNotepadMenuBar.getMenuBar().updateMenuStatusBySelectedTab(); JNotepadMenuBar.getMenuBar().updateMenuStatusBySelectedTab();
} }
// 更新状态标签
JNotepadStatusBox.getInstance().updateWordCountStatusLabel();
} }
); );
} }
@ -65,5 +69,6 @@ public class JNotepadTabPane extends TabPane {
public void fireTabSelected() { public void fireTabSelected() {
JNotepadTab selectedTab = getSelected(); JNotepadTab selectedTab = getSelected();
selectedTab.setAutoLine(GlobalConfig.getConfig().getAutoLineConfig()); selectedTab.setAutoLine(GlobalConfig.getConfig().getAutoLineConfig());
JNotepadStatusBox.getInstance().updateWhenTabSelected();
} }
} }

View File

@ -1,6 +1,5 @@
package org.jcnc.jnotepad.view.init; package org.jcnc.jnotepad.view.init;
import org.jcnc.jnotepad.controller.manager.Controller;
import org.jcnc.jnotepad.controller.manager.ShortcutKey; import org.jcnc.jnotepad.controller.manager.ShortcutKey;
@ -9,10 +8,6 @@ import org.jcnc.jnotepad.controller.manager.ShortcutKey;
*/ */
public class View { public class View {
public void initTabPane() {
Controller.getInstance().initTabPane();
}
// 初始化快捷键 // 初始化快捷键
public void initShortcutKey() { public void initShortcutKey() {
new ShortcutKey().createShortcutKeyByConfig(); new ShortcutKey().createShortcutKeyByConfig();

View File

@ -7,6 +7,7 @@ import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox; import javafx.scene.layout.HBox;
import org.jcnc.jnotepad.exception.AppException; import org.jcnc.jnotepad.exception.AppException;
import org.jcnc.jnotepad.ui.menu.JNotepadMenuBar; import org.jcnc.jnotepad.ui.menu.JNotepadMenuBar;
import org.jcnc.jnotepad.ui.status.JNotepadStatusBox;
import org.jcnc.jnotepad.ui.tab.JNotepadTabPane; import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
@ -17,11 +18,6 @@ import org.jcnc.jnotepad.ui.tab.JNotepadTabPane;
*/ */
public class ViewManager { public class ViewManager {
/**
* 显示文本编码
*/
private Label enCodingLabel; // 显示文本编码
private int tabIndex = 0; private int tabIndex = 0;
private Boolean line = true; private Boolean line = true;
@ -30,19 +26,13 @@ public class ViewManager {
// 主界面布局 // 主界面布局
private BorderPane root; // 主布局 private BorderPane root; // 主布局
// 状态栏
private Label statusLabel;
private static ViewManager instance = null; private static ViewManager instance = null;
public Label getEnCodingLabel() {
return enCodingLabel;
}
/** /**
* 自增并获取标签页索引 * 自增并获取标签页索引
* *
*
* @return int 标签页索引 * @return int 标签页索引
* @apiNote ++tabIndex * @apiNote ++tabIndex
*/ */
@ -59,10 +49,6 @@ public class ViewManager {
return root; return root;
} }
public Label getStatusLabel() {
return statusLabel;
}
/** /**
* 获取ViewManager的实例如果实例不存在则创建一个新实例 * 获取ViewManager的实例如果实例不存在则创建一个新实例
@ -109,14 +95,7 @@ public class ViewManager {
// 创建标签页和文本编辑区域 // 创建标签页和文本编辑区域
root.setCenter(JNotepadTabPane.getInstance()); root.setCenter(JNotepadTabPane.getInstance());
root.setBottom(JNotepadStatusBox.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));
scene.setRoot(root); scene.setRoot(root);
} }