diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index ca143d2..232b63d 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -30,10 +30,12 @@ module org.jcnc.jnotepad { exports org.jcnc.jnotepad.util; exports org.jcnc.jnotepad.common.interfaces; opens org.jcnc.jnotepad.app.config; - exports org.jcnc.jnotepad.views.root.center.main.bottom.status; exports org.jcnc.jnotepad.plugin.interfaces; exports org.jcnc.jnotepad.ui.dialog; exports org.jcnc.jnotepad.ui.dialog.interfaces; exports org.jcnc.jnotepad.model.entity; + exports org.jcnc.jnotepad.views.root.bottom; + exports org.jcnc.jnotepad.views.root.bottom.status; + exports org.jcnc.jnotepad.views.root.bottom.cmd; } \ No newline at end of file diff --git a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menubar/NewFile.java b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menubar/NewFile.java index 91214b8..548f2b9 100644 --- a/src/main/java/org/jcnc/jnotepad/controller/event/handler/menubar/NewFile.java +++ b/src/main/java/org/jcnc/jnotepad/controller/event/handler/menubar/NewFile.java @@ -7,7 +7,7 @@ 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.ui.module.LineNumberTextArea; -import org.jcnc.jnotepad.views.root.center.main.bottom.status.BottomStatusBox; +import org.jcnc.jnotepad.views.root.bottom.status.BottomStatusBox; import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTab; import org.jcnc.jnotepad.views.root.center.main.center.tab.CenterTabPane; diff --git a/src/main/java/org/jcnc/jnotepad/ui/module/LineNumberTextArea.java b/src/main/java/org/jcnc/jnotepad/ui/module/LineNumberTextArea.java index 64b570c..e13cc1d 100644 --- a/src/main/java/org/jcnc/jnotepad/ui/module/LineNumberTextArea.java +++ b/src/main/java/org/jcnc/jnotepad/ui/module/LineNumberTextArea.java @@ -4,7 +4,7 @@ import javafx.geometry.Insets; import org.fxmisc.richtext.LineNumberFactory; import org.fxmisc.richtext.StyleClassedTextArea; import org.jcnc.jnotepad.util.LogUtil; -import org.jcnc.jnotepad.views.root.center.main.bottom.status.BottomStatusBox; +import org.jcnc.jnotepad.views.root.bottom.status.BottomStatusBox; 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; diff --git a/src/main/java/org/jcnc/jnotepad/ui/module/TerminalEmulatorComponent.java b/src/main/java/org/jcnc/jnotepad/ui/module/TerminalEmulatorComponent.java new file mode 100644 index 0000000..ef8ab6a --- /dev/null +++ b/src/main/java/org/jcnc/jnotepad/ui/module/TerminalEmulatorComponent.java @@ -0,0 +1,80 @@ +package org.jcnc.jnotepad.ui.module; + +import javafx.application.Platform; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.BorderPane; +import org.fxmisc.richtext.CodeArea; +import org.fxmisc.richtext.LineNumberFactory; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * 终端仿真器组件,用于执行命令并显示输出。 + *
+ * 该组件包括一个用于输入命令的文本字段和一个显示命令输出的CodeArea。 + * 用户可以在文本字段中输入命令,按回车键执行,并在CodeArea中查看输出。 + * + * @author luke + */ +public class TerminalEmulatorComponent extends BorderPane { + + private CodeArea terminalOutput; + + /** + * 创建一个新的终端仿真器组件。 + */ + public TerminalEmulatorComponent() { + init(); + } + + private void init() { + terminalOutput = new CodeArea(); + TextField commandInput = new TextField(); + + // 设置行号 + terminalOutput.setParagraphGraphicFactory(LineNumberFactory.get(terminalOutput)); + + commandInput.setOnKeyPressed(event -> { + if (event.getCode() == KeyCode.ENTER) { + String command = commandInput.getText(); + executeCommand(command); + commandInput.clear(); + } + }); + + setCenter(terminalOutput); + setBottom(commandInput); + } + + private void executeCommand(String command) { + try { + ProcessBuilder processBuilder = new ProcessBuilder(command.split(" ")); + processBuilder.redirectErrorStream(true); + Process process = processBuilder.start(); + + BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "GBK")); + String line; + while ((line = reader.readLine()) != null) { + appendToTerminalOutput(line + "\n"); + } + + int exitCode = process.waitFor(); + appendToTerminalOutput("Exit Code: " + exitCode + "\n"); + + } catch (IOException | InterruptedException e) { + appendToTerminalOutput("Error: " + e.getMessage() + "\n"); + } + } + + private void appendToTerminalOutput(String text) { + Platform.runLater(() -> { + terminalOutput.appendText(text); + // 将滚动条滚动到最后一行 + terminalOutput.moveTo(terminalOutput.getLength()); + terminalOutput.requestFollowCaret(); + }); + } +} diff --git a/src/main/java/org/jcnc/jnotepad/views/root/bottom/RootBottomSideBarVerticalBox.java b/src/main/java/org/jcnc/jnotepad/views/root/bottom/RootBottomSideBarVerticalBox.java index 0e649d6..cbb2f53 100644 --- a/src/main/java/org/jcnc/jnotepad/views/root/bottom/RootBottomSideBarVerticalBox.java +++ b/src/main/java/org/jcnc/jnotepad/views/root/bottom/RootBottomSideBarVerticalBox.java @@ -1,7 +1,9 @@ package org.jcnc.jnotepad.views.root.bottom; +import javafx.scene.layout.VBox; import org.jcnc.jnotepad.ui.module.AbstractVerticalBox; -import org.jcnc.jnotepad.views.root.center.main.bottom.status.BottomStatusBox; +import org.jcnc.jnotepad.views.root.bottom.cmd.CmdStatusBox; +import org.jcnc.jnotepad.views.root.bottom.status.BottomStatusBox; /** * 底部根侧边栏垂直布局 @@ -12,11 +14,7 @@ import org.jcnc.jnotepad.views.root.center.main.bottom.status.BottomStatusBox; */ public class RootBottomSideBarVerticalBox extends AbstractVerticalBox { - private static final RootBottomSideBarVerticalBox INSTANCE = new RootBottomSideBarVerticalBox(); - - private RootBottomSideBarVerticalBox() { - initSidebarVerticalBox(); - } + VBox bottomSideBarVerticalBox; /** * 获取 RootBottomSideBarVerticalBox 的唯一实例。 @@ -27,7 +25,20 @@ public class RootBottomSideBarVerticalBox extends AbstractVerticalBox { return INSTANCE; } - private void initSidebarVerticalBox() { - getChildren().addAll(BottomStatusBox.getInstance()); + public void initSidebarVerticalBox() { + bottomSideBarVerticalBox = new VBox(); + + bottomSideBarVerticalBox.getChildren().addAll(CmdStatusBox.getInstance(), BottomStatusBox.getInstance()); + + + getChildren().addAll(bottomSideBarVerticalBox); + } + + private static final RootBottomSideBarVerticalBox INSTANCE = new RootBottomSideBarVerticalBox(); + + public RootBottomSideBarVerticalBox() { + initSidebarVerticalBox(); + } + } diff --git a/src/main/java/org/jcnc/jnotepad/views/root/bottom/cmd/CmdStatusBox.java b/src/main/java/org/jcnc/jnotepad/views/root/bottom/cmd/CmdStatusBox.java new file mode 100644 index 0000000..74842f6 --- /dev/null +++ b/src/main/java/org/jcnc/jnotepad/views/root/bottom/cmd/CmdStatusBox.java @@ -0,0 +1,118 @@ +package org.jcnc.jnotepad.views.root.bottom.cmd; + +import javafx.geometry.Insets; +import javafx.scene.Scene; +import javafx.scene.control.Label; +import javafx.scene.control.Menu; +import javafx.scene.control.MenuBar; +import javafx.scene.layout.BorderPane; +import javafx.scene.layout.HBox; +import javafx.scene.layout.Priority; +import javafx.stage.Stage; +import org.jcnc.jnotepad.ui.module.TerminalEmulatorComponent; +import org.jcnc.jnotepad.util.UiUtil; + +/** + * CmdStatusBox 类表示应用程序的命令状态框。 + *
+ * 该框包括一个菜单栏,用于运行、终端和构建命令。用户可以点击终端菜单项以切换终端的显示状态。 + * 终端显示时,将创建一个新的窗口以显示终端模拟器组件。 + * + * @author luke + */ +public class CmdStatusBox extends HBox { + + Stage terminalStage = new Stage(); + + private static final CmdStatusBox CMD_STATUS_BOX = new CmdStatusBox(); + + /** + * 用于跟踪终端的显示状态 + */ + private boolean terminalVisible = false; + + private CmdStatusBox() { + initStatusBox(); + } + + /** + * 获取 CmdStatusBox 的实例。 + * + * @return CmdStatusBox 的实例 + */ + public static CmdStatusBox getInstance() { + return CMD_STATUS_BOX; + } + + /** + * 初始化命令状态框。 + */ + public void initStatusBox() { + var menuBar = new MenuBar(); + HBox.setHgrow(menuBar, Priority.ALWAYS); + menuBar.setPadding(new Insets(-3, 0, -3, 35)); + + var runMenu = new Menu(); + var cmdMenu = new Menu(); + var buildMenu = new Menu(); + + var runLabel = new Label("运行"); + var cmdLabel = new Label("终端"); + var buildLabel = new Label("构建"); + + runMenu.setGraphic(runLabel); + cmdMenu.setGraphic(cmdLabel); + buildMenu.setGraphic(buildLabel); + + cmdLabel.setOnMouseClicked(mouseEvent -> { + toggleTerminal(); // 切换终端的显示/隐藏状态 + }); + + menuBar.getMenus().addAll(runMenu, cmdMenu, buildMenu); + this.getChildren().add(menuBar); + } + + /** + * 切换终端的显示/隐藏状态。 + */ + private void toggleTerminal() { + if (terminalVisible) { + // 隐藏终端 + terminalVisible = false; + hideTerminal(); + } else { + // 显示终端 + terminalVisible = true; + showTerminal(); + } + } + + /** + * 隐藏终端窗口。 + */ + private void hideTerminal() { + terminalStage.close(); + } + + /** + * 显示终端窗口。 + */ + private void showTerminal() { + // 创建一个新的舞台(窗口) + terminalStage.setTitle("终端"); + terminalStage.getIcons().add(UiUtil.getAppIcon()); + + // 创建一个根节点(布局) + BorderPane root = new BorderPane(); + Scene scene = new Scene(root, UiUtil.getAppWindow().getWidth() - 50, UiUtil.getAppWindow().getHeight() / 3); + + // 创建TerminalEmulatorComponent并添加到根节点 + TerminalEmulatorComponent terminal = new TerminalEmulatorComponent(); + + root.setCenter(terminal); + terminalStage.setScene(scene); + + // 显示窗口 + terminalStage.show(); + } +} diff --git a/src/main/java/org/jcnc/jnotepad/views/root/center/main/bottom/status/BottomStatusBox.java b/src/main/java/org/jcnc/jnotepad/views/root/bottom/status/BottomStatusBox.java similarity index 98% rename from src/main/java/org/jcnc/jnotepad/views/root/center/main/bottom/status/BottomStatusBox.java rename to src/main/java/org/jcnc/jnotepad/views/root/bottom/status/BottomStatusBox.java index 4cf2baa..8675bb3 100644 --- a/src/main/java/org/jcnc/jnotepad/views/root/center/main/bottom/status/BottomStatusBox.java +++ b/src/main/java/org/jcnc/jnotepad/views/root/bottom/status/BottomStatusBox.java @@ -1,4 +1,4 @@ -package org.jcnc.jnotepad.views.root.center.main.bottom.status; +package org.jcnc.jnotepad.views.root.bottom.status; import javafx.beans.value.ChangeListener; import javafx.beans.value.ObservableValue; @@ -131,8 +131,9 @@ public class BottomStatusBox extends AbstractHorizontalBox { /** * 更新行列信息 + * * @param caretPosition 光标位置 - * @param text 文本内容 + * @param text 文本内容 */ private void updateRowColumnLabel(int caretPosition, String text) { int row = getRow(caretPosition, text); diff --git a/src/main/java/org/jcnc/jnotepad/views/root/center/main/center/tab/CenterTabPane.java b/src/main/java/org/jcnc/jnotepad/views/root/center/main/center/tab/CenterTabPane.java index d934939..28cfd1b 100644 --- a/src/main/java/org/jcnc/jnotepad/views/root/center/main/center/tab/CenterTabPane.java +++ b/src/main/java/org/jcnc/jnotepad/views/root/center/main/center/tab/CenterTabPane.java @@ -2,7 +2,7 @@ package org.jcnc.jnotepad.views.root.center.main.center.tab; import javafx.scene.control.TabPane; import org.jcnc.jnotepad.controller.config.AppConfigController; -import org.jcnc.jnotepad.views.root.center.main.bottom.status.BottomStatusBox; +import org.jcnc.jnotepad.views.root.bottom.status.BottomStatusBox; import org.jcnc.jnotepad.views.root.top.menu.TopMenuBar; /** diff --git a/src/main/java/org/jcnc/jnotepad/views/root/top/menu/TopMenuBar.java b/src/main/java/org/jcnc/jnotepad/views/root/top/menu/TopMenuBar.java index b581e4d..a826e91 100644 --- a/src/main/java/org/jcnc/jnotepad/views/root/top/menu/TopMenuBar.java +++ b/src/main/java/org/jcnc/jnotepad/views/root/top/menu/TopMenuBar.java @@ -1,6 +1,7 @@ package org.jcnc.jnotepad.views.root.top.menu; import javafx.event.ActionEvent; +import javafx.geometry.Insets; import javafx.scene.control.*; import javafx.scene.input.KeyCombination; import javafx.stage.Stage; @@ -141,6 +142,7 @@ public class TopMenuBar extends MenuBar { * 初始化菜单栏 */ public void initMenuBar() { + setPadding(new Insets(-3,0,-3,0)); initFileMenu(); initLanguageMenu(); initSettingMenu();