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 d61e7a2..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.13"; + 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 abc28a8..17b9373 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 @@ -2,14 +2,10 @@ package org.jcnc.jnotepad.views.root.center.main.center.tab; import javafx.scene.control.Tab; import org.fxmisc.flowless.VirtualizedScrollPane; -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.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; @@ -37,6 +33,11 @@ public class CenterTab extends Tab { * 是否与本地文件关联 */ private boolean isRelevance = false; + + /** + * 是否关闭 + */ + private boolean isFixed = false; /** * 关联文件上次修改时间 */ @@ -58,7 +59,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() { @@ -90,54 +92,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 -> { - - }) - .addMenuItem("终端", e -> { - - }) - .build()) - .build()); + TabUtil.updateTabContextMenu(this); } + /** * 保存选中的文件标签页 */ @@ -206,4 +168,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; + } }