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 @@
-
+
-
+