From f7e77cdc5d24ac0cb7d5047152c9d5ec0d80cecf Mon Sep 17 00:00:00 2001 From: Gary Gan Date: Mon, 24 Nov 2025 16:25:25 +0800 Subject: [PATCH] =?UTF-8?q?feature:=E5=AE=9E=E7=8E=B0=E4=BA=86=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E7=9A=84=E4=BF=9D=E5=AD=98=E5=92=8C=E8=AF=BB=E5=8F=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 7 + .../dev/bytevibe/hyperpoint/Controller.java | 85 +++++- .../bytevibe/hyperpoint/DrawableObject.java | 4 + .../hyperpoint/JsonSerializationUtil.java | 248 ++++++++++++++++++ src/main/java/module-info.java | 1 + .../dev/bytevibe/hyperpoint/main.fxml | 4 +- 6 files changed, 346 insertions(+), 3 deletions(-) create mode 100644 src/main/java/dev/bytevibe/hyperpoint/JsonSerializationUtil.java diff --git a/pom.xml b/pom.xml index 2fbbc80..ee638a4 100644 --- a/pom.xml +++ b/pom.xml @@ -27,6 +27,13 @@ ${javafx.version} + + + com.google.code.gson + gson + 2.10.1 + + org.junit.jupiter junit-jupiter-api diff --git a/src/main/java/dev/bytevibe/hyperpoint/Controller.java b/src/main/java/dev/bytevibe/hyperpoint/Controller.java index 4e8233b..21711d0 100644 --- a/src/main/java/dev/bytevibe/hyperpoint/Controller.java +++ b/src/main/java/dev/bytevibe/hyperpoint/Controller.java @@ -46,6 +46,10 @@ public class Controller implements Initializable { private VBox propertyPanelContainer; @FXML private AnchorPane scenePane; + @FXML + private Button openSlideButton; // 添加打开幻灯片按钮 + @FXML + private Button saveSlideButton; // 添加保存幻灯片按钮 private Slide currentSlide; private DrawingCanvas drawingCanvas; @@ -346,6 +350,86 @@ public class Controller implements Initializable { } } + /** + * 打开幻灯片文件 + */ + @FXML + public void onOpenSlideFile(ActionEvent actionEvent) { + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("打开幻灯片"); + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("幻灯片文件 (*.hyperpoint)", "*.hyperpoint"), + new FileChooser.ExtensionFilter("所有文件", "*.*") + ); + + stage = (Stage) openSlideButton.getScene().getWindow(); + File file = fileChooser.showOpenDialog(stage); + + if (file != null) { + try { + Presentation presentation = JsonSerializationUtil.loadFromJson(file); + currentSlide = presentation.getSlides().isEmpty() ? null : presentation.getSlides().get(0); + + if (currentSlide != null) { + loadSlidePages(); + stage.setTitle("Hyperpoint - " + presentation.getName()); + + MyAlert alert = new MyAlert(Alert.AlertType.INFORMATION); + alert.setTitle("成功"); + alert.setHeaderText("幻灯片已打开"); + alert.setContentText("幻灯片 \"" + presentation.getName() + "\" 已成功加载,包含 " + currentSlide.getPages().size() + " 个页面。"); + alert.showAndWait(); + } else { + showWarning("加载失败", "幻灯片中没有找到任何页面。"); + } + } catch (Exception e) { + showWarning("错误", "无法加载幻灯片文件: " + e.getMessage()); + e.printStackTrace(); + } + } + } + + /** + * 保存幻灯片文件 + */ + @FXML + public void onSaveSlide(ActionEvent actionEvent) { + if (currentSlide == null) { + showWarning("无幻灯片", "请先创建或打开一个幻灯片。"); + return; + } + + FileChooser fileChooser = new FileChooser(); + fileChooser.setTitle("保存幻灯片"); + fileChooser.getExtensionFilters().addAll( + new FileChooser.ExtensionFilter("幻灯片文件", "*.hyperpoint"), + new FileChooser.ExtensionFilter("所有文件", "*.*") + ); + fileChooser.setInitialFileName(currentSlide.getName() + ".hyperpoint"); + + stage = (Stage) saveSlideButton.getScene().getWindow(); + File file = fileChooser.showSaveDialog(stage); + + if (file != null) { + try { + // 创建临时演示文稿对象用于保存 + Presentation tempPresentation = new Presentation(currentSlide.getName()); + tempPresentation.addSlide(currentSlide); + + JsonSerializationUtil.saveToJson(tempPresentation, file); + + MyAlert alert = new MyAlert(Alert.AlertType.INFORMATION); + alert.setTitle("成功"); + alert.setHeaderText("幻灯片已保存"); + alert.setContentText("幻灯片已保存到: " + file.getAbsolutePath()); + alert.showAndWait(); + } catch (Exception e) { + showWarning("错误", "无法保存幻灯片文件: " + e.getMessage()); + e.printStackTrace(); + } + } + } + /** * 显示警告对话框 */ @@ -357,4 +441,3 @@ public class Controller implements Initializable { alert.showAndWait(); } } - diff --git a/src/main/java/dev/bytevibe/hyperpoint/DrawableObject.java b/src/main/java/dev/bytevibe/hyperpoint/DrawableObject.java index ea79310..dc97350 100644 --- a/src/main/java/dev/bytevibe/hyperpoint/DrawableObject.java +++ b/src/main/java/dev/bytevibe/hyperpoint/DrawableObject.java @@ -58,6 +58,10 @@ public abstract class DrawableObject implements Serializable { return id; } + public void setId(String id) { + this.id = id; + } + public boolean isSelected() { return selected; } diff --git a/src/main/java/dev/bytevibe/hyperpoint/JsonSerializationUtil.java b/src/main/java/dev/bytevibe/hyperpoint/JsonSerializationUtil.java new file mode 100644 index 0000000..2b572cc --- /dev/null +++ b/src/main/java/dev/bytevibe/hyperpoint/JsonSerializationUtil.java @@ -0,0 +1,248 @@ +package dev.bytevibe.hyperpoint; + +import com.google.gson.*; +import javafx.scene.paint.Color; + +import java.io.*; +import java.util.*; + +/** + * JSON序列化工具,用于将演示文稿保存和加载为JSON格式 + */ +public class JsonSerializationUtil { + private static final Gson gson = new GsonBuilder() + .setPrettyPrinting() + .create(); + + /** + * 将演示文稿保存为JSON文件 + */ + public static void saveToJson(Presentation presentation, File file) throws IOException { + JsonObject root = new JsonObject(); + + // 保存演示文稿基本信息 + root.addProperty("name", presentation.getName()); + + // 保存所有幻灯片 + JsonArray slidesArray = new JsonArray(); + for (Slide slide : presentation.getSlides()) { + slidesArray.add(serializeSlide(slide)); + } + root.add("slides", slidesArray); + + // 写入文件 + try (FileWriter writer = new FileWriter(file)) { + gson.toJson(root, writer); + } + } + + /** + * 从JSON文件加载演示文稿 + */ + public static Presentation loadFromJson(File file) throws IOException { + // 读取文件 + String jsonContent; + try (FileReader reader = new FileReader(file)) { + jsonContent = new String(new byte[]{}); + StringBuilder sb = new StringBuilder(); + int c; + while ((c = reader.read()) != -1) { + sb.append((char) c); + } + jsonContent = sb.toString(); + } + + // 解析JSON + JsonObject root = JsonParser.parseString(jsonContent).getAsJsonObject(); + String name = root.get("name").getAsString(); + Presentation presentation = new Presentation(name); + + // 加载所有幻灯片 + JsonArray slidesArray = root.getAsJsonArray("slides"); + for (JsonElement slideElement : slidesArray) { + presentation.addSlide(deserializeSlide(slideElement.getAsJsonObject())); + } + + return presentation; + } + + /** + * 序列化幻灯片 + */ + private static JsonObject serializeSlide(Slide slide) { + JsonObject slideObj = new JsonObject(); + slideObj.addProperty("id", slide.getId()); + slideObj.addProperty("name", slide.getName()); + + // 序列化所有页面 + JsonArray pagesArray = new JsonArray(); + for (SlidePage page : slide.getPages()) { + pagesArray.add(serializePage(page)); + } + slideObj.add("pages", pagesArray); + + return slideObj; + } + + /** + * 反序列化幻灯片 + */ + private static Slide deserializeSlide(JsonObject slideObj) { + String name = slideObj.get("name").getAsString(); + Slide slide = new Slide(name); + + // 清除默认页面 + slide.getPages().clear(); + + // 加载所有页面 + JsonArray pagesArray = slideObj.getAsJsonArray("pages"); + for (JsonElement pageElement : pagesArray) { + slide.getPages().add(deserializePage(pageElement.getAsJsonObject())); + } + + return slide; + } + + /** + * 序列化页面 + */ + private static JsonObject serializePage(SlidePage page) { + JsonObject pageObj = new JsonObject(); + pageObj.addProperty("id", page.getId()); + pageObj.addProperty("title", page.getTitle()); + pageObj.addProperty("content", page.getContent()); + + // 序列化页面中的所有可绘制对象 + JsonArray objectsArray = new JsonArray(); + for (DrawableObject obj : page.getPageContent().getDrawableObjects()) { + objectsArray.add(serializeDrawableObject(obj)); + } + pageObj.add("objects", objectsArray); + + return pageObj; + } + + /** + * 反序列化页面 + */ + private static SlidePage deserializePage(JsonObject pageObj) { + String id = pageObj.get("id").getAsString(); + String title = pageObj.get("title").getAsString(); + String content = pageObj.has("content") ? pageObj.get("content").getAsString() : ""; + + SlidePage page = new SlidePage(title); + page.setId(id); + page.setContent(content); + + // 加载所有可绘制对象 + if (pageObj.has("objects")) { + JsonArray objectsArray = pageObj.getAsJsonArray("objects"); + for (JsonElement objElement : objectsArray) { + DrawableObject obj = deserializeDrawableObject(objElement.getAsJsonObject()); + if (obj != null) { + page.getPageContent().addObject(obj); + } + } + } + + return page; + } + + /** + * 序列化可绘制对象 + */ + private static JsonObject serializeDrawableObject(DrawableObject obj) { + JsonObject objJson = new JsonObject(); + + objJson.addProperty("id", obj.getId()); + objJson.addProperty("x", obj.getX()); + objJson.addProperty("y", obj.getY()); + objJson.addProperty("width", obj.getWidth()); + objJson.addProperty("height", obj.getHeight()); + objJson.addProperty("type", obj.getTypeName()); + + if (obj instanceof TextObject) { + TextObject textObj = (TextObject) obj; + objJson.addProperty("text", textObj.getText()); + objJson.addProperty("fontFamily", textObj.getFontFamily()); + objJson.addProperty("fontSize", textObj.getFontSize()); + objJson.addProperty("fontStyle", textObj.getFontStyle()); + objJson.addProperty("textColor", textObj.getTextColor()); + + } else if (obj instanceof ShapeObject) { + ShapeObject shapeObj = (ShapeObject) obj; + objJson.addProperty("shapeType", shapeObj.getShapeType().name()); + objJson.addProperty("fillColor", shapeObj.getFillColor()); + objJson.addProperty("strokeColor", shapeObj.getStrokeColor()); + objJson.addProperty("strokeWidth", shapeObj.getStrokeWidth()); + + } else if (obj instanceof ImageObject) { + ImageObject imgObj = (ImageObject) obj; + objJson.addProperty("imagePath", imgObj.getImagePath()); + } + + return objJson; + } + + /** + * 反序列化可绘制对象 + */ + private static DrawableObject deserializeDrawableObject(JsonObject objJson) { + String type = objJson.get("type").getAsString(); + double x = objJson.get("x").getAsDouble(); + double y = objJson.get("y").getAsDouble(); + double width = objJson.get("width").getAsDouble(); + double height = objJson.get("height").getAsDouble(); + String id = objJson.get("id").getAsString(); + + DrawableObject obj = null; + + if (type.equals("Text")) { + String text = objJson.get("text").getAsString(); + obj = new TextObject(x, y, text); + obj.setWidth(width); + obj.setHeight(height); + + TextObject textObj = (TextObject) obj; + if (objJson.has("fontFamily")) { + textObj.setFontFamily(objJson.get("fontFamily").getAsString()); + } + if (objJson.has("fontSize")) { + textObj.setFontSize(objJson.get("fontSize").getAsDouble()); + } + if (objJson.has("fontStyle")) { + textObj.setFontStyle(objJson.get("fontStyle").getAsString()); + } + if (objJson.has("textColor")) { + textObj.setTextColor(objJson.get("textColor").getAsString()); + } + + } else if (type.startsWith("Shape_")) { + String shapeTypeStr = type.replace("Shape_", ""); + ShapeObject.ShapeType shapeType = ShapeObject.ShapeType.valueOf(shapeTypeStr); + obj = new ShapeObject(x, y, width, height, shapeType); + + ShapeObject shapeObj = (ShapeObject) obj; + if (objJson.has("fillColor")) { + shapeObj.setFillColor(objJson.get("fillColor").getAsString()); + } + if (objJson.has("strokeColor")) { + shapeObj.setStrokeColor(objJson.get("strokeColor").getAsString()); + } + if (objJson.has("strokeWidth")) { + shapeObj.setStrokeWidth(objJson.get("strokeWidth").getAsDouble()); + } + + } else if (type.equals("Image")) { + String imagePath = objJson.get("imagePath").getAsString(); + obj = new ImageObject(x, y, width, height, imagePath); + } + + if (obj != null) { + obj.setId(id); + } + + return obj; + } +} + diff --git a/src/main/java/module-info.java b/src/main/java/module-info.java index 6ed302a..fd967a3 100644 --- a/src/main/java/module-info.java +++ b/src/main/java/module-info.java @@ -2,6 +2,7 @@ module dev.bytevibe.hyperpoint { requires javafx.controls; requires javafx.fxml; requires java.desktop; + requires com.google.gson; opens dev.bytevibe.hyperpoint to javafx.fxml; diff --git a/src/main/resources/dev/bytevibe/hyperpoint/main.fxml b/src/main/resources/dev/bytevibe/hyperpoint/main.fxml index 88913df..5b1c557 100644 --- a/src/main/resources/dev/bytevibe/hyperpoint/main.fxml +++ b/src/main/resources/dev/bytevibe/hyperpoint/main.fxml @@ -15,10 +15,10 @@