!155 完善标签页右键菜单

Merge pull request !155 from 格物方能致知/develop
This commit is contained in:
格物方能致知 2023-10-08 13:11:55 +00:00 committed by Gitee
commit ac015b4679
No known key found for this signature in database
GPG Key ID: 173E9B9CA92EEF8F
19 changed files with 676 additions and 451 deletions

View File

@ -6,7 +6,7 @@
<groupId>org.jcnc</groupId> <groupId>org.jcnc</groupId>
<artifactId>JNotepad</artifactId> <artifactId>JNotepad</artifactId>
<version>1.1.4-beta</version> <version>1.0.14-alpha</version>
<name>JNotepad</name> <name>JNotepad</name>
<packaging>jar</packaging> <packaging>jar</packaging>

View File

@ -48,5 +48,6 @@ module org.jcnc.jnotepad {
exports org.jcnc.jnotepad.component.stage.topmenu.help; exports org.jcnc.jnotepad.component.stage.topmenu.help;
exports org.jcnc.jnotepad.component.stage.topmenu.plugin; exports org.jcnc.jnotepad.component.stage.topmenu.plugin;
exports org.jcnc.jnotepad.component.module.vbox.components; exports org.jcnc.jnotepad.component.module.vbox.components;
exports org.jcnc.jnotepad.views.root.center.main.center.tab;
} }

View File

@ -3,9 +3,7 @@ package org.jcnc.jnotepad.api.core.views.menu.builder;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.scene.control.Menu; import javafx.scene.control.*;
import javafx.scene.control.MenuItem;
import javafx.scene.control.SeparatorMenuItem;
/** /**
* 抽象菜单建造者类 * 抽象菜单建造者类
@ -60,14 +58,14 @@ public abstract class AbstractMenuBuilder<B, T> {
} }
/** /**
* 添加单选菜单项 * 添加单选菜单项 todo 待完善
* *
* @param label 菜单项名称 * @param label 菜单项名称
* @param eventHandler 事件 * @param eventHandler 事件
* @return 建造者 * @return 建造者
*/ */
public B addRadioMenuItem(String label, EventHandler<ActionEvent> eventHandler) { public B addRadioMenuItem(String label, EventHandler<ActionEvent> eventHandler) {
MenuItem menuItem = new MenuItem(label); RadioMenuItem menuItem = new RadioMenuItem(label);
menuItem.setOnAction(eventHandler); menuItem.setOnAction(eventHandler);
getItems().add(menuItem); getItems().add(menuItem);
return getBuilder(); return getBuilder();
@ -81,7 +79,7 @@ public abstract class AbstractMenuBuilder<B, T> {
* @return 建造者 * @return 建造者
*/ */
public B addCheckMenuItem(String label, EventHandler<ActionEvent> eventHandler) { public B addCheckMenuItem(String label, EventHandler<ActionEvent> eventHandler) {
MenuItem menuItem = new MenuItem(label); CheckMenuItem menuItem = new CheckMenuItem(label);
menuItem.setOnAction(eventHandler); menuItem.setOnAction(eventHandler);
getItems().add(menuItem); getItems().add(menuItem);
return getBuilder(); return getBuilder();
@ -98,6 +96,21 @@ public abstract class AbstractMenuBuilder<B, T> {
return getBuilder(); return getBuilder();
} }
/**
* 添加菜单
*
* @param menu 菜单
* @param disable 是否禁用
* @return 建造者
*/
public B addMenu(Menu menu, boolean disable) {
if (!disable) {
return getBuilder();
}
getItems().add(menu);
return getBuilder();
}
/** /**
* 添加分割线 * 添加分割线
* *

View File

@ -13,7 +13,7 @@ public class AppConstants {
/** /**
* 版本号 * 版本号
*/ */
public static final String VERSION = "1.0.14"; public static final String VERSION = "1.0.14-alpha";
/** /**
* 作者 * 作者
*/ */

View File

@ -1,6 +1,5 @@
package org.jcnc.jnotepad.component.stage.topmenu.help; package org.jcnc.jnotepad.component.stage.topmenu.help;
import atlantafx.base.controls.Notification;
import atlantafx.base.theme.Styles; import atlantafx.base.theme.Styles;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; 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.api.core.component.stage.AbstractPaneStage;
import org.jcnc.jnotepad.util.ClipboardUtil; import org.jcnc.jnotepad.util.ClipboardUtil;
import org.jcnc.jnotepad.util.LogUtil; import org.jcnc.jnotepad.util.LogUtil;
import org.jcnc.jnotepad.util.NotificationUtil;
import org.jcnc.jnotepad.util.UiUtil; import org.jcnc.jnotepad.util.UiUtil;
import org.jcnc.jnotepad.views.manager.RootManager;
import static org.jcnc.jnotepad.common.constants.AppConstants.*; import static org.jcnc.jnotepad.common.constants.AppConstants.*;
@ -128,16 +127,7 @@ public class HelpPaneStage extends AbstractPaneStage {
bottomBox.setAlignment(Pos.BOTTOM_RIGHT); bottomBox.setAlignment(Pos.BOTTOM_RIGHT);
Button leftBtn = getButton(" 复制并关闭 ", event -> { Button leftBtn = getButton(" 复制并关闭 ", event -> {
// 获取 RootManager 的实例 NotificationUtil.infoNotification("软件信息已经复制到剪贴板!");
RootManager rootManager = RootManager.getInstance();
// 创建一个新的 Notification
Notification notification = new Notification();
notification.setMessage("已成功复制软件信息!");
// 调用 RootManager 中的方法来显示 Notification
rootManager.addNotificationToStackPane(rootManager.getRootStackPane(), notification);
String info = "软件名字:" + APP_NAME + "\t" + "版本:" + VERSION; String info = "软件名字:" + APP_NAME + "\t" + "版本:" + VERSION;
ClipboardUtil.writeTextToClipboard(info); ClipboardUtil.writeTextToClipboard(info);
LogUtil.getLogger(this.getClass()).info("软件信息已经复制到剪贴板:{}", info); LogUtil.getLogger(this.getClass()).info("软件信息已经复制到剪贴板:{}", info);

View File

@ -2,18 +2,8 @@ package org.jcnc.jnotepad.controller.event.handler.menuitem;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.scene.control.Tab;
import org.jcnc.jnotepad.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 static org.jcnc.jnotepad.util.TabUtil.addNewFileTab;
import java.util.List;
/** /**
* 新建文件事件的事件处理程序 * 新建文件事件的事件处理程序
@ -32,46 +22,4 @@ public class NewFile implements EventHandler<ActionEvent> {
public void handle(ActionEvent event) { public void handle(ActionEvent event) {
addNewFileTab(); addNewFileTab();
} }
/**
* 添加新的文件标签页
*/
public void addNewFileTab() {
// 创建一个新的文本编辑区
TextCodeArea textArea = new TextCodeArea();
// TODO: refactor统一TextArea新建绑定监听器入口
// 设定初始索引
int index = 1;
StringBuilder tabTitle = new StringBuilder();
// 获取当前默认创建标签页集合
List<Tab> 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();
}
} }

View File

@ -6,6 +6,8 @@ import org.jcnc.jnotepad.util.LogUtil;
import java.io.File; 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(); File file = UserConfigController.getInstance().getConfigPath().toFile();
LogUtil.getLogger(this.getClass()).info("已调用打开配置文件功能, {}", file); LogUtil.getLogger(this.getClass()).info("已调用打开配置文件功能, {}", file);
// 创建打开文件的任务并启动线程执行任务 // 创建打开文件的任务并启动线程执行任务
openFile(file); openFileToTab(file);
} }
} }

View File

@ -2,26 +2,18 @@ package org.jcnc.jnotepad.controller.event.handler.menuitem;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; import javafx.event.EventHandler;
import javafx.scene.control.Tab;
import javafx.stage.FileChooser; import javafx.stage.FileChooser;
import org.jcnc.jnotepad.app.i18n.UiResourceBundle; import org.jcnc.jnotepad.app.i18n.UiResourceBundle;
import org.jcnc.jnotepad.common.constants.TextConstants; import org.jcnc.jnotepad.common.constants.TextConstants;
import org.jcnc.jnotepad.common.manager.ApplicationCacheManager; 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.component.stage.dialog.factory.impl.BasicFileChooserFactory;
import org.jcnc.jnotepad.model.entity.Cache; import org.jcnc.jnotepad.model.entity.Cache;
import org.jcnc.jnotepad.model.enums.CacheExpirationTime; 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.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.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<ActionEvent> {
cache.setCacheData(file.getParent()); cache.setCacheData(file.getParent());
CACHE_MANAGER.addCache(cache); CACHE_MANAGER.addCache(cache);
} }
openFile(file); openFileToTab(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);
} }
} }

View File

@ -2,21 +2,9 @@ package org.jcnc.jnotepad.controller.event.handler.menuitem;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; 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.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 * @author gewuyou
*/ */
public class RenameFile implements EventHandler<ActionEvent> { public class RenameFile implements EventHandler<ActionEvent> {
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 @Override
public void handle(ActionEvent actionEvent) { public void handle(ActionEvent actionEvent) {
rename(); rename(CenterTabPaneManager.getInstance().getSelected());
} }
} }

View File

@ -2,6 +2,9 @@ package org.jcnc.jnotepad.controller.event.handler.menuitem;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import org.jcnc.jnotepad.util.LogUtil; 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 @Override
public void handle(ActionEvent event) { public void handle(ActionEvent event) {
LogUtil.getLogger(SaveAsFile.class).info("已调用另存为功能"); LogUtil.getLogger(SaveAsFile.class).info("已调用另存为功能");
saveAsFile(); saveAsFile(CenterTabPaneManager.getInstance().getSelected());
} }
} }

View File

@ -2,25 +2,9 @@ package org.jcnc.jnotepad.controller.event.handler.menuitem;
import javafx.event.ActionEvent; import javafx.event.ActionEvent;
import javafx.event.EventHandler; 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.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.util.TabUtil.saveFile;
import static org.jcnc.jnotepad.controller.config.UserConfigController.CONFIG_NAME;
/** /**
* 保存文件事件处理程序 * 保存文件事件处理程序
@ -31,68 +15,6 @@ import static org.jcnc.jnotepad.controller.config.UserConfigController.CONFIG_NA
* @author gewuyou * @author gewuyou
*/ */
public class SaveFile implements EventHandler<ActionEvent> { public class SaveFile implements EventHandler<ActionEvent> {
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<ActionEvent> {
*/ */
@Override @Override
public void handle(ActionEvent actionEvent) { public void handle(ActionEvent actionEvent) {
saveFile(); // 保存当前选中的标签页
saveFile(CenterTabPaneManager.getInstance().getSelected());
} }
} }

View File

@ -2,14 +2,15 @@ package org.jcnc.jnotepad.controller.manager;
import org.jcnc.jnotepad.common.manager.ApplicationCacheManager; import org.jcnc.jnotepad.common.manager.ApplicationCacheManager;
import org.jcnc.jnotepad.component.module.interfaces.ControllerAble; 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.io.File;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import static org.jcnc.jnotepad.util.TabUtil.addNewFileTab;
import static org.jcnc.jnotepad.util.TabUtil.openFileToTab;
/** /**
* 控制器类实现 ControllerAble 接口用于管理文本编辑器的各种操作和事件处理 * 控制器类实现 ControllerAble 接口用于管理文本编辑器的各种操作和事件处理
* *
@ -44,7 +45,7 @@ public class Controller implements ControllerAble<List<String>> {
// 判空 // 判空
List<String> fileTab = (List<String>) cacheData.orElse(Collections.emptyList()); List<String> fileTab = (List<String>) cacheData.orElse(Collections.emptyList());
// 打开上次打开的标签页 // 打开上次打开的标签页
fileTab.forEach(filePath -> OpenFile.openFile(new File(filePath))); fileTab.forEach(filePath -> openFileToTab(new File(filePath)));
if (!rawParameters.isEmpty()) { if (!rawParameters.isEmpty()) {
String filePath = rawParameters.get(0); String filePath = rawParameters.get(0);
@ -52,7 +53,7 @@ public class Controller implements ControllerAble<List<String>> {
return; return;
} }
if (fileTab.isEmpty()) { if (fileTab.isEmpty()) {
new NewFile().addNewFileTab(); addNewFileTab();
} }
} }
@ -65,7 +66,7 @@ public class Controller implements ControllerAble<List<String>> {
public void openAssociatedFile(String filePath) { public void openAssociatedFile(String filePath) {
File file = new File(filePath); File file = new File(filePath);
if (file.exists() && file.isFile()) { if (file.exists() && file.isFile()) {
OpenFile.openFile(file); openFileToTab(file);
} }
} }
} }

View File

@ -5,6 +5,7 @@ import org.jcnc.jnotepad.controller.exception.AppException;
import org.jcnc.jnotepad.model.entity.DirFileModel; import org.jcnc.jnotepad.model.entity.DirFileModel;
import org.kordamp.ikonli.javafx.FontIcon; import org.kordamp.ikonli.javafx.FontIcon;
import java.awt.*;
import java.io.*; import java.io.*;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.channels.FileChannel; import java.nio.channels.FileChannel;
@ -267,5 +268,24 @@ public class FileUtil {
throw new AppException(e); 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);
}
}
} }

View File

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

View File

@ -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<Tab> 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());
}
}

View File

@ -1,5 +1,6 @@
package org.jcnc.jnotepad.views.manager; package org.jcnc.jnotepad.views.manager;
import javafx.collections.ListChangeListener;
import javafx.collections.ObservableList; import javafx.collections.ObservableList;
import javafx.scene.control.Tab; import javafx.scene.control.Tab;
import javafx.stage.Stage; 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.model.enums.CacheExpirationTime;
import org.jcnc.jnotepad.util.FileUtil; import org.jcnc.jnotepad.util.FileUtil;
import org.jcnc.jnotepad.util.PopUpUtil; 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.CenterTab;
import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTabPane; import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTabPane;
import org.jcnc.jnotepad.views.root.top.menubar.TopMenuBar; import org.jcnc.jnotepad.views.root.top.menubar.TopMenuBar;
import java.io.File; import java.io.File;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Iterator;
import java.util.List; 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. * 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) { public void removeOtherTabs(CenterTab currTab) {
centerTabPane.getTabs().removeIf(tab -> !centerTab.equals(tab)); centerTabPane.getTabs().removeIf(tab -> (!currTab.equals(tab) || !((CenterTab) tab).isFixed()));
} }
/** /**
@ -190,7 +193,16 @@ public class CenterTabPaneManager {
*/ */
public void removeLeftTabs(CenterTab centerTab) { public void removeLeftTabs(CenterTab centerTab) {
ObservableList<Tab> tabs = centerTabPane.getTabs(); ObservableList<Tab> tabs = centerTabPane.getTabs();
tabs.remove(0, tabs.indexOf(centerTab)); Iterator<Tab> 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) { public void removeRightTabs(CenterTab centerTab) {
ObservableList<Tab> tabs = centerTabPane.getTabs(); ObservableList<Tab> tabs = centerTabPane.getTabs();
tabs.remove(tabs.indexOf(centerTab), tabs.size()); Iterator<Tab> 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); int index = tabs.indexOf(centerTab);
return index != tabs.size() - 1; 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<Tab>) 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);
}
} }

View File

@ -1,8 +1,9 @@
package org.jcnc.jnotepad.views.manager; package org.jcnc.jnotepad.views.manager;
import atlantafx.base.controls.Notification; import atlantafx.base.controls.Notification;
import atlantafx.base.theme.Styles;
import atlantafx.base.util.Animations; import atlantafx.base.util.Animations;
import javafx.animation.KeyFrame;
import javafx.animation.Timeline;
import javafx.geometry.Insets; import javafx.geometry.Insets;
import javafx.geometry.Pos; import javafx.geometry.Pos;
import javafx.scene.Scene; import javafx.scene.Scene;
@ -89,8 +90,33 @@ public class RootManager {
* @param stackPane 要添加提示框的 StackPane * @param stackPane 要添加提示框的 StackPane
* @param msg 要显示的提示框 * @param msg 要显示的提示框
*/ */
public void addNotificationToStackPane(StackPane stackPane, Notification msg) { public static void addNotificationToStackPane(StackPane stackPane, Notification msg, int closeDelaySeconds) {
msg.getStyleClass().addAll(Styles.ACCENT, Styles.ELEVATED_1); 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.setPrefHeight(Region.USE_PREF_SIZE);
msg.setMaxHeight(Region.USE_PREF_SIZE); msg.setMaxHeight(Region.USE_PREF_SIZE);
StackPane.setAlignment(msg, Pos.BOTTOM_RIGHT); StackPane.setAlignment(msg, Pos.BOTTOM_RIGHT);
@ -109,6 +135,26 @@ public class RootManager {
in.playFromStart(); 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() { public StackPane getRootStackPane() {
return rootStackPane; return rootStackPane;
} }

View File

@ -2,12 +2,13 @@ package org.jcnc.jnotepad.views.root.center.main.center.directory;
import javafx.scene.control.TreeItem; import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeView; import javafx.scene.control.TreeView;
import org.jcnc.jnotepad.controller.event.handler.menuitem.OpenFile;
import org.jcnc.jnotepad.model.entity.DirFileModel; import org.jcnc.jnotepad.model.entity.DirFileModel;
import java.io.File; import java.io.File;
import java.util.Objects; import java.util.Objects;
import static org.jcnc.jnotepad.util.TabUtil.openFileToTab;
/** /**
* 目录树ui * 目录树ui
@ -34,7 +35,7 @@ public class DirectorySidebarPane extends TreeView<DirFileModel> {
if (!file.isFile()) { if (!file.isFile()) {
return; return;
} }
OpenFile.openFile(file); openFileToTab(file);
} }
}); });
} }

View File

@ -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.api.core.views.menu.builder.MenuBuilder;
import org.jcnc.jnotepad.component.module.TextCodeArea; import org.jcnc.jnotepad.component.module.TextCodeArea;
import org.jcnc.jnotepad.controller.config.UserConfigController; 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.RenameFile;
import org.jcnc.jnotepad.controller.event.handler.menuitem.SaveFile; import org.jcnc.jnotepad.controller.event.handler.menuitem.SaveFile;
import org.jcnc.jnotepad.util.ClipboardUtil; import org.jcnc.jnotepad.util.ClipboardUtil;
import org.jcnc.jnotepad.util.LogUtil; 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.BottomStatusBoxManager;
import org.jcnc.jnotepad.views.manager.CenterTabPaneManager; import org.jcnc.jnotepad.views.manager.CenterTabPaneManager;
import org.slf4j.Logger; import org.slf4j.Logger;
import java.awt.*;
import java.io.BufferedWriter; import java.io.BufferedWriter;
import java.io.File; import java.io.File;
import java.io.FileWriter; import java.io.FileWriter;
@ -39,6 +38,11 @@ public class CenterTab extends Tab {
* 是否与本地文件关联 * 是否与本地文件关联
*/ */
private boolean isRelevance = false; private boolean isRelevance = false;
/**
* 是否固定
*/
private boolean isFixed = false;
/** /**
* 关联文件上次修改时间 * 关联文件上次修改时间
*/ */
@ -60,7 +64,8 @@ public class CenterTab extends Tab {
this.setContent(new VirtualizedScrollPane<>(textCodeArea)); this.setContent(new VirtualizedScrollPane<>(textCodeArea));
setAutoLine(UserConfigController.getInstance().getAutoLineConfig()); setAutoLine(UserConfigController.getInstance().getAutoLineConfig());
this.charset = charset; this.charset = charset;
contextMenuMonitor(); // 绑定标签页监听
CenterTabPaneManager.getInstance().setTabsListener(this);
} }
public boolean isRelevance() { public boolean isRelevance() {
@ -92,60 +97,14 @@ public class CenterTab extends Tab {
this.charset = charset; this.charset = charset;
} }
/**
* Monitors the context menu.
*/
public void contextMenuMonitor() { public void contextMenuMonitor() {
ContextMenuBuilder builder = new ContextMenuBuilder(); TabUtil.updateTabContextMenu(this);
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());
} }
/** /**
* 保存选中的文件标签页 * 保存选中的文件标签页
*/ */
@ -214,4 +173,12 @@ public class CenterTab extends Tab {
public void setLastModifiedTimeOfAssociatedFile(Long lastModifiedTimeOfAssociatedFile) { public void setLastModifiedTimeOfAssociatedFile(Long lastModifiedTimeOfAssociatedFile) {
this.lastModifiedTimeOfAssociatedFile = lastModifiedTimeOfAssociatedFile; this.lastModifiedTimeOfAssociatedFile = lastModifiedTimeOfAssociatedFile;
} }
public boolean isFixed() {
return isFixed;
}
public void setFixed(boolean fixed) {
isFixed = fixed;
}
} }