diff --git a/pom.xml b/pom.xml
index a1effb8..08c2f5a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -6,7 +6,7 @@
org.jcnc
JNotepad
- 1.1.4-beta
+ 1.0.14-alpha
JNotepad
jar
diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java
index 8cefcaa..40812fd 100644
--- a/src/main/java/module-info.java
+++ b/src/main/java/module-info.java
@@ -48,5 +48,6 @@ module org.jcnc.jnotepad {
exports org.jcnc.jnotepad.component.stage.topmenu.help;
exports org.jcnc.jnotepad.component.stage.topmenu.plugin;
exports org.jcnc.jnotepad.component.module.vbox.components;
+ exports org.jcnc.jnotepad.views.root.center.main.center.tab;
}
\ No newline at end of file
diff --git a/src/main/java/org/jcnc/jnotepad/api/core/views/menu/builder/AbstractMenuBuilder.java b/src/main/java/org/jcnc/jnotepad/api/core/views/menu/builder/AbstractMenuBuilder.java
index d62a6df..ad0d88f 100644
--- a/src/main/java/org/jcnc/jnotepad/api/core/views/menu/builder/AbstractMenuBuilder.java
+++ b/src/main/java/org/jcnc/jnotepad/api/core/views/menu/builder/AbstractMenuBuilder.java
@@ -3,9 +3,7 @@ package org.jcnc.jnotepad.api.core.views.menu.builder;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
-import javafx.scene.control.Menu;
-import javafx.scene.control.MenuItem;
-import javafx.scene.control.SeparatorMenuItem;
+import javafx.scene.control.*;
/**
* 抽象菜单建造者类
@@ -60,14 +58,14 @@ public abstract class AbstractMenuBuilder {
}
/**
- * 添加单选菜单项
+ * 添加单选菜单项 todo 待完善
*
* @param label 菜单项名称
* @param eventHandler 事件
* @return 建造者
*/
public B addRadioMenuItem(String label, EventHandler eventHandler) {
- MenuItem menuItem = new MenuItem(label);
+ RadioMenuItem menuItem = new RadioMenuItem(label);
menuItem.setOnAction(eventHandler);
getItems().add(menuItem);
return getBuilder();
@@ -81,7 +79,7 @@ public abstract class AbstractMenuBuilder {
* @return 建造者
*/
public B addCheckMenuItem(String label, EventHandler eventHandler) {
- MenuItem menuItem = new MenuItem(label);
+ CheckMenuItem menuItem = new CheckMenuItem(label);
menuItem.setOnAction(eventHandler);
getItems().add(menuItem);
return getBuilder();
@@ -98,6 +96,21 @@ public abstract class AbstractMenuBuilder {
return getBuilder();
}
+ /**
+ * 添加菜单
+ *
+ * @param menu 菜单
+ * @param disable 是否禁用
+ * @return 建造者
+ */
+ public B addMenu(Menu menu, boolean disable) {
+ if (!disable) {
+ return getBuilder();
+ }
+ getItems().add(menu);
+ return getBuilder();
+ }
+
/**
* 添加分割线
*
diff --git a/src/main/java/org/jcnc/jnotepad/common/constants/AppConstants.java b/src/main/java/org/jcnc/jnotepad/common/constants/AppConstants.java
index 4f2560c..69a7a19 100644
--- a/src/main/java/org/jcnc/jnotepad/common/constants/AppConstants.java
+++ b/src/main/java/org/jcnc/jnotepad/common/constants/AppConstants.java
@@ -13,7 +13,7 @@ public class AppConstants {
/**
* 版本号
*/
- public static final String VERSION = "1.0.14";
+ public static final String VERSION = "1.0.14-alpha";
/**
* 作者
*/
diff --git a/src/main/java/org/jcnc/jnotepad/component/stage/topmenu/help/HelpPaneStage.java b/src/main/java/org/jcnc/jnotepad/component/stage/topmenu/help/HelpPaneStage.java
index 81a65e0..7ac86ad 100644
--- a/src/main/java/org/jcnc/jnotepad/component/stage/topmenu/help/HelpPaneStage.java
+++ b/src/main/java/org/jcnc/jnotepad/component/stage/topmenu/help/HelpPaneStage.java
@@ -1,6 +1,5 @@
package org.jcnc.jnotepad.component.stage.topmenu.help;
-import atlantafx.base.controls.Notification;
import atlantafx.base.theme.Styles;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
@@ -19,8 +18,8 @@ import javafx.stage.Stage;
import org.jcnc.jnotepad.api.core.component.stage.AbstractPaneStage;
import org.jcnc.jnotepad.util.ClipboardUtil;
import org.jcnc.jnotepad.util.LogUtil;
+import org.jcnc.jnotepad.util.NotificationUtil;
import org.jcnc.jnotepad.util.UiUtil;
-import org.jcnc.jnotepad.views.manager.RootManager;
import static org.jcnc.jnotepad.common.constants.AppConstants.*;
@@ -128,16 +127,7 @@ public class HelpPaneStage extends AbstractPaneStage {
bottomBox.setAlignment(Pos.BOTTOM_RIGHT);
Button leftBtn = getButton(" 复制并关闭 ", event -> {
- // 获取 RootManager 的实例
- RootManager rootManager = RootManager.getInstance();
-
- // 创建一个新的 Notification
- Notification notification = new Notification();
- notification.setMessage("已成功复制软件信息!");
-
- // 调用 RootManager 中的方法来显示 Notification
- rootManager.addNotificationToStackPane(rootManager.getRootStackPane(), notification);
-
+ NotificationUtil.infoNotification("软件信息已经复制到剪贴板!");
String info = "软件名字:" + APP_NAME + "\t" + "版本:" + VERSION;
ClipboardUtil.writeTextToClipboard(info);
LogUtil.getLogger(this.getClass()).info("软件信息已经复制到剪贴板:{}", info);
diff --git a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/NewFile.java b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/NewFile.java
index 81c107c..6c9b0b4 100644
--- a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/NewFile.java
+++ b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/NewFile.java
@@ -2,18 +2,8 @@ package org.jcnc.jnotepad.controller.event.handler.menuitem;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
-import javafx.scene.control.Tab;
-import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
-import org.jcnc.jnotepad.common.constants.AppConstants;
-import org.jcnc.jnotepad.common.constants.TextConstants;
-import org.jcnc.jnotepad.component.module.TextCodeArea;
-import org.jcnc.jnotepad.views.manager.BottomStatusBoxManager;
-import org.jcnc.jnotepad.views.manager.CenterTabPaneManager;
-import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTab;
-import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTabPane;
-import java.util.Comparator;
-import java.util.List;
+import static org.jcnc.jnotepad.util.TabUtil.addNewFileTab;
/**
* 新建文件事件的事件处理程序。
@@ -32,46 +22,4 @@ public class NewFile implements EventHandler {
public void handle(ActionEvent event) {
addNewFileTab();
}
-
- /**
- * 添加新的文件标签页。
- */
- public void addNewFileTab() {
- // 创建一个新的文本编辑区
- TextCodeArea textArea = new TextCodeArea();
- // TODO: refactor:统一TextArea新建、绑定监听器入口
- // 设定初始索引
- int index = 1;
- StringBuilder tabTitle = new StringBuilder();
- // 获取当前默认创建标签页集合
- List tabs = CenterTabPane.getInstance()
- .getTabs()
- .stream()
- // 排除不属于默认创建的标签页
- .filter(tab -> AppConstants.TABNAME_PATTERN.matcher(tab.getText()).matches())
- // 对默认创建的标签页进行排序
- .sorted(Comparator.comparing(Tab::getText))
- // 转为List集合
- .toList();
- // 构建初始标签页名称
- tabTitle.append(UiResourceBundle.getContent(TextConstants.NEW_FILE)).append(index);
- for (Tab tab : tabs) {
- if (tab.getText().contentEquals(tabTitle)) {
- tabTitle.setLength(0);
- tabTitle.append(UiResourceBundle.getContent(TextConstants.NEW_FILE)).append(++index);
- } else {
- break;
- }
- }
- // 创建标签页
- CenterTab centerTab = new CenterTab(
- tabTitle.toString(),
- textArea);
- // 设置当前标签页与本地文件无关联
- centerTab.setRelevance(false);
- // 将Tab页添加到TabPane中
- CenterTabPaneManager.getInstance().addNewTab(centerTab);
- // 更新编码信息
- BottomStatusBoxManager.getInstance().updateEncodingLabel();
- }
}
diff --git a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/OpenConfig.java b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/OpenConfig.java
index e7efc24..b73c3f7 100644
--- a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/OpenConfig.java
+++ b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/OpenConfig.java
@@ -6,6 +6,8 @@ import org.jcnc.jnotepad.util.LogUtil;
import java.io.File;
+import static org.jcnc.jnotepad.util.TabUtil.openFileToTab;
+
/**
* 打开配置文件事件处理程序。
*
@@ -26,6 +28,6 @@ public class OpenConfig extends OpenFile {
File file = UserConfigController.getInstance().getConfigPath().toFile();
LogUtil.getLogger(this.getClass()).info("已调用打开配置文件功能, {}", file);
// 创建打开文件的任务并启动线程执行任务
- openFile(file);
+ openFileToTab(file);
}
}
diff --git a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/OpenFile.java b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/OpenFile.java
index ededf51..580ffce 100644
--- a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/OpenFile.java
+++ b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/OpenFile.java
@@ -2,26 +2,18 @@ package org.jcnc.jnotepad.controller.event.handler.menuitem;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
-import javafx.scene.control.Tab;
import javafx.stage.FileChooser;
import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
import org.jcnc.jnotepad.common.constants.TextConstants;
import org.jcnc.jnotepad.common.manager.ApplicationCacheManager;
-import org.jcnc.jnotepad.component.module.TextCodeArea;
import org.jcnc.jnotepad.component.stage.dialog.factory.impl.BasicFileChooserFactory;
import org.jcnc.jnotepad.model.entity.Cache;
import org.jcnc.jnotepad.model.enums.CacheExpirationTime;
-import org.jcnc.jnotepad.util.EncodingDetector;
-import org.jcnc.jnotepad.util.LogUtil;
import org.jcnc.jnotepad.util.UiUtil;
-import org.jcnc.jnotepad.views.manager.CenterTabPaneManager;
-import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTab;
-import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTabPane;
import java.io.File;
-import java.nio.charset.Charset;
-import static org.jcnc.jnotepad.util.FileUtil.getFileText;
+import static org.jcnc.jnotepad.util.TabUtil.openFileToTab;
/**
@@ -60,73 +52,6 @@ public class OpenFile implements EventHandler {
cache.setCacheData(file.getParent());
CACHE_MANAGER.addCache(cache);
}
- openFile(file);
- }
-
- /**
- * 打开文件。
- *
- * @param file 文件对象
- */
- public static void openFile(File file) {
- // 获取标签页集合
- CenterTabPane centerTabPane = CenterTabPane.getInstance();
- // 遍历标签页,查找匹配的标签页
- for (Tab tab : centerTabPane.getTabs()) {
- // 获取绑定的文件
- File tabFile = (File) tab.getUserData();
- if (tabFile == null) {
- continue;
- }
- if (file.getPath().equals((tabFile).getPath())) {
- // 找到匹配的标签页,设置为选中状态并跳转
- centerTabPane.getSelectionModel().select(tab);
- return;
- }
- }
- getText(file);
- }
-
- /**
- * 读取文本文件的内容。
- *
- * @param file 文件对象
- */
- public static void getText(File file) {
- TextCodeArea textCodeArea = createNewTextArea();
- // 检测文件编码
- Charset encoding = EncodingDetector.detectEncodingCharset(file);
- String fileText = getFileText(file, encoding);
- LogUtil.getLogger(OpenFile.class).info("已调用读取文件功能");
- textCodeArea.appendText(fileText);
- CenterTab tab = createNewTab(file.getName(), textCodeArea, encoding);
- // 设置当前标签页关联本地文件
- tab.setRelevance(true);
- // 设置标签页关联文件
- tab.setUserData(file);
- // 设置关联文件最后的修改时间
- tab.setLastModifiedTimeOfAssociatedFile(file.lastModified());
- CenterTabPaneManager.getInstance().addNewTab(tab);
- }
-
-
- /**
- * 创建新的文本区域。
- *
- * @return 新的文本区域
- */
- private static TextCodeArea createNewTextArea() {
- return new TextCodeArea();
- }
-
- /**
- * 创建新的标签页。
- *
- * @param tabName 标签名
- * @param textCodeArea 文本区域
- * @return 新的标签页
- */
- private static CenterTab createNewTab(String tabName, TextCodeArea textCodeArea, Charset charset) {
- return new CenterTab(tabName, textCodeArea, charset);
+ openFileToTab(file);
}
}
diff --git a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/RenameFile.java b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/RenameFile.java
index 92bc735..a636d64 100644
--- a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/RenameFile.java
+++ b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/RenameFile.java
@@ -2,21 +2,9 @@ package org.jcnc.jnotepad.controller.event.handler.menuitem;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
-import javafx.scene.control.TextField;
-import javafx.scene.input.KeyCode;
-import javafx.stage.FileChooser;
-import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
-import org.jcnc.jnotepad.common.constants.TextConstants;
-import org.jcnc.jnotepad.component.stage.dialog.factory.impl.BasicFileChooserFactory;
-import org.jcnc.jnotepad.util.LogUtil;
-import org.jcnc.jnotepad.util.PopUpUtil;
-import org.jcnc.jnotepad.util.UiUtil;
import org.jcnc.jnotepad.views.manager.CenterTabPaneManager;
-import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTab;
-import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTabPane;
-import org.slf4j.Logger;
-import java.io.File;
+import static org.jcnc.jnotepad.util.TabUtil.rename;
/**
* 重命名文件事件处理器。
@@ -27,144 +15,8 @@ import java.io.File;
* @author gewuyou
*/
public class RenameFile implements EventHandler {
- static Logger logger = LogUtil.getLogger(RenameFile.class);
-
- /**
- * 重命名
- */
- public static void rename() {
- // 获取当前标签页
- CenterTab centerTab = CenterTabPaneManager.getInstance().getSelected();
- if (centerTab == null || centerTab.getText().isEmpty()) {
- return;
- }
- // 判断当前是否为关联文件
- if (centerTab.isRelevance()) {
- // 重命名关联文件
- handleRenameRelevanceFile(centerTab);
- }
- // 如果当前不是关联文件则重命名标签页
- else {
- handleRenameTab(centerTab);
- }
- }
-
- /**
- * 重命名标签页。
- *
- * @param centerTab 标签页组件
- */
- private static void handleRenameTab(CenterTab centerTab) {
- TextField textField = new TextField(centerTab.getText());
- textField.getStyleClass().add("tab-title-editable");
- // 临时记录标签页名称
- String tempName = centerTab.getText();
- // 清空标签页名称
- centerTab.setText("");
-
- // 监听 Enter 键,完成编辑
- textField.setOnKeyPressed(event -> {
- if (event.getCode() == KeyCode.ENTER) {
- String newTabName = textField.getText();
- // 检查是否存在相同名称的标签页
- if (isTabNameExists(newTabName)) {
-
- // 显示弹窗并提示用户更换名称
- showDuplicateNameAlert(newTabName);
-
- // 恢复原始名称
- centerTab.setText(tempName);
-
- } else {
- centerTab.setText(newTabName);
- // 可选:移除 TextField 的图形
- centerTab.setGraphic(null);
- // 可选:恢复标签页的关闭按钮
- centerTab.setClosable(true);
- }
- }
- });
-
- // 监听失去焦点事件,完成编辑
- textField.focusedProperty().addListener((observable, oldValue, newValue) -> {
- String newTabName = textField.getText();
- // 检查是否存在相同名称的标签页
- if (isTabNameExists(newTabName)) {
- // 恢复原始名称
- centerTab.setText(tempName);
-
- }
- if (Boolean.FALSE.equals(newValue)) {
- centerTab.setText(newTabName);
- // 可选:移除 TextField 的图形
- centerTab.setGraphic(null);
- // 可选:恢复标签页的关闭按钮
- centerTab.setClosable(true);
-
- }
- });
-
- centerTab.setClosable(false);
- // 设置 TextField 作为标签页的图形
- centerTab.setGraphic(textField);
- // 默认获取焦点并选中所有文字
- textField.requestFocus();
- textField.selectAll();
- }
-
- /**
- * 判断是否存在具有相同名称的标签页。
- *
- * @param newTabName 要检查的新标签页名称
- * @return 如果存在具有相同名称的标签页,则返回 true;否则返回 false
- */
- private static boolean isTabNameExists(String newTabName) {
- CenterTabPane tabPane = CenterTabPane.getInstance();
- return tabPane.getTabs().stream()
- .anyMatch(tab -> tab.getText().equals(newTabName));
- }
-
- /**
- * 显示警告弹窗,提示用户更换重复的名称。
- */
- private static void showDuplicateNameAlert(String newTabName) {
-
- PopUpUtil.errorAlert("重命名错误", "\" " + newTabName + "\" 和已有标签页名字重复", "请再次重命名", null, null);
-
- }
-
- /**
- * 重命名关联文件。
- *
- * @param centerTab 标签页组件
- */
- private static void handleRenameRelevanceFile(CenterTab centerTab) {
- // 获取原始文件对象
- File file = (File) centerTab.getUserData();
-
- // 获取应用窗口并绑定
- File newFile = BasicFileChooserFactory.getInstance()
- .createFileChooser(
- UiResourceBundle.getContent(TextConstants.RENAME),
- centerTab.getText(),
- new File(file.getParent()),
- new FileChooser.ExtensionFilter("All types", "*.*"))
- .showSaveDialog(UiUtil.getAppWindow());
- if (newFile != null) {
- boolean rename = file.renameTo(newFile);
- // 设置文件数据
- centerTab.setUserData(newFile);
- if (rename) {
- centerTab.setText(newFile.getName());
- logger.info("文件重命名成功");
- } else {
- logger.debug("文件重命名失败");
- }
- }
- }
-
@Override
public void handle(ActionEvent actionEvent) {
- rename();
+ rename(CenterTabPaneManager.getInstance().getSelected());
}
}
diff --git a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/SaveAsFile.java b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/SaveAsFile.java
index 57fb530..eb754d2 100644
--- a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/SaveAsFile.java
+++ b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/SaveAsFile.java
@@ -2,6 +2,9 @@ package org.jcnc.jnotepad.controller.event.handler.menuitem;
import javafx.event.ActionEvent;
import org.jcnc.jnotepad.util.LogUtil;
+import org.jcnc.jnotepad.views.manager.CenterTabPaneManager;
+
+import static org.jcnc.jnotepad.util.TabUtil.saveAsFile;
/**
* 保存文件事件处理器。
@@ -22,6 +25,6 @@ public class SaveAsFile extends SaveFile {
@Override
public void handle(ActionEvent event) {
LogUtil.getLogger(SaveAsFile.class).info("已调用另存为功能");
- saveAsFile();
+ saveAsFile(CenterTabPaneManager.getInstance().getSelected());
}
}
diff --git a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/SaveFile.java b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/SaveFile.java
index f3534a6..a57f382 100644
--- a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/SaveFile.java
+++ b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menuitem/SaveFile.java
@@ -2,25 +2,9 @@ package org.jcnc.jnotepad.controller.event.handler.menuitem;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
-import javafx.stage.FileChooser;
-import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
-import org.jcnc.jnotepad.common.constants.TextConstants;
-import org.jcnc.jnotepad.common.manager.ApplicationCacheManager;
-import org.jcnc.jnotepad.component.stage.dialog.factory.impl.BasicFileChooserFactory;
-import org.jcnc.jnotepad.controller.config.UserConfigController;
-import org.jcnc.jnotepad.controller.i18n.LocalizationController;
-import org.jcnc.jnotepad.model.entity.Cache;
-import org.jcnc.jnotepad.model.enums.CacheExpirationTime;
-import org.jcnc.jnotepad.util.LogUtil;
-import org.jcnc.jnotepad.util.UiUtil;
import org.jcnc.jnotepad.views.manager.CenterTabPaneManager;
-import org.jcnc.jnotepad.views.manager.TopMenuBarManager;
-import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTab;
-import org.slf4j.Logger;
-import java.io.File;
-
-import static org.jcnc.jnotepad.controller.config.UserConfigController.CONFIG_NAME;
+import static org.jcnc.jnotepad.util.TabUtil.saveFile;
/**
* 保存文件事件处理程序。
@@ -31,68 +15,6 @@ import static org.jcnc.jnotepad.controller.config.UserConfigController.CONFIG_NA
* @author gewuyou
*/
public class SaveFile implements EventHandler {
- private static final ApplicationCacheManager CACHE_MANAGER = ApplicationCacheManager.getInstance();
- static Logger logger = LogUtil.getLogger(SaveFile.class);
-
- public static void saveFile() {
- // 获取当前tab页
- CenterTab selectedTab = CenterTabPaneManager.getInstance().getSelected();
- if (selectedTab == null) {
- return;
- }
- // 如果打开的是非关联文件,则调用另存为方法
- if (!selectedTab.isRelevance()) {
- logger.info("当前保存文件为非关联打开文件,调用另存为方法");
- saveAsFile();
- } else {
- logger.info("当前保存文件为关联打开文件,调用自动保存方法");
- // 调用tab保存方法
- selectedTab.saveSelectedFileTab();
- // 如果该文件是配置文件,则刷新快捷键
- if (CONFIG_NAME.equals(selectedTab.getText())) {
- // 重新加载语言包和快捷键
- UserConfigController.getInstance().loadConfig();
- TopMenuBarManager.getInstance().initShortcutKeys();
- LocalizationController.initLocal();
- logger.info("已刷新语言包!");
- logger.info("已刷新快捷键!");
- }
- }
- }
-
- /**
- * 保存标签页的方法。
- *
- * @apiNote 将当前选中的标签页进行另存为弹出窗口式的保存。
- * @see LogUtil
- */
- public static void saveAsFile() {
- CenterTab selectedTab = CenterTabPaneManager.getInstance().getSelected();
- if (selectedTab == null) {
- return;
- }
- Cache cache = CACHE_MANAGER.getCache("folder", "saveFile");
- File file = BasicFileChooserFactory.getInstance().createFileChooser(
- UiResourceBundle.getContent(TextConstants.SAVE_AS),
- selectedTab.getText(),
- cache == null ? null : new File((String) cache.getCacheData()),
- new FileChooser.ExtensionFilter("All types", "*.*"))
- .showSaveDialog(UiUtil.getAppWindow());
- if (file != null) {
- if (cache == null) {
- CACHE_MANAGER.addCache(CACHE_MANAGER.createCache("folder", "saveFile", file.getParent(), CacheExpirationTime.NEVER_EXPIRES.getValue()));
- } else {
- cache.setCacheData(file.getParent());
- CACHE_MANAGER.addCache(cache);
- }
- logger.info("正在保存文件: {}", file.getName());
- selectedTab.save(file);
- // 将保存后的文件设置为关联文件
- selectedTab.setRelevance(true);
- // 更新标签页上的文件名
- selectedTab.setText(file.getName());
- }
- }
/**
* 处理保存文件事件。
@@ -103,6 +25,7 @@ public class SaveFile implements EventHandler {
*/
@Override
public void handle(ActionEvent actionEvent) {
- saveFile();
+ // 保存当前选中的标签页
+ saveFile(CenterTabPaneManager.getInstance().getSelected());
}
}
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 9b6b851..3be9d5a 100644
--- a/src/main/java/org/jcnc/jnotepad/controller/manager/Controller.java
+++ b/src/main/java/org/jcnc/jnotepad/controller/manager/Controller.java
@@ -2,14 +2,15 @@ package org.jcnc.jnotepad.controller.manager;
import org.jcnc.jnotepad.common.manager.ApplicationCacheManager;
import org.jcnc.jnotepad.component.module.interfaces.ControllerAble;
-import org.jcnc.jnotepad.controller.event.handler.menuitem.NewFile;
-import org.jcnc.jnotepad.controller.event.handler.menuitem.OpenFile;
import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import static org.jcnc.jnotepad.util.TabUtil.addNewFileTab;
+import static org.jcnc.jnotepad.util.TabUtil.openFileToTab;
+
/**
* 控制器类,实现 ControllerAble 接口,用于管理文本编辑器的各种操作和事件处理。
*
@@ -44,7 +45,7 @@ public class Controller implements ControllerAble> {
// 判空
List fileTab = (List) cacheData.orElse(Collections.emptyList());
// 打开上次打开的标签页
- fileTab.forEach(filePath -> OpenFile.openFile(new File(filePath)));
+ fileTab.forEach(filePath -> openFileToTab(new File(filePath)));
if (!rawParameters.isEmpty()) {
String filePath = rawParameters.get(0);
@@ -52,7 +53,7 @@ public class Controller implements ControllerAble> {
return;
}
if (fileTab.isEmpty()) {
- new NewFile().addNewFileTab();
+ addNewFileTab();
}
}
@@ -65,7 +66,7 @@ public class Controller implements ControllerAble> {
public void openAssociatedFile(String filePath) {
File file = new File(filePath);
if (file.exists() && file.isFile()) {
- OpenFile.openFile(file);
+ openFileToTab(file);
}
}
}
diff --git a/src/main/java/org/jcnc/jnotepad/util/FileUtil.java b/src/main/java/org/jcnc/jnotepad/util/FileUtil.java
index f2a0560..33b7d74 100644
--- a/src/main/java/org/jcnc/jnotepad/util/FileUtil.java
+++ b/src/main/java/org/jcnc/jnotepad/util/FileUtil.java
@@ -5,6 +5,7 @@ import org.jcnc.jnotepad.controller.exception.AppException;
import org.jcnc.jnotepad.model.entity.DirFileModel;
import org.kordamp.ikonli.javafx.FontIcon;
+import java.awt.*;
import java.io.*;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
@@ -267,5 +268,24 @@ public class FileUtil {
throw new AppException(e);
}
}
+
+ /**
+ * Opens the file explorer to the specified file or its parent directory.
+ *
+ * @param file the file or directory to open in the file explorer
+ */
+ public static void openExplorer(File file) {
+ try { // 判断传入的是文件还是文件夹
+ if (file.isDirectory()) {
+ Desktop.getDesktop().open(file);
+ }
+ // 如果是文件则打开所在文件夹
+ else {
+ Desktop.getDesktop().open(file.getParentFile());
+ }
+ } catch (IOException e) {
+ throw new AppException(e);
+ }
+ }
}
diff --git a/src/main/java/org/jcnc/jnotepad/util/NotificationUtil.java b/src/main/java/org/jcnc/jnotepad/util/NotificationUtil.java
new file mode 100644
index 0000000..3c5bf4f
--- /dev/null
+++ b/src/main/java/org/jcnc/jnotepad/util/NotificationUtil.java
@@ -0,0 +1,102 @@
+package org.jcnc.jnotepad.util;
+
+import atlantafx.base.controls.Notification;
+import atlantafx.base.theme.Styles;
+import javafx.scene.layout.StackPane;
+import org.jcnc.jnotepad.views.manager.RootManager;
+import org.kordamp.ikonli.javafx.FontIcon;
+
+import java.util.Collections;
+
+/**
+ * 通知实用程序
+ *
+ * @author gewuyou
+ */
+public class NotificationUtil {
+ private static final RootManager ROOT_MANAGER = RootManager.getInstance();
+ private static final StackPane ROOT_STACK_PANE = ROOT_MANAGER.getRootStackPane();
+
+ private NotificationUtil() {
+
+ }
+
+ /**
+ * Generates a custom notification with the given message and icon, applying the specified styles.
+ *
+ * @param message the message to display in the notification
+ * @param icon the icon to display in the notification
+ * @param styles additional styles to apply to the notification
+ */
+ public static void customNotification(String message, FontIcon icon, String... styles) {
+ Notification notification = new Notification(message, icon);
+ Collections.addAll(notification.getStyleClass(), styles);
+ RootManager.addNotificationToStackPane(ROOT_STACK_PANE, notification, true);
+ }
+
+ /**
+ * Displays a success notification with the given message.
+ *
+ * @param message the message to be displayed in the notification
+ */
+ public static void successNotification(String message) {
+ Notification notification = new Notification(message, UiUtil.getSuccessIcon());
+ setStyleClass(notification, Styles.SUCCESS);
+ RootManager.addNotificationToStackPane(ROOT_STACK_PANE, notification, true);
+ }
+
+ /**
+ * Generates an info notification with the given message.
+ *
+ * @param message the message to display in the notification
+ */
+ public static void infoNotification(String message) {
+ Notification notification = new Notification(message, UiUtil.getInfoIcon());
+ setStyleClass(notification, Styles.ACCENT);
+ RootManager.addNotificationToStackPane(ROOT_STACK_PANE, notification, true);
+ }
+
+ /**
+ * Generates an error notification with the given message and displays it on the root stack pane.
+ *
+ * @param message the error message to be displayed
+ */
+ public static void errorNotification(String message) {
+ Notification notification = new Notification(message, UiUtil.getErrorIcon());
+ setStyleClass(notification, Styles.DANGER);
+ RootManager.addNotificationToStackPane(ROOT_STACK_PANE, notification, true);
+ }
+
+ /**
+ * Generates a warning notification with the given message and displays it on the root stack pane.
+ *
+ * @param message the warning message to be displayed
+ */
+ public static void warningNotification(String message) {
+ Notification notification = new Notification(message, UiUtil.getWarningIcon());
+ setStyleClass(notification, Styles.WARNING);
+ RootManager.addNotificationToStackPane(ROOT_STACK_PANE, notification, true);
+ }
+
+ /**
+ * Generates a question notification with the given message and displays it on the root stack pane.
+ *
+ * @param message the question message to be displayed
+ */
+ public static void questionNotification(String message) {
+ Notification notification = new Notification(message, UiUtil.getQuestionIcon());
+ setStyleClass(notification, Styles.ACCENT);
+ RootManager.addNotificationToStackPane(ROOT_STACK_PANE, notification, true);
+ }
+
+ /**
+ * Sets the style class of the given notification.
+ *
+ * @param notification The notification object to set the style class for.
+ * @param styleClass The style class to add to the notification.
+ */
+ private static void setStyleClass(Notification notification, String styleClass) {
+ notification.getStyleClass().add(Styles.ELEVATED_1);
+ notification.getStyleClass().add(styleClass);
+ }
+}
diff --git a/src/main/java/org/jcnc/jnotepad/util/TabUtil.java b/src/main/java/org/jcnc/jnotepad/util/TabUtil.java
new file mode 100644
index 0000000..6611148
--- /dev/null
+++ b/src/main/java/org/jcnc/jnotepad/util/TabUtil.java
@@ -0,0 +1,386 @@
+package org.jcnc.jnotepad.util;
+
+import javafx.scene.control.Tab;
+import javafx.scene.control.TextField;
+import javafx.scene.input.KeyCode;
+import javafx.stage.FileChooser;
+import org.jcnc.jnotepad.api.core.views.menu.builder.ContextMenuBuilder;
+import org.jcnc.jnotepad.api.core.views.menu.builder.MenuBuilder;
+import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
+import org.jcnc.jnotepad.common.constants.AppConstants;
+import org.jcnc.jnotepad.common.constants.TextConstants;
+import org.jcnc.jnotepad.common.manager.ApplicationCacheManager;
+import org.jcnc.jnotepad.component.module.TextCodeArea;
+import org.jcnc.jnotepad.component.stage.dialog.factory.impl.BasicFileChooserFactory;
+import org.jcnc.jnotepad.controller.config.UserConfigController;
+import org.jcnc.jnotepad.controller.event.handler.menuitem.OpenFile;
+import org.jcnc.jnotepad.controller.i18n.LocalizationController;
+import org.jcnc.jnotepad.model.entity.Cache;
+import org.jcnc.jnotepad.model.enums.CacheExpirationTime;
+import org.jcnc.jnotepad.views.manager.BottomStatusBoxManager;
+import org.jcnc.jnotepad.views.manager.CenterTabPaneManager;
+import org.jcnc.jnotepad.views.manager.TopMenuBarManager;
+import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTab;
+import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTabPane;
+import org.slf4j.Logger;
+
+import java.io.File;
+import java.nio.charset.Charset;
+import java.util.Comparator;
+import java.util.List;
+
+import static org.jcnc.jnotepad.controller.config.UserConfigController.CONFIG_NAME;
+import static org.jcnc.jnotepad.util.FileUtil.getFileText;
+
+/**
+ * 标签页工具
+ *
+ * @author gewuyou
+ */
+public class TabUtil {
+ private static final ApplicationCacheManager CACHE_MANAGER = ApplicationCacheManager.getInstance();
+ static Logger logger = LogUtil.getLogger(TabUtil.class);
+ private TabUtil() {
+ }
+
+ /**
+ * 保存文件标签页
+ */
+ public static void saveFile(CenterTab tab) {
+ if (tab == null) {
+ return;
+ }
+ // 如果打开的是非关联文件,则调用另存为方法
+ if (!tab.isRelevance()) {
+ logger.info("当前保存文件为非关联打开文件,调用另存为方法");
+ saveAsFile(tab);
+ } else {
+ logger.info("当前保存文件为关联打开文件,调用自动保存方法");
+ // 调用tab保存方法
+ tab.saveSelectedFileTab();
+ // 如果该文件是配置文件,则刷新快捷键
+ if (CONFIG_NAME.equals(tab.getText())) {
+ // 重新加载语言包和快捷键
+ UserConfigController.getInstance().loadConfig();
+ TopMenuBarManager.getInstance().initShortcutKeys();
+ LocalizationController.initLocal();
+ logger.info("已刷新语言包!");
+ logger.info("已刷新快捷键!");
+ }
+ }
+ }
+
+ /**
+ * 另存为
+ *
+ * @apiNote 将当前选中的标签页进行另存为弹出窗口式的保存。
+ * @see LogUtil
+ */
+ public static void saveAsFile(CenterTab tab) {
+ if (tab == null) {
+ return;
+ }
+ Cache cache = CACHE_MANAGER.getCache("folder", "saveFile");
+ File file = BasicFileChooserFactory.getInstance().createFileChooser(
+ UiResourceBundle.getContent(TextConstants.SAVE_AS),
+ tab.getText(),
+ cache == null ? null : new File((String) cache.getCacheData()),
+ new FileChooser.ExtensionFilter("All types", "*.*"))
+ .showSaveDialog(UiUtil.getAppWindow());
+ if (file != null) {
+ if (cache == null) {
+ CACHE_MANAGER.addCache(CACHE_MANAGER.createCache("folder", "saveFile", file.getParent(), CacheExpirationTime.NEVER_EXPIRES.getValue()));
+ } else {
+ cache.setCacheData(file.getParent());
+ CACHE_MANAGER.addCache(cache);
+ }
+ logger.info("正在保存文件: {}", file.getName());
+ tab.save(file);
+ // 将保存后的文件设置为关联文件
+ tab.setRelevance(true);
+ // 更新标签页上的文件名
+ tab.setText(file.getName());
+ }
+ }
+
+ /**
+ * 重命名
+ */
+ public static void rename(CenterTab tab) {
+ if (tab == null || tab.getText().isEmpty()) {
+ return;
+ }
+ // 判断当前是否为关联文件
+ if (tab.isRelevance()) {
+ // 重命名关联文件
+ handleRenameRelevanceFile(tab);
+ }
+ // 如果当前不是关联文件则重命名标签页
+ else {
+ handleRenameTab(tab);
+ }
+ }
+
+ /**
+ * 重命名标签页。
+ *
+ * @param tab 标签页组件
+ */
+ private static void handleRenameTab(CenterTab tab) {
+ TextField textField = new TextField(tab.getText());
+ textField.getStyleClass().add("tab-title-editable");
+ // 临时记录标签页名称
+ String tempName = tab.getText();
+ // 清空标签页名称
+ tab.setText("");
+
+ // 监听 Enter 键,完成编辑
+ textField.setOnKeyPressed(event -> {
+ if (event.getCode() == KeyCode.ENTER) {
+ String newTabName = textField.getText();
+ // 检查是否存在相同名称的标签页
+ if (isTabNameExists(newTabName)) {
+
+ // 显示弹窗并提示用户更换名称
+ showDuplicateNameAlert(newTabName);
+
+ // 恢复原始名称
+ tab.setText(tempName);
+
+ } else {
+ tab.setText(newTabName);
+ // 可选:移除 TextField 的图形
+ tab.setGraphic(null);
+ // 可选:恢复标签页的关闭按钮
+ tab.setClosable(true);
+ }
+ }
+ });
+
+ // 监听失去焦点事件,完成编辑
+ textField.focusedProperty().addListener((observable, oldValue, newValue) -> {
+ String newTabName = textField.getText();
+ // 检查是否存在相同名称的标签页
+ if (isTabNameExists(newTabName)) {
+ // 恢复原始名称
+ tab.setText(tempName);
+
+ }
+ if (Boolean.FALSE.equals(newValue)) {
+ tab.setText(newTabName);
+ // 可选:移除 TextField 的图形
+ tab.setGraphic(null);
+ // 可选:恢复标签页的关闭按钮
+ tab.setClosable(true);
+
+ }
+ });
+
+ tab.setClosable(false);
+ // 设置 TextField 作为标签页的图形
+ tab.setGraphic(textField);
+ // 默认获取焦点并选中所有文字
+ textField.requestFocus();
+ textField.selectAll();
+ }
+
+ /**
+ * 判断是否存在具有相同名称的标签页。
+ *
+ * @param newTabName 要检查的新标签页名称
+ * @return 如果存在具有相同名称的标签页,则返回 true;否则返回 false
+ */
+ private static boolean isTabNameExists(String newTabName) {
+ CenterTabPane tabPane = CenterTabPane.getInstance();
+ return tabPane.getTabs().stream()
+ .anyMatch(tab -> tab.getText().equals(newTabName));
+ }
+
+ /**
+ * 显示警告弹窗,提示用户更换重复的名称。
+ */
+ private static void showDuplicateNameAlert(String newTabName) {
+
+ PopUpUtil.errorAlert("重命名错误", "\" " + newTabName + "\" 和已有标签页名字重复", "请再次重命名", null, null);
+
+ }
+
+ /**
+ * 重命名关联文件。
+ *
+ * @param tab 标签页组件
+ */
+ private static void handleRenameRelevanceFile(CenterTab tab) {
+ // 获取原始文件对象
+ File file = (File) tab.getUserData();
+
+ // 获取应用窗口并绑定
+ File newFile = BasicFileChooserFactory.getInstance()
+ .createFileChooser(
+ UiResourceBundle.getContent(TextConstants.RENAME),
+ tab.getText(),
+ new File(file.getParent()),
+ new FileChooser.ExtensionFilter("All types", "*.*"))
+ .showSaveDialog(UiUtil.getAppWindow());
+ if (newFile != null) {
+ boolean rename = file.renameTo(newFile);
+ // 设置文件数据
+ tab.setUserData(newFile);
+ if (rename) {
+ tab.setText(newFile.getName());
+ logger.info("文件重命名成功");
+ } else {
+ logger.debug("文件重命名失败");
+ }
+ }
+ }
+
+ /**
+ * 添加新的文件标签页。
+ */
+ public static void addNewFileTab() {
+ // 创建一个新的文本编辑区
+ TextCodeArea textArea = new TextCodeArea();
+ // 设定初始索引
+ int index = 1;
+ StringBuilder tabTitle = new StringBuilder();
+ // 获取当前默认创建标签页集合
+ List tabs = CenterTabPane.getInstance()
+ .getTabs()
+ .stream()
+ // 排除不属于默认创建的标签页
+ .filter(tab -> AppConstants.TABNAME_PATTERN.matcher(tab.getText()).matches())
+ // 对默认创建的标签页进行排序
+ .sorted(Comparator.comparing(Tab::getText))
+ // 转为List集合
+ .toList();
+ // 构建初始标签页名称
+ tabTitle.append(UiResourceBundle.getContent(TextConstants.NEW_FILE)).append(index);
+ for (Tab tab : tabs) {
+ if (tab.getText().contentEquals(tabTitle)) {
+ tabTitle.setLength(0);
+ tabTitle.append(UiResourceBundle.getContent(TextConstants.NEW_FILE)).append(++index);
+ } else {
+ break;
+ }
+ }
+ // 创建标签页
+ CenterTab centerTab = new CenterTab(
+ tabTitle.toString(),
+ textArea);
+ // 设置当前标签页与本地文件无关联
+ centerTab.setRelevance(false);
+ // 将Tab页添加到TabPane中
+ CenterTabPaneManager.getInstance().addNewTab(centerTab);
+ // 更新编码信息
+ BottomStatusBoxManager.getInstance().updateEncodingLabel();
+ }
+
+ /**
+ * 打开文件到选项卡
+ *
+ * @param file 文件对象
+ */
+ public static void openFileToTab(File file) {
+ // 获取标签页集合
+ CenterTabPane centerTabPane = CenterTabPane.getInstance();
+ // 遍历标签页,查找匹配的标签页
+ for (Tab tab : centerTabPane.getTabs()) {
+ // 获取绑定的文件
+ File tabFile = (File) tab.getUserData();
+ if (tabFile == null) {
+ continue;
+ }
+ if (file.getPath().equals((tabFile).getPath())) {
+ // 找到匹配的标签页,设置为选中状态并跳转
+ centerTabPane.getSelectionModel().select(tab);
+ return;
+ }
+ }
+ getText(file);
+ }
+
+ /**
+ * 读取文本文件的内容。
+ *
+ * @param file 文件对象
+ */
+ public static void getText(File file) {
+ TextCodeArea textCodeArea = new TextCodeArea();
+ // 检测文件编码
+ Charset encoding = EncodingDetector.detectEncodingCharset(file);
+ String fileText = getFileText(file, encoding);
+ LogUtil.getLogger(OpenFile.class).info("已调用读取文件功能");
+ textCodeArea.appendText(fileText);
+ CenterTab tab = new CenterTab(file.getName(), textCodeArea, encoding);
+ // 设置当前标签页关联本地文件
+ tab.setRelevance(true);
+ // 设置标签页关联文件
+ tab.setUserData(file);
+ // 设置关联文件最后的修改时间
+ tab.setLastModifiedTimeOfAssociatedFile(file.lastModified());
+ CenterTabPaneManager.getInstance().addNewTab(tab);
+ }
+
+ public static void updateTabContextMenu(CenterTab tab) {
+ ContextMenuBuilder builder = new ContextMenuBuilder();
+ CenterTabPaneManager centerTabPaneManager = CenterTabPaneManager.getInstance();
+ File file = (File) tab.getUserData();
+ //todo 设置上下文菜单
+ tab.setContextMenu(
+ builder
+ .addMenuItem("关闭", e -> centerTabPaneManager.removeTab(tab))
+ .addMenuItem(
+ "关闭其它标签页",
+ e -> centerTabPaneManager.removeOtherTabs(tab),
+ centerTabPaneManager.hasOtherTabs()
+ )
+ .addMenuItem("关闭所有标签页", e -> centerTabPaneManager.removeAllTabs())
+ .addMenuItem(
+ "关闭左侧标签页",
+ e -> centerTabPaneManager.removeLeftTabs(tab),
+ centerTabPaneManager.hasLeftTabs(tab)
+ )
+ .addMenuItem(
+ "关闭右侧标签页",
+ e -> centerTabPaneManager.removeRightTabs(tab),
+ centerTabPaneManager.hasRightTabs(tab)
+ )
+ .addSeparatorMenuItem()
+ .addMenu(
+ new MenuBuilder("复制")
+ .addMenuItem("文件名", e -> {
+ ClipboardUtil.writeTextToClipboard(tab.getText());
+ NotificationUtil.infoNotification("已复制文件名!");
+ }, tab.isRelevance())
+ .addMenuItem("标签页名", e -> {
+ ClipboardUtil.writeTextToClipboard(tab.getText());
+ NotificationUtil.infoNotification("已复制标签页名!");
+ }, !tab.isRelevance())
+ .addMenuItem("文件路径", e -> {
+ ClipboardUtil.writeTextToClipboard(file.getAbsolutePath());
+ NotificationUtil.infoNotification("已复制文件路径!");
+ }, tab.isRelevance())
+ .addMenuItem("所在文件夹", e -> {
+ ClipboardUtil.writeTextToClipboard(file.getParent());
+ NotificationUtil.infoNotification("已复制所在文件夹!");
+ }, tab.isRelevance())
+ .build()
+ )
+ .addSeparatorMenuItem()
+ .addMenuItem("保存", e -> saveFile(tab))
+ .addMenuItem("另存为", e -> saveAsFile(tab), tab.isRelevance())
+ .addMenuItem("重命名", e -> rename(tab))
+ .addSeparatorMenuItem()
+ .addMenu(new MenuBuilder("打开于")
+ .addMenuItem("资源管理器", e -> FileUtil.openExplorer(file))
+ .addMenuItem("终端", e -> {
+ //todo @luke 请你在此设置打开文件所在文件夹路径于终端
+ })
+ .build(), tab.isRelevance())
+ .addSeparatorMenuItem()
+ .addMenuItem("固定标签页", e -> centerTabPaneManager.updateTabPinnedState(tab), !tab.isFixed())
+ .addMenuItem("取消固定", e -> centerTabPaneManager.updateTabPinnedState(tab), tab.isFixed())
+ .build());
+ }
+}
diff --git a/src/main/java/org/jcnc/jnotepad/views/manager/CenterTabPaneManager.java b/src/main/java/org/jcnc/jnotepad/views/manager/CenterTabPaneManager.java
index 09732c2..4a33f96 100644
--- a/src/main/java/org/jcnc/jnotepad/views/manager/CenterTabPaneManager.java
+++ b/src/main/java/org/jcnc/jnotepad/views/manager/CenterTabPaneManager.java
@@ -1,5 +1,6 @@
package org.jcnc.jnotepad.views.manager;
+import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList;
import javafx.scene.control.Tab;
import javafx.stage.Stage;
@@ -9,12 +10,14 @@ import org.jcnc.jnotepad.controller.config.UserConfigController;
import org.jcnc.jnotepad.model.enums.CacheExpirationTime;
import org.jcnc.jnotepad.util.FileUtil;
import org.jcnc.jnotepad.util.PopUpUtil;
+import org.jcnc.jnotepad.util.TabUtil;
import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTab;
import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTabPane;
import org.jcnc.jnotepad.views.root.top.menubar.TopMenuBar;
import java.io.File;
import java.util.ArrayList;
+import java.util.Iterator;
import java.util.List;
/**
@@ -177,10 +180,10 @@ public class CenterTabPaneManager {
/**
* Removes all tabs from the center tab pane that are not equal to the specified center tab.
*
- * @param centerTab the center tab to compare against
+ * @param currTab the current tab
*/
- public void removeOtherTabs(CenterTab centerTab) {
- centerTabPane.getTabs().removeIf(tab -> !centerTab.equals(tab));
+ public void removeOtherTabs(CenterTab currTab) {
+ centerTabPane.getTabs().removeIf(tab -> (!currTab.equals(tab) || !((CenterTab) tab).isFixed()));
}
/**
@@ -190,7 +193,16 @@ public class CenterTabPaneManager {
*/
public void removeLeftTabs(CenterTab centerTab) {
ObservableList tabs = centerTabPane.getTabs();
- tabs.remove(0, tabs.indexOf(centerTab));
+ Iterator iterator = tabs.iterator();
+ while (iterator.hasNext()) {
+ CenterTab tab = (CenterTab) iterator.next();
+ if (tab.equals(centerTab)) {
+ return;
+ }
+ if (!tab.isFixed()) {
+ iterator.remove();
+ }
+ }
}
/**
@@ -200,7 +212,18 @@ public class CenterTabPaneManager {
*/
public void removeRightTabs(CenterTab centerTab) {
ObservableList tabs = centerTabPane.getTabs();
- tabs.remove(tabs.indexOf(centerTab), tabs.size());
+ Iterator iterator = tabs.iterator();
+ boolean flag = false;
+ while (iterator.hasNext()) {
+ CenterTab tab = (CenterTab) iterator.next();
+ if (tab.equals(centerTab)) {
+ flag = true;
+ continue;
+ }
+ if (flag && !tab.isFixed()) {
+ iterator.remove();
+ }
+ }
}
/**
@@ -235,4 +258,26 @@ public class CenterTabPaneManager {
int index = tabs.indexOf(centerTab);
return index != tabs.size() - 1;
}
+
+ /**
+ * Sets a listener for the tabs in the center tab pane.
+ *
+ * @param tab the tab to set the listener for
+ */
+ public void setTabsListener(CenterTab tab) {
+ centerTabPane.getTabs().addListener((ListChangeListener) c -> tab.contextMenuMonitor());
+ }
+
+ /**
+ * Updates the pinned state of the given center tab.
+ *
+ * @param tab the center tab to update
+ */
+ public void updateTabPinnedState(CenterTab tab) {
+ tab.setFixed(!tab.isFixed());
+ TabUtil.updateTabContextMenu(tab);
+ }
+
+
+
}
diff --git a/src/main/java/org/jcnc/jnotepad/views/manager/RootManager.java b/src/main/java/org/jcnc/jnotepad/views/manager/RootManager.java
index 820e31b..fc6b5bc 100644
--- a/src/main/java/org/jcnc/jnotepad/views/manager/RootManager.java
+++ b/src/main/java/org/jcnc/jnotepad/views/manager/RootManager.java
@@ -1,8 +1,9 @@
package org.jcnc.jnotepad.views.manager;
import atlantafx.base.controls.Notification;
-import atlantafx.base.theme.Styles;
import atlantafx.base.util.Animations;
+import javafx.animation.KeyFrame;
+import javafx.animation.Timeline;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
@@ -89,8 +90,33 @@ public class RootManager {
* @param stackPane 要添加提示框的 StackPane。
* @param msg 要显示的提示框。
*/
- public void addNotificationToStackPane(StackPane stackPane, Notification msg) {
- msg.getStyleClass().addAll(Styles.ACCENT, Styles.ELEVATED_1);
+ public static void addNotificationToStackPane(StackPane stackPane, Notification msg, int closeDelaySeconds) {
+ addNotificationToStackPane(stackPane, msg);
+ automaticallyCloseNotification(stackPane, msg, closeDelaySeconds);
+ }
+
+ /**
+ * Adds a notification to the given StackPane.
+ *
+ * @param stackPane the StackPane to add the notification to
+ * @param msg the notification message
+ * @param autoClose true if the notification should automatically close, false otherwise
+ */
+ public static void addNotificationToStackPane(StackPane stackPane, Notification msg, boolean autoClose) {
+ addNotificationToStackPane(stackPane, msg);
+ if (autoClose) {
+ automaticallyCloseNotification(stackPane, msg, 3);
+ }
+
+ }
+
+ /**
+ * Adds a notification message to a StackPane.
+ *
+ * @param stackPane the StackPane to add the notification to
+ * @param msg the notification message to be added
+ */
+ private static void addNotificationToStackPane(StackPane stackPane, Notification msg) {
msg.setPrefHeight(Region.USE_PREF_SIZE);
msg.setMaxHeight(Region.USE_PREF_SIZE);
StackPane.setAlignment(msg, Pos.BOTTOM_RIGHT);
@@ -109,6 +135,26 @@ public class RootManager {
in.playFromStart();
}
+ /**
+ * Automatically closes the notification after a specified delay.
+ *
+ * @param stackPane the StackPane containing the notification
+ * @param msg the notification to be closed
+ * @param closeDelaySeconds the delay time in seconds before closing the notification
+ */
+ private static void automaticallyCloseNotification(StackPane stackPane, Notification msg, int closeDelaySeconds) {
+ // 自动关闭提示框
+ // 设置关闭延迟时间(单位:秒)
+ Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(closeDelaySeconds), event -> {
+ // 关闭提示框
+ var out = Animations.slideOutUp(msg, Duration.millis(450));
+ out.setOnFinished(f -> stackPane.getChildren().remove(msg));
+ out.playFromStart();
+ }));
+ timeline.setCycleCount(1);
+ timeline.play();
+ }
+
public StackPane getRootStackPane() {
return rootStackPane;
}
diff --git a/src/main/java/org/jcnc/jnotepad/views/root/center/main/center/directory/DirectorySidebarPane.java b/src/main/java/org/jcnc/jnotepad/views/root/center/main/center/directory/DirectorySidebarPane.java
index 9df82d8..02563bc 100644
--- a/src/main/java/org/jcnc/jnotepad/views/root/center/main/center/directory/DirectorySidebarPane.java
+++ b/src/main/java/org/jcnc/jnotepad/views/root/center/main/center/directory/DirectorySidebarPane.java
@@ -2,12 +2,13 @@ package org.jcnc.jnotepad.views.root.center.main.center.directory;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView;
-import org.jcnc.jnotepad.controller.event.handler.menuitem.OpenFile;
import org.jcnc.jnotepad.model.entity.DirFileModel;
import java.io.File;
import java.util.Objects;
+import static org.jcnc.jnotepad.util.TabUtil.openFileToTab;
+
/**
* 目录树ui
@@ -34,7 +35,7 @@ public class DirectorySidebarPane extends TreeView {
if (!file.isFile()) {
return;
}
- OpenFile.openFile(file);
+ openFileToTab(file);
}
});
}
diff --git a/src/main/java/org/jcnc/jnotepad/views/root/center/main/center/tab/CenterTab.java b/src/main/java/org/jcnc/jnotepad/views/root/center/main/center/tab/CenterTab.java
index 24a8a5f..9538fa7 100644
--- a/src/main/java/org/jcnc/jnotepad/views/root/center/main/center/tab/CenterTab.java
+++ b/src/main/java/org/jcnc/jnotepad/views/root/center/main/center/tab/CenterTab.java
@@ -6,16 +6,15 @@ import org.jcnc.jnotepad.api.core.views.menu.builder.ContextMenuBuilder;
import org.jcnc.jnotepad.api.core.views.menu.builder.MenuBuilder;
import org.jcnc.jnotepad.component.module.TextCodeArea;
import org.jcnc.jnotepad.controller.config.UserConfigController;
-import org.jcnc.jnotepad.controller.event.handler.menuitem.OpenFile;
import org.jcnc.jnotepad.controller.event.handler.menuitem.RenameFile;
import org.jcnc.jnotepad.controller.event.handler.menuitem.SaveFile;
import org.jcnc.jnotepad.util.ClipboardUtil;
import org.jcnc.jnotepad.util.LogUtil;
+import org.jcnc.jnotepad.util.TabUtil;
import org.jcnc.jnotepad.views.manager.BottomStatusBoxManager;
import org.jcnc.jnotepad.views.manager.CenterTabPaneManager;
import org.slf4j.Logger;
-import java.awt.*;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
@@ -39,6 +38,11 @@ public class CenterTab extends Tab {
* 是否与本地文件关联
*/
private boolean isRelevance = false;
+
+ /**
+ * 是否固定
+ */
+ private boolean isFixed = false;
/**
* 关联文件上次修改时间
*/
@@ -60,7 +64,8 @@ public class CenterTab extends Tab {
this.setContent(new VirtualizedScrollPane<>(textCodeArea));
setAutoLine(UserConfigController.getInstance().getAutoLineConfig());
this.charset = charset;
- contextMenuMonitor();
+ // 绑定标签页监听
+ CenterTabPaneManager.getInstance().setTabsListener(this);
}
public boolean isRelevance() {
@@ -92,60 +97,14 @@ public class CenterTab extends Tab {
this.charset = charset;
}
+ /**
+ * Monitors the context menu.
+ */
public void contextMenuMonitor() {
- ContextMenuBuilder builder = new ContextMenuBuilder();
- CenterTabPaneManager centerTabPaneManager = CenterTabPaneManager.getInstance();
- File file = (File) this.getUserData();
- //todo 设置上下文菜单
- this.setContextMenu(
- builder
- .addMenuItem("关闭", e -> centerTabPaneManager.removeTab(this))
- .addMenuItem(
- "关闭其它标签页",
- e -> centerTabPaneManager.removeOtherTabs(this),
- centerTabPaneManager.hasOtherTabs()
- )
- .addMenuItem("关闭所有标签页", e -> centerTabPaneManager.removeAllTabs())
- .addMenuItem(
- "关闭左侧标签页",
- e -> centerTabPaneManager.removeLeftTabs(this),
- centerTabPaneManager.hasLeftTabs(this)
- )
- .addMenuItem(
- "关闭右侧标签页",
- e -> centerTabPaneManager.removeRightTabs(this),
- centerTabPaneManager.hasRightTabs(this)
- )
- .addSeparatorMenuItem()
- .addMenu(
- new MenuBuilder("复制")
- .addMenuItem("文件名", e -> ClipboardUtil.writeTextToClipboard(this.getText()))
- .addMenuItem("文件路径", e -> ClipboardUtil.writeTextToClipboard(file.getAbsolutePath()))
- .addMenuItem("所在文件夹", e -> ClipboardUtil.writeTextToClipboard(file.getParent()))
- .build()
- )
- .addSeparatorMenuItem()
- .addMenuItem("保存", e -> SaveFile.saveFile())
- .addMenuItem("另存为", e -> SaveFile.saveAsFile(), isRelevance)
- .addMenuItem("重命名", e -> RenameFile.rename())
- .addSeparatorMenuItem()
- .addMenu(new MenuBuilder("打开于")
- .addMenuItem("资源管理器", e -> {
- try {
- LogUtil.getLogger(OpenFile.class).info("已调用资源管理器");
- File file1 = (File) this.getUserData();
- Desktop.getDesktop().open(file1.getParentFile());
- } catch (IOException exception) {
- logger.error("加载资源管理器失败!");
- }
- })
- .addMenuItem("终端", e -> {
-
- })
- .build())
- .build());
+ TabUtil.updateTabContextMenu(this);
}
+
/**
* 保存选中的文件标签页
*/
@@ -214,4 +173,12 @@ public class CenterTab extends Tab {
public void setLastModifiedTimeOfAssociatedFile(Long lastModifiedTimeOfAssociatedFile) {
this.lastModifiedTimeOfAssociatedFile = lastModifiedTimeOfAssociatedFile;
}
+
+ public boolean isFixed() {
+ return isFixed;
+ }
+
+ public void setFixed(boolean fixed) {
+ isFixed = fixed;
+ }
}