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 519fe54..9bab10d 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 @@ -76,6 +76,23 @@ public abstract class AbstractMenuBuilder { return getBuilder(); } + /** + * 添加菜单项 + * + * @param label 菜单项名称 + * @param eventHandler 事件 + * @param visible 是否可见 + * @return 建造者 + */ + public B addMenuItem(String label, EventHandler eventHandler, boolean visible) { + MenuItem menuItem = new MenuItem(label); + menuItem.setOnAction(eventHandler); + menuItem.setVisible(visible); + menuItems.put(label, menuItem); + getItems().add(menuItem); + return getBuilder(); + } + /** * 添加单选菜单项 * @@ -141,6 +158,20 @@ public abstract class AbstractMenuBuilder { return getBuilder(); } + /** + * 添加菜单 + * + * @param menu 菜单 + * @param visible 是否隐藏 + * @return 建造者 + */ + public B addMenu(Menu menu, boolean visible) { + menu.setVisible(visible); + menuItems.put(menu.getText(), menu); + getItems().add(menu); + return getBuilder(); + } + /** * 添加分割线 @@ -166,6 +197,19 @@ public abstract class AbstractMenuBuilder { return getBuilder(); } + /** + * 添加分割线 + * + * @param visible 是否可见 + * @return 建造者 + */ + public B addSeparatorMenuItem(boolean visible) { + SeparatorMenuItem separatorMenuItem = new SeparatorMenuItem(); + separatorMenuItem.setVisible(visible); + getItems().add(separatorMenuItem); + return getBuilder(); + } + /** * Build menu * diff --git a/src/main/java/org/jcnc/jnotepad/app/common/constants/TextConstants.java b/src/main/java/org/jcnc/jnotepad/app/common/constants/TextConstants.java index a389507..5153b5a 100644 --- a/src/main/java/org/jcnc/jnotepad/app/common/constants/TextConstants.java +++ b/src/main/java/org/jcnc/jnotepad/app/common/constants/TextConstants.java @@ -23,6 +23,10 @@ public class TextConstants { * 文件文本常量 */ public static final String FILE = "FILE"; + /** + * 文件夹 + */ + public static final String FOLDER = "FOLDER"; /** * 构建文本常量 @@ -139,6 +143,16 @@ public class TextConstants { */ public static final String NEW_FILE = "NEW_FILE"; + /** + * 新建文件夹 + */ + public static final String NEW_DIRECTORY = "NEW_DIRECTORY"; + + /** + * 删除 + */ + public static final String DELETE = "DELETE"; + /** * 行文本常量 */ diff --git a/src/main/java/org/jcnc/jnotepad/app/utils/FileUtil.java b/src/main/java/org/jcnc/jnotepad/app/utils/FileUtil.java index d45e678..93a873c 100644 --- a/src/main/java/org/jcnc/jnotepad/app/utils/FileUtil.java +++ b/src/main/java/org/jcnc/jnotepad/app/utils/FileUtil.java @@ -37,6 +37,8 @@ public class FileUtil { private static final String MAC = "mac"; + private static final String PATH = "path"; + static { try { MESSAGE_DIGEST_SHA_256 = MessageDigest.getInstance("SHA-256"); @@ -189,9 +191,10 @@ public class FileUtil { if (Objects.isNull(dirFileModels) || dirFileModels.isEmpty()) { return null; } + File rootDir = new File((String) dirFileModels.get(PATH)); DirFileModel dirFileModel = new DirFileModel( - (String) dirFileModels.get("path"), - (String) dirFileModels.get("name"), new ArrayList<>(), + rootDir.getAbsolutePath(), + rootDir.getName(), new ArrayList<>(), new FontIcon(FOLDER), new FontIcon(FOLDER_OPEN), (Boolean) dirFileModels.get("open")); Optional o = Optional.ofNullable(dirFileModels.get("childFile")); @@ -199,16 +202,29 @@ public class FileUtil { return null; } List> childFile = (List>) o.get(); - for (Map map : childFile) { - Object obj = map.get("childFile"); - if (obj == null) { - dirFileModel.getChildFile().add(new DirFileModel( - (String) map.get("path"), (String) map.get("name"), null, - getIconCorrespondingToFileName((String) map.get("name")), - null)); - } else { - DirFileModel childDirFileModel = getDirFileModel(map); + File[] files = rootDir.listFiles(); + if (files == null) { + return null; + } + + for (File f : files) { + if (f.isDirectory()) { + Optional> first = childFile + .stream() + .filter(map -> map.get(PATH).equals(f.getAbsolutePath())).findFirst(); + DirFileModel childDirFileModel; + if (first.isPresent()) { + childDirFileModel = getDirFileModel(first.get()); + } else { + childDirFileModel = getDirFileModel(f); + } dirFileModel.getChildFile().add(childDirFileModel); + } else { + // 在此监测文件后缀,设置对应的图标 + dirFileModel.getChildFile().add(new DirFileModel( + f.getAbsolutePath(), f.getName(), null, + getIconCorrespondingToFileName(f.getName()), + null)); } } return dirFileModel; @@ -365,17 +381,18 @@ public class FileUtil { * @param folder the folder in which to open the terminal */ public static void openTerminal(File folder) { - if (folder.exists() && folder.isDirectory()) { - String os = System.getProperty("os.name").toLowerCase(); - - ProcessBuilder processBuilder = getProcessBuilder(folder, os); - try { - processBuilder.start(); - } catch (IOException e) { - PopUpUtil.errorAlert("打开失败", "打开于终端失败", "错误原因" + e.getMessage(), null, null); - } - } else { - logger.info("文件夹不存在或者不是文件夹"); + if (!folder.exists()) { + return; + } + if (folder.isFile()) { + folder = folder.getParentFile(); + } + String os = System.getProperty("os.name").toLowerCase(); + ProcessBuilder processBuilder = getProcessBuilder(folder, os); + try { + processBuilder.start(); + } catch (IOException e) { + PopUpUtil.errorAlert("打开失败", "打开于终端失败", "错误原因:" + e.getMessage(), null, null); } } @@ -400,5 +417,18 @@ public class FileUtil { } return processBuilder; } + + /** + * Creates a file at the specified path. + * + * @param path The path to the file to be created. + */ + public static void createFile(Path path) { + try { + Files.createFile(path); + } catch (IOException e) { + logger.error("创建文件失败", e); + } + } } diff --git a/src/main/java/org/jcnc/jnotepad/app/utils/TabUtil.java b/src/main/java/org/jcnc/jnotepad/app/utils/TabUtil.java index 691825a..921f44e 100644 --- a/src/main/java/org/jcnc/jnotepad/app/utils/TabUtil.java +++ b/src/main/java/org/jcnc/jnotepad/app/utils/TabUtil.java @@ -128,19 +128,18 @@ public class TabUtil { * @param tab 标签页组件 */ private static void handleRenameTab(CenterTab tab) { - TextField textField = new TextField(tab.getText()); - textField.getStyleClass().add("tab-title-editable"); // 临时记录标签页名称 String tempName = tab.getText(); + TextField textField = new TextField(tempName); + textField.getStyleClass().add("tab-title-editable"); // 清空标签页名称 tab.setText(""); - // 监听 Enter 键,完成编辑 textField.setOnKeyPressed(event -> { if (event.getCode() == KeyCode.ENTER) { String newTabName = textField.getText(); // 检查是否存在相同名称的标签页 - if (isTabNameExists(newTabName)) { + if (tabNameExists(newTabName)) { // 显示弹窗并提示用户更换名称 showDuplicateNameAlert(newTabName); @@ -162,7 +161,7 @@ public class TabUtil { textField.focusedProperty().addListener((observable, oldValue, newValue) -> { String newTabName = textField.getText(); // 检查是否存在相同名称的标签页 - if (isTabNameExists(newTabName)) { + if (tabNameExists(newTabName)) { // 恢复原始名称 tab.setText(tempName); @@ -191,7 +190,7 @@ public class TabUtil { * @param newTabName 要检查的新标签页名称 * @return 如果存在具有相同名称的标签页,则返回 true;否则返回 false */ - private static boolean isTabNameExists(String newTabName) { + private static boolean tabNameExists(String newTabName) { CenterTabPane tabPane = CenterTabPane.getInstance(); return tabPane.getTabs().stream() .anyMatch(tab -> tab.getText().equals(newTabName)); @@ -245,6 +244,22 @@ public class TabUtil { public static void addNewFileTab() { // 创建一个新的文本编辑区 TextCodeArea textArea = new TextCodeArea(); + // 创建标签页 + CenterTab centerTab = new CenterTab( + generateDefaultName(), + textArea); + // 将Tab页添加到TabPane中 + CenterTabPaneManager.getInstance().addNewTab(centerTab); + // 更新编码信息 + BottomStatusBoxManager.getInstance().updateEncodingLabel(); + } + + /** + * Generate the default name for a new tab. + * + * @return The default name for a new tab. + */ + private static String generateDefaultName() { // 设定初始索引 int index = 1; StringBuilder tabTitle = new StringBuilder(); @@ -275,14 +290,7 @@ public class TabUtil { break; } } - // 创建标签页 - CenterTab centerTab = new CenterTab( - tabTitle.toString(), - textArea); - // 将Tab页添加到TabPane中 - CenterTabPaneManager.getInstance().addNewTab(centerTab); - // 更新编码信息 - BottomStatusBoxManager.getInstance().updateEncodingLabel(); + return tabTitle.toString(); } /** diff --git a/src/main/java/org/jcnc/jnotepad/controller/event/handler/toolbar/OpenDirectory.java b/src/main/java/org/jcnc/jnotepad/controller/event/handler/toolbar/OpenDirectory.java index 209d677..86fca0c 100644 --- a/src/main/java/org/jcnc/jnotepad/controller/event/handler/toolbar/OpenDirectory.java +++ b/src/main/java/org/jcnc/jnotepad/controller/event/handler/toolbar/OpenDirectory.java @@ -51,9 +51,13 @@ public class OpenDirectory implements EventHandler { CACHE_MANAGER.addCache(cache); } flushDirSidebar(file); - } + /** + * Flushes the directory sidebar with the given file. + * + * @param file the file to be converted into an entity class + */ public void flushDirSidebar(File file) { // 将文件转为实体类 DirFileModel dirFileModel = FileUtil.getDirFileModel(file); diff --git a/src/main/java/org/jcnc/jnotepad/model/entity/DirFileModel.java b/src/main/java/org/jcnc/jnotepad/model/entity/DirFileModel.java index 67eb026..7dc9ef9 100644 --- a/src/main/java/org/jcnc/jnotepad/model/entity/DirFileModel.java +++ b/src/main/java/org/jcnc/jnotepad/model/entity/DirFileModel.java @@ -23,6 +23,7 @@ public class DirFileModel { /** * 文件名 */ + @JsonIgnore private String name; /** @@ -45,13 +46,6 @@ public class DirFileModel { */ private boolean isOpen; - public DirFileModel(String path, String name, List childFile, boolean isOpen) { - this.path = path; - this.name = name; - this.childFile = childFile; - this.isOpen = isOpen; - } - public DirFileModel(String path, String name, List childFile, Node iconIsNotSelected, Node iconIsSelected) { this.path = path; this.name = name; diff --git a/src/main/java/org/jcnc/jnotepad/ui/views/manager/DirectorySidebarManager.java b/src/main/java/org/jcnc/jnotepad/ui/views/manager/DirectorySidebarManager.java index 7b308a3..ab60134 100644 --- a/src/main/java/org/jcnc/jnotepad/ui/views/manager/DirectorySidebarManager.java +++ b/src/main/java/org/jcnc/jnotepad/ui/views/manager/DirectorySidebarManager.java @@ -103,13 +103,13 @@ public class DirectorySidebarManager { */ public void setTreeView(DirFileModel dirFileModel) { TreeItem rootItem = new TreeItem<>(dirFileModel, dirFileModel.getIconIsNotSelected()); - DIRECTORY_SIDEBAR_PANE.setRoot(rootItem); rootItem.expandedProperty().addListener(getTreeItemListener(rootItem)); rootItem.setExpanded(dirFileModel.isOpen()); expandFolder(dirFileModel, rootItem); } + /** * 递归展开 dirFileModel * diff --git a/src/main/java/org/jcnc/jnotepad/ui/views/manager/MenuManager.java b/src/main/java/org/jcnc/jnotepad/ui/views/manager/MenuManager.java new file mode 100644 index 0000000..ba85651 --- /dev/null +++ b/src/main/java/org/jcnc/jnotepad/ui/views/manager/MenuManager.java @@ -0,0 +1,74 @@ +package org.jcnc.jnotepad.ui.views.manager; + +import javafx.scene.control.Menu; +import org.jcnc.jnotepad.api.core.views.menu.builder.MenuBuilder; +import org.jcnc.jnotepad.app.utils.ClipboardUtil; +import org.jcnc.jnotepad.app.utils.FileUtil; +import org.jcnc.jnotepad.app.utils.NotificationUtil; + +import java.io.File; + +import static org.jcnc.jnotepad.app.common.constants.TextConstants.*; + +/** + * 菜单管理类 + * + * @author gewuyou + */ +public class MenuManager { + + private MenuManager() { + + } + + /** + * Generates a copy menu for the given file. + * + * @param file the file to generate the copy menu for + * @return the generated copy menu + */ + public static Menu getCopyMenu(File file) { + return new MenuBuilder(COPY) + .addMenuItem(FILE_NAME, e -> { + ClipboardUtil.writeTextToClipboard(file.getName()); + NotificationUtil.infoNotification("已复制文件名!"); + }) + .addMenuItem(FILE_PATH, e -> { + ClipboardUtil.writeTextToClipboard(file.getAbsolutePath()); + NotificationUtil.infoNotification("已复制文件路径!"); + }) + .addMenuItem(FOLDER_PATH, e -> { + ClipboardUtil.writeTextToClipboard(file.getParent()); + NotificationUtil.infoNotification("已复制所在文件夹!"); + }) + .build(); + } + + /** + * Retrieves the open menu for a given file. + * + * @param file the file for which to retrieve the open menu + * @return the open menu for the given file + */ + public static Menu getOpenMenu(File file) { + return new MenuBuilder(OPEN_ON) + .addMenuItem(EXPLORER, e -> FileUtil.openExplorer(file)) + .addMenuItem(TERMINAL, e -> FileUtil.openTerminal(file)) + .build(); + } + + /** + * Returns a new Menu. + * + * @return a new Menu object + */ + public static Menu getNewMenu() { + return new MenuBuilder(NEW) + .addMenuItem(FILE, e -> { + }) + .addMenuItem(FOLDER, e -> { + }) + .build(); + } + +} diff --git a/src/main/java/org/jcnc/jnotepad/ui/views/root/center/main/center/directory/DirectorySidebarPane.java b/src/main/java/org/jcnc/jnotepad/ui/views/root/center/main/center/directory/DirectorySidebarPane.java index f36bba4..57b9a28 100644 --- a/src/main/java/org/jcnc/jnotepad/ui/views/root/center/main/center/directory/DirectorySidebarPane.java +++ b/src/main/java/org/jcnc/jnotepad/ui/views/root/center/main/center/directory/DirectorySidebarPane.java @@ -1,12 +1,19 @@ package org.jcnc.jnotepad.ui.views.root.center.main.center.directory; +import javafx.scene.control.ContextMenu; import javafx.scene.control.TreeItem; import javafx.scene.control.TreeView; +import javafx.scene.input.MouseButton; +import javafx.scene.input.MouseEvent; +import org.jcnc.jnotepad.api.core.views.menu.builder.ContextMenuBuilder; import org.jcnc.jnotepad.model.entity.DirFileModel; +import org.jcnc.jnotepad.ui.views.manager.DirectorySidebarManager; +import org.jcnc.jnotepad.ui.views.manager.MenuManager; import java.io.File; import java.util.Objects; +import static org.jcnc.jnotepad.app.common.constants.TextConstants.OPEN; import static org.jcnc.jnotepad.app.utils.TabUtil.openFileToTab; @@ -23,23 +30,64 @@ public class DirectorySidebarPane extends TreeView { private static final int CLICK_COUNT = 2; - private DirectorySidebarPane() { - this.setOnMouseClicked(mouseEvent -> { - if (mouseEvent.getClickCount() == CLICK_COUNT) { - TreeItem item = DirectorySidebarPane.this.getSelectionModel().getSelectedItem(); - if (Objects.isNull(item)) { - return; - } - File file = new File(item.getValue().getPath()); + private final DirectorySidebarManager directorySidebarManager = DirectorySidebarManager.getInstance(); + private ContextMenu contextMenu; + + private DirectorySidebarPane() { + initMouseClickEvent(); + } + + /** + * Initializes the mouse click event for the Java function. + */ + private void initMouseClickEvent() { + this.setOnMouseClicked(mouseEvent -> { + TreeItem item = DirectorySidebarPane.this.getSelectionModel().getSelectedItem(); + if (Objects.isNull(item)) { + return; + } + if (mouseEvent.getClickCount() == CLICK_COUNT) { + File file = new File(item.getValue().getPath()); if (!file.isFile()) { return; } openFileToTab(file); } + if (!Objects.isNull(contextMenu)) { + contextMenu.hide(); + } + if (mouseEvent.getButton() == MouseButton.SECONDARY) { + updateContextMenu(mouseEvent, item); + } }); } + + private void updateContextMenu(MouseEvent mouseEvent, TreeItem item) { + createContextMenu(item); + contextMenu.show(this, mouseEvent.getScreenX(), mouseEvent.getScreenY()); + } + + private void createContextMenu(TreeItem item) { + ContextMenuBuilder builder = new ContextMenuBuilder(); + boolean isFile = !DirFileModel.isDirectoryByDirFileModel(item.getValue()); + File file = new File(item.getValue().getPath()); + contextMenu = builder + .addMenuItem(OPEN, e -> openFileToTab(file), isFile) + .addSeparatorMenuItem(isFile) + .addMenu(MenuManager.getNewMenu(), !isFile) +// .addSeparatorMenuItem() +// .addMenuItem(RENAME, e -> directorySidebarManager.rename(file)) +// .addMenuItem(DELETE, e -> directorySidebarManager.delete(file)) +// .addSeparatorMenuItem(isFile) + .addMenu(MenuManager.getCopyMenu(file), isFile) + .addSeparatorMenuItem(isFile) + .addMenu(MenuManager.getOpenMenu(file)) + .build(); + } + + public static DirectorySidebarPane getInstance() { return INSTANCE; } diff --git a/src/main/java/org/jcnc/jnotepad/ui/views/root/center/main/center/tab/CenterTab.java b/src/main/java/org/jcnc/jnotepad/ui/views/root/center/main/center/tab/CenterTab.java index 248f7ed..5f95e36 100644 --- a/src/main/java/org/jcnc/jnotepad/ui/views/root/center/main/center/tab/CenterTab.java +++ b/src/main/java/org/jcnc/jnotepad/ui/views/root/center/main/center/tab/CenterTab.java @@ -8,12 +8,14 @@ import javafx.scene.control.CheckMenuItem; 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.app.utils.*; +import org.jcnc.jnotepad.app.utils.FileUtil; +import org.jcnc.jnotepad.app.utils.LoggerUtil; +import org.jcnc.jnotepad.app.utils.TabUtil; import org.jcnc.jnotepad.controller.config.UserConfigController; import org.jcnc.jnotepad.ui.component.module.TextCodeArea; import org.jcnc.jnotepad.ui.views.manager.BottomStatusBoxManager; import org.jcnc.jnotepad.ui.views.manager.CenterTabPaneManager; +import org.jcnc.jnotepad.ui.views.manager.MenuManager; import org.slf4j.Logger; import java.io.BufferedWriter; @@ -163,20 +165,7 @@ public class CenterTab extends Tab { ) .addSeparatorMenuItem(this.relevancePropertyProperty()) .addMenu( - new MenuBuilder(COPY) - .addMenuItem(FILE_NAME, e -> { - ClipboardUtil.writeTextToClipboard(file.getName()); - NotificationUtil.infoNotification("已复制文件名!"); - }) - .addMenuItem(FILE_PATH, e -> { - ClipboardUtil.writeTextToClipboard(file.getAbsolutePath()); - NotificationUtil.infoNotification("已复制文件路径!"); - }) - .addMenuItem(FOLDER_PATH, e -> { - ClipboardUtil.writeTextToClipboard(file.getParent()); - NotificationUtil.infoNotification("已复制所在文件夹!"); - }) - .build() + MenuManager.getCopyMenu(file) , this.relevancePropertyProperty() ) .addSeparatorMenuItem() @@ -184,10 +173,7 @@ public class CenterTab extends Tab { .addMenuItem(SAVE_AS, e -> TabUtil.saveAsFile(this), this.relevancePropertyProperty()) .addMenuItem(RENAME, e -> TabUtil.rename(this)) .addSeparatorMenuItem(this.relevancePropertyProperty()) - .addMenu(new MenuBuilder(OPEN_ON) - .addMenuItem(EXPLORER, e -> FileUtil.openExplorer(file)) - .addMenuItem(TERMINAL, e -> FileUtil.openTerminal(file.getParentFile())) - .build(), this.relevancePropertyProperty()) + .addMenu(MenuManager.getOpenMenu(file), this.relevancePropertyProperty()) .addSeparatorMenuItem() .addCheckMenuItem(FIXED_TAB, e -> centerTabPaneManager.updateTabPinnedState(this)) @@ -197,6 +183,7 @@ public class CenterTab extends Tab { .build()); } + /** * 保存选中的文件标签页 */ diff --git a/src/main/resources/jcnc/app/i18n/i18n.properties b/src/main/resources/jcnc/app/i18n/i18n.properties index eeb3199..f381975 100644 --- a/src/main/resources/jcnc/app/i18n/i18n.properties +++ b/src/main/resources/jcnc/app/i18n/i18n.properties @@ -41,5 +41,5 @@ CLOSE_OTHER_TABS=关闭其他标签页 FIXED_TAB=固定标签页 READ_ONLY=只读 OPEN_ON=打开于 - +FOLDER=文件夹 diff --git a/src/main/resources/jcnc/app/i18n/i18n_en.properties b/src/main/resources/jcnc/app/i18n/i18n_en.properties index 5826385..a0a3892 100644 --- a/src/main/resources/jcnc/app/i18n/i18n_en.properties +++ b/src/main/resources/jcnc/app/i18n/i18n_en.properties @@ -41,3 +41,4 @@ CLOSE_OTHER_TABS=Close Other Tabs FIXED_TAB=Fixed Tab READ_ONLY=Read Only OPEN_ON=Open On +FOLDER=Folder \ No newline at end of file diff --git a/src/main/resources/jcnc/app/i18n/i18n_zh_CN.properties b/src/main/resources/jcnc/app/i18n/i18n_zh_CN.properties index 092a327..d57dcfb 100644 --- a/src/main/resources/jcnc/app/i18n/i18n_zh_CN.properties +++ b/src/main/resources/jcnc/app/i18n/i18n_zh_CN.properties @@ -41,3 +41,4 @@ CLOSE_OTHER_TABS=关闭其他标签页 FIXED_TAB=固定标签页 READ_ONLY=只读 OPEN_ON=打开于 +FOLDER=文件夹