From 2410f6e2130dc7dff3936d796575ea238880a822 Mon Sep 17 00:00:00 2001 From: songdragon Date: Thu, 24 Aug 2023 00:12:46 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E4=BE=A6=E6=B5=8B?= =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=86=85=E5=AE=B9=E7=BC=96=E7=A0=81=EF=BC=8C?= =?UTF-8?q?=E5=B9=B6=E4=BD=BF=E7=94=A8=E4=BE=A6=E6=B5=8B=E6=88=90=E5=8A=9F?= =?UTF-8?q?=E7=9A=84=E7=BC=96=E7=A0=81=E6=89=93=E5=BC=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + pom.xml | 5 + src/main/java/module-info.java | 2 + .../Interface/ControllerInterface.java | 69 ------- src/main/java/org/jcnc/jnotepad/LunchApp.java | 3 +- .../controller/event/handler/NewFile.java | 15 +- .../controller/event/handler/OpenFile.java | 5 +- .../controller/event/handler/SaveFile.java | 11 +- .../controller/manager/Controller.java | 177 ++---------------- .../jcnc/jnotepad/tool/EncodingDetector.java | 81 ++++---- .../java/org/jcnc/jnotepad/tool/FileUtil.java | 28 +-- .../jcnc/jnotepad/ui/LineNumberTextArea.java | 53 +++++- .../jnotepad/ui/status/JNotepadStatusBox.java | 115 ++++++++++++ .../org/jcnc/jnotepad/ui/tab/JNotepadTab.java | 19 ++ .../jcnc/jnotepad/ui/tab/JNotepadTabPane.java | 5 + .../org/jcnc/jnotepad/view/init/View.java | 5 - .../jnotepad/view/manager/ViewManager.java | 25 +-- 17 files changed, 263 insertions(+), 356 deletions(-) create mode 100644 src/main/java/org/jcnc/jnotepad/ui/status/JNotepadStatusBox.java diff --git a/.gitignore b/.gitignore index 88fbce5..68e77c9 100644 --- a/.gitignore +++ b/.gitignore @@ -37,3 +37,4 @@ build/ /src/main/JNotepad.java /.idea/ /project.txt +logs/ diff --git a/pom.xml b/pom.xml index 319c9c6..cc4e2e0 100644 --- a/pom.xml +++ b/pom.xml @@ -55,6 +55,11 @@ logback-classic 1.4.11 + + com.ibm.icu + icu4j + 68.1 + diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 35e74dd..82564b9 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -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; diff --git a/src/main/java/org/jcnc/jnotepad/Interface/ControllerInterface.java b/src/main/java/org/jcnc/jnotepad/Interface/ControllerInterface.java index 34c53b9..feed804 100644 --- a/src/main/java/org/jcnc/jnotepad/Interface/ControllerInterface.java +++ b/src/main/java/org/jcnc/jnotepad/Interface/ControllerInterface.java @@ -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 rawParameters); - /** - * 获取新建文件处理事件处理程序 - * - * @param textArea 文本区域 - * @return 新建文件处理事件处理程序 - */ - EventHandler getNewFileEventHandler(LineNumberTextArea textArea); - - /** - * 获取打开文件处理事件处理程序 - * - * @return 打开文件处理事件处理程序 - */ - EventHandler getOpenFileEventHandler(); - - /** - * 获取另存为文件处理事件处理程序 - * - * @return 另存为文件处理事件处理程序 - */ - EventHandler 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和标签页 * diff --git a/src/main/java/org/jcnc/jnotepad/LunchApp.java b/src/main/java/org/jcnc/jnotepad/LunchApp.java index 0c81bfc..dd5a873 100644 --- a/src/main/java/org/jcnc/jnotepad/LunchApp.java +++ b/src/main/java/org/jcnc/jnotepad/LunchApp.java @@ -65,8 +65,7 @@ public class LunchApp extends Application { primaryStage.show(); ViewManager viewManager = ViewManager.getInstance(scene); viewManager.initScreen(scene); - // 初始化菜单项和标签栏 - view.initTabPane(); + // 初始化快捷键 view.initShortcutKey(); // 使用线程池加载关联文件并创建文本区域 diff --git a/src/main/java/org/jcnc/jnotepad/controller/event/handler/NewFile.java b/src/main/java/org/jcnc/jnotepad/controller/event/handler/NewFile.java index 1e0e83f..0170693 100644 --- a/src/main/java/org/jcnc/jnotepad/controller/event/handler/NewFile.java +++ b/src/main/java/org/jcnc/jnotepad/controller/event/handler/NewFile.java @@ -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 { */ @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(); } } \ No newline at end of file diff --git a/src/main/java/org/jcnc/jnotepad/controller/event/handler/OpenFile.java b/src/main/java/org/jcnc/jnotepad/controller/event/handler/OpenFile.java index 4fdd55c..97fe5bb 100644 --- a/src/main/java/org/jcnc/jnotepad/controller/event/handler/OpenFile.java +++ b/src/main/java/org/jcnc/jnotepad/controller/event/handler/OpenFile.java @@ -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; * 打开文件的事件处理程序。 *

* 当用户选择打开文件时,将创建一个新的文本编辑区,并在Tab页中显示。 + * * @author 许轲 */ public class OpenFile implements EventHandler { @@ -38,8 +37,6 @@ public class OpenFile implements EventHandler { protected Void call() { // 调用控制器的getText方法,读取文件内容 controller.getText(file); - // 更新编码标签 - controller.upDateEncodingLabel(((LineNumberTextArea) JNotepadTabPane.getInstance().getSelected().getContent()).getMainTextArea().getText()); return null; } }; diff --git a/src/main/java/org/jcnc/jnotepad/controller/event/handler/SaveFile.java b/src/main/java/org/jcnc/jnotepad/controller/event/handler/SaveFile.java index a6f5f76..bab2bdd 100644 --- a/src/main/java/org/jcnc/jnotepad/controller/event/handler/SaveFile.java +++ b/src/main/java/org/jcnc/jnotepad/controller/event/handler/SaveFile.java @@ -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 { @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 { saveTab(this.getClass()); } else { logger.info("当前保存文件为关联打开文件,调用自动保存方法"); - // 打开的是关联文件 - Controller controller = Controller.getInstance(); - // 自动保存 - controller.autoSave(textArea); + // 调用tab保存 + selectedTab.save(); } } } diff --git a/src/main/java/org/jcnc/jnotepad/controller/manager/Controller.java b/src/main/java/org/jcnc/jnotepad/controller/manager/Controller.java index 1d06777..67bdad4 100644 --- a/src/main/java/org/jcnc/jnotepad/controller/manager/Controller.java +++ b/src/main/java/org/jcnc/jnotepad/controller/manager/Controller.java @@ -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 getNewFileEventHandler(LineNumberTextArea textArea) { - return new NewFile(); - } - - /** - * 获取打开文件事件处理程序。 - * - * @return 打开文件事件处理程序 - */ - @Override - public EventHandler 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 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; } }; diff --git a/src/main/java/org/jcnc/jnotepad/tool/EncodingDetector.java b/src/main/java/org/jcnc/jnotepad/tool/EncodingDetector.java index 6da7196..9e77439 100644 --- a/src/main/java/org/jcnc/jnotepad/tool/EncodingDetector.java +++ b/src/main/java/org/jcnc/jnotepad/tool/EncodingDetector.java @@ -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; + } + 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); - } - } \ No newline at end of file diff --git a/src/main/java/org/jcnc/jnotepad/tool/FileUtil.java b/src/main/java/org/jcnc/jnotepad/tool/FileUtil.java index 0672391..bc8928a 100644 --- a/src/main/java/org/jcnc/jnotepad/tool/FileUtil.java +++ b/src/main/java/org/jcnc/jnotepad/tool/FileUtil.java @@ -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(); - // 将保存后的文件设置为已关联 - textArea.setRelevance(true); - String text = textArea.getMainTextArea().getText(); - // 写入文件内容 - writer.write(text); - writer.flush(); - // 更新Tab页标签上的文件名 - selectedTab.setText(file.getName()); - // 将文件对象保存到Tab页的UserData中 - selectedTab.setUserData(file); - } catch (IOException ignored) { - LogUtil.getLogger(currentClass).info("已忽视IO异常!"); - } + selectedTab.save(); + // 将保存后的文件设置为已关联 + selectedTab.getLineNumberTextArea().setRelevance(true); + // 更新Tab页标签上的文件名 + selectedTab.setText(file.getName()); + // 将文件对象保存到Tab页的UserData中 + selectedTab.setUserData(file); } } } diff --git a/src/main/java/org/jcnc/jnotepad/ui/LineNumberTextArea.java b/src/main/java/org/jcnc/jnotepad/ui/LineNumberTextArea.java index 583ba2a..9008a0f 100644 --- a/src/main/java/org/jcnc/jnotepad/ui/LineNumberTextArea.java +++ b/src/main/java/org/jcnc/jnotepad/ui/LineNumberTextArea.java @@ -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; } + } diff --git a/src/main/java/org/jcnc/jnotepad/ui/status/JNotepadStatusBox.java b/src/main/java/org/jcnc/jnotepad/ui/status/JNotepadStatusBox.java new file mode 100644 index 0000000..a37051a --- /dev/null +++ b/src/main/java/org/jcnc/jnotepad/ui/status/JNotepadStatusBox.java @@ -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); + } + +} diff --git a/src/main/java/org/jcnc/jnotepad/ui/tab/JNotepadTab.java b/src/main/java/org/jcnc/jnotepad/ui/tab/JNotepadTab.java index 4dfbc50..f12c90a 100644 --- a/src/main/java/org/jcnc/jnotepad/ui/tab/JNotepadTab.java +++ b/src/main/java/org/jcnc/jnotepad/ui/tab/JNotepadTab.java @@ -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(); + } } diff --git a/src/main/java/org/jcnc/jnotepad/ui/tab/JNotepadTabPane.java b/src/main/java/org/jcnc/jnotepad/ui/tab/JNotepadTabPane.java index ca98c9b..602e2e6 100644 --- a/src/main/java/org/jcnc/jnotepad/ui/tab/JNotepadTabPane.java +++ b/src/main/java/org/jcnc/jnotepad/ui/tab/JNotepadTabPane.java @@ -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(); } } diff --git a/src/main/java/org/jcnc/jnotepad/view/init/View.java b/src/main/java/org/jcnc/jnotepad/view/init/View.java index 7055ffe..82abcdf 100644 --- a/src/main/java/org/jcnc/jnotepad/view/init/View.java +++ b/src/main/java/org/jcnc/jnotepad/view/init/View.java @@ -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(); diff --git a/src/main/java/org/jcnc/jnotepad/view/manager/ViewManager.java b/src/main/java/org/jcnc/jnotepad/view/manager/ViewManager.java index af87eb0..846d1d4 100644 --- a/src/main/java/org/jcnc/jnotepad/view/manager/ViewManager.java +++ b/src/main/java/org/jcnc/jnotepad/view/manager/ViewManager.java @@ -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); }