SwingGraphicsApp.java
package com.example.graphics;
import com.example.graphics.factory.ShapeFactory;
import com.example.graphics.factory.SwingRendererFactory;
import com.example.graphics.model.Line;
import com.example.graphics.model.Shape;
import com.example.graphics.observer.ConsoleLogger;
import com.example.graphics.render.Renderer;
import com.example.graphics.render.SwingRenderer;
import com.example.graphics.singleton.RenderingConfig;
import com.example.graphics.util.FileManager;
import com.example.graphics.visitor.JsonExportVisitor;
import com.example.graphics.visitor.XmlExportVisitor;
import com.example.graphics.proxy.RemoteRendererProxy;
import javax.swing.*;
import javax.swing.filechooser.FileNameExtensionFilter;
import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.List;
/**
* Swing GUI application for the graphics rendering system
*/
public class SwingGraphicsApp extends JFrame {
private final Drawing drawing;
private final SwingRenderer localRenderer;
private final RemoteRendererProxy remoteRenderer;
private final ShapeFactory shapeFactory;
private final FileManager fileManager;
private Shape selectedShape;
private String currentShapeType = "Circle";
private Point dragStart;
private boolean isDragging = false;
private boolean isDrawingLine = false;
private boolean isDrawingTriangle = false;
private int triangleStage = 0;
private int x1, y1, x2, y2; // 用于存储线条和三角形的点
private JLabel statusLabel;
private JToggleButton remoteRenderingToggle;
private boolean isRemoteRenderingEnabled = false;
public SwingGraphicsApp() {
super("图形渲染系统");
// 获取配置
RenderingConfig config = RenderingConfig.getInstance();
// 创建工厂和渲染器
SwingRendererFactory rendererFactory = new SwingRendererFactory();
localRenderer = (SwingRenderer) rendererFactory.createRenderer(800, 600);
// 创建远程渲染器代理
remoteRenderer = new RemoteRendererProxy();
remoteRenderer.setLocalRenderer(localRenderer); // 设置本地渲染器引用
// 创建绘图对象,默认使用本地渲染器
drawing = new Drawing(localRenderer);
drawing.addObserver(new ConsoleLogger());
// 创建形状工厂
shapeFactory = new ShapeFactory();
// 创建文件管理器
fileManager = new FileManager();
// 设置UI
setupUI();
// 设置窗口属性
setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
setSize(1000, 700);
setLocationRelativeTo(null);
setVisible(true);
}
private void setupUI() {
JPanel mainPanel = new JPanel(new BorderLayout());
// 创建菜单栏
JMenuBar menuBar = createMenuBar();
setJMenuBar(menuBar);
// 创建工具栏
JToolBar toolBar = createToolBar();
mainPanel.add(toolBar, BorderLayout.NORTH);
// 创建渲染面板
JPanel renderPanel = localRenderer.getRenderPanel();
renderPanel.setBorder(BorderFactory.createLoweredBevelBorder());
setupRenderPanelListeners(renderPanel);
// 创建滚动面板
JScrollPane scrollPane = new JScrollPane(renderPanel);
mainPanel.add(scrollPane, BorderLayout.CENTER);
// 创建状态栏
JPanel statusBar = new JPanel(new FlowLayout(FlowLayout.LEFT));
statusBar.setBorder(BorderFactory.createLoweredBevelBorder());
statusLabel = new JLabel("就绪");
statusBar.add(statusLabel);
mainPanel.add(statusBar, BorderLayout.SOUTH);
setContentPane(mainPanel);
// 添加一些初始形状作为示例
addSampleShapes();
}
private JMenuBar createMenuBar() {
JMenuBar menuBar = new JMenuBar();
// 文件菜单
JMenu fileMenu = new JMenu("文件");
JMenuItem newItem = new JMenuItem("新建");
newItem.addActionListener(e -> newDrawing());
fileMenu.add(newItem);
JMenuItem openItem = new JMenuItem("打开...");
openItem.addActionListener(e -> openDrawing());
fileMenu.add(openItem);
fileMenu.addSeparator();
JMenuItem saveItem = new JMenuItem("保存...");
saveItem.addActionListener(e -> saveDrawing());
fileMenu.add(saveItem);
JMenuItem saveJsonItem = new JMenuItem("导出为JSON...");
saveJsonItem.addActionListener(e -> exportToJson());
fileMenu.add(saveJsonItem);
JMenuItem saveXmlItem = new JMenuItem("导出为XML...");
saveXmlItem.addActionListener(e -> exportToXml());
fileMenu.add(saveXmlItem);
fileMenu.addSeparator();
JMenuItem exitItem = new JMenuItem("退出");
exitItem.addActionListener(e -> System.exit(0));
fileMenu.add(exitItem);
menuBar.add(fileMenu);
// 编辑菜单
JMenu editMenu = new JMenu("编辑");
JMenuItem undoItem = new JMenuItem("撤销");
undoItem.addActionListener(e -> {
if (drawing.undo()) {
drawing.render();
statusLabel.setText("已撤销操作");
} else {
statusLabel.setText("没有操作可撤销");
}
});
editMenu.add(undoItem);
JMenuItem redoItem = new JMenuItem("重做");
redoItem.addActionListener(e -> {
if (drawing.redo()) {
drawing.render();
statusLabel.setText("已重做操作");
} else {
statusLabel.setText("没有操作可重做");
}
});
editMenu.add(redoItem);
editMenu.addSeparator();
JMenuItem clearItem = new JMenuItem("清除所有");
clearItem.addActionListener(e -> {
clearDrawing();
});
editMenu.add(clearItem);
menuBar.add(editMenu);
// 渲染菜单
JMenu renderMenu = new JMenu("渲染");
JMenuItem localRenderItem = new JMenuItem("本地渲染");
localRenderItem.addActionListener(e -> switchToLocalRendering());
renderMenu.add(localRenderItem);
JMenuItem remoteRenderItem = new JMenuItem("远程渲染");
remoteRenderItem.addActionListener(e -> switchToRemoteRendering());
renderMenu.add(remoteRenderItem);
menuBar.add(renderMenu);
// 帮助菜单
JMenu helpMenu = new JMenu("帮助");
JMenuItem aboutItem = new JMenuItem("关于");
aboutItem.addActionListener(e -> {
JOptionPane.showMessageDialog(this,
"图形渲染系统\n版本 1.0\n\n使用Java Swing开发的图形渲染应用\n支持多种设计模式\nsxizhuo.cn\nmengxizhuo@gmail.com",
"关于图形渲染系统",
JOptionPane.INFORMATION_MESSAGE);
});
helpMenu.add(aboutItem);
menuBar.add(helpMenu);
return menuBar;
}
private void addSampleShapes() {
// 添加一个圆形
Shape circle = shapeFactory.createCircle(200, 150, 50);
drawing.addShape(circle);
// 添加一个矩形
Shape rectangle = shapeFactory.createRectangle(400, 200, 150, 100);
drawing.addShape(rectangle);
// 添加一条线
Shape line = shapeFactory.createLine(100, 300, 500, 400);
drawing.addShape(line);
// 添加一个三角形
Shape triangle = shapeFactory.createTriangle(300, 100, 350, 200, 250, 200);
drawing.addShape(triangle);
// 刷新显示
drawing.render();
statusLabel.setText("已添加示例图形");
}
private JToolBar createToolBar() {
JToolBar toolBar = new JToolBar();
toolBar.setFloatable(false);
// 形状选择按钮
String[] shapeTypes = {"Circle", "Rectangle", "Line", "Triangle"};
ButtonGroup shapeGroup = new ButtonGroup();
for (String type : shapeTypes) {
JToggleButton button = new JToggleButton(type);
button.addActionListener(e -> {
currentShapeType = type;
statusLabel.setText("已选择: " + type);
// 重置绘制状态
isDrawingLine = false;
isDrawingTriangle = false;
triangleStage = 0;
});
shapeGroup.add(button);
toolBar.add(button);
// 默认选择圆形
if ("Circle".equals(type)) {
button.setSelected(true);
}
}
toolBar.addSeparator();
// 清除按钮
JButton clearButton = new JButton("清除");
clearButton.addActionListener(e -> clearDrawing());
toolBar.add(clearButton);
toolBar.addSeparator();
// 渲染模式切换按钮
remoteRenderingToggle = new JToggleButton("远程渲染");
remoteRenderingToggle.addActionListener(e -> {
if (remoteRenderingToggle.isSelected()) {
switchToRemoteRendering();
} else {
switchToLocalRendering();
}
});
toolBar.add(remoteRenderingToggle);
return toolBar;
}
// 切换到本地渲染
private void switchToLocalRendering() {
if (isRemoteRenderingEnabled) {
// 如果当前是远程渲染,则断开连接
remoteRenderer.disconnect();
// 切换到本地渲染器
drawing.setRenderer(localRenderer);
// 更新状态
isRemoteRenderingEnabled = false;
remoteRenderingToggle.setSelected(false);
statusLabel.setText("【本地渲染模式】已切换到本地渲染");
// 重新渲染
drawing.render();
}
}
// 切换到远程渲染
private void switchToRemoteRendering() {
if (!isRemoteRenderingEnabled) {
// 尝试连接到远程渲染器
boolean connected = remoteRenderer.connect();
if (connected) {
// 切换到远程渲染器
drawing.setRenderer(remoteRenderer);
// 更新状态
isRemoteRenderingEnabled = true;
remoteRenderingToggle.setSelected(true);
statusLabel.setText("【远程渲染模式】已连接到远程渲染服务");
// 重新渲染
drawing.render();
} else {
// 连接失败,保持本地渲染
remoteRenderingToggle.setSelected(false);
statusLabel.setText("远程渲染连接失败,保持本地渲染模式");
JOptionPane.showMessageDialog(this,
"无法连接到远程渲染服务",
"连接错误",
JOptionPane.ERROR_MESSAGE);
}
}
}
private void setupRenderPanelListeners(JPanel renderPanel) {
renderPanel.addMouseListener(new MouseAdapter() {
@Override
public void mousePressed(MouseEvent e) {
dragStart = e.getPoint();
// 检查是否点击了现有形状
for (Shape shape : drawing.getShapes()) {
if (isPointOnShape(shape, e.getPoint())) {
selectedShape = shape;
isDragging = true;
statusLabel.setText("已选中图形: " + getShapeTypeName(shape));
return;
}
}
// 如果没有点击现有形状,处理新形状创建
if (currentShapeType.equals("Line")) {
if (!isDrawingLine) {
// 开始绘制线条
isDrawingLine = true;
x1 = e.getX();
y1 = e.getY();
statusLabel.setText("绘制线条: 已设置起点,请点击终点");
} else {
// 完成线条绘制
x2 = e.getX();
y2 = e.getY();
Shape line = shapeFactory.createLine(x1, y1, x2, y2);
drawing.addShape(line);
drawing.render();
isDrawingLine = false;
statusLabel.setText("已添加线条");
}
} else if (currentShapeType.equals("Triangle")) {
if (triangleStage == 0) {
// 第一个点
x1 = e.getX();
y1 = e.getY();
triangleStage = 1;
isDrawingTriangle = true;
statusLabel.setText("绘制三角形: 已设置第一个顶点,请点击第二个顶点");
} else if (triangleStage == 1) {
// 第二个点
x2 = e.getX();
y2 = e.getY();
triangleStage = 2;
statusLabel.setText("绘制三角形: 已设置第二个顶点,请点击第三个顶点");
} else if (triangleStage == 2) {
// 第三个点,完成三角形
Shape triangle = shapeFactory.createTriangle(x1, y1, x2, y2, e.getX(), e.getY());
drawing.addShape(triangle);
drawing.render();
triangleStage = 0;
isDrawingTriangle = false;
statusLabel.setText("已添加三角形");
}
} else {
// 创建其他形状
createNewShape(e.getPoint());
}
}
@Override
public void mouseReleased(MouseEvent e) {
isDragging = false;
if (selectedShape != null) {
statusLabel.setText("已移动图形: " + getShapeTypeName(selectedShape));
}
selectedShape = null;
}
});
renderPanel.addMouseMotionListener(new MouseMotionAdapter() {
@Override
public void mouseDragged(MouseEvent e) {
if (isDragging && selectedShape != null) {
drawing.moveShape(selectedShape, e.getX(), e.getY());
statusLabel.setText("正在移动图形: " + getShapeTypeName(selectedShape));
}
}
@Override
public void mouseMoved(MouseEvent e) {
// 显示鼠标坐标
statusLabel.setText("坐标: (" + e.getX() + ", " + e.getY() + ")");
}
});
}
private String getShapeTypeName(Shape shape) {
if (shape instanceof com.example.graphics.model.Circle) {
return "圆形";
} else if (shape instanceof com.example.graphics.model.Rectangle) {
return "矩形";
} else if (shape instanceof com.example.graphics.model.Line) {
return "直线";
} else if (shape instanceof com.example.graphics.model.Triangle) {
return "三角形";
}
return "图形";
}
private boolean isPointOnShape(Shape shape, Point point) {
if (shape instanceof com.example.graphics.model.Circle) {
com.example.graphics.model.Circle circle = (com.example.graphics.model.Circle) shape;
double distance = Math.sqrt(
Math.pow(point.x - circle.getX(), 2) +
Math.pow(point.y - circle.getY(), 2)
);
return distance <= circle.getRadius();
} else if (shape instanceof com.example.graphics.model.Rectangle) {
com.example.graphics.model.Rectangle rect = (com.example.graphics.model.Rectangle) shape;
return point.x >= rect.getX() && point.x <= rect.getX() + rect.getWidth() &&
point.y >= rect.getY() && point.y <= rect.getY() + rect.getHeight();
} else if (shape instanceof com.example.graphics.model.Line) {
com.example.graphics.model.Line line = (com.example.graphics.model.Line) shape;
double lineLength = Math.sqrt(
Math.pow(line.getX2() - line.getX1(), 2) +
Math.pow(line.getY2() - line.getY1(), 2)
);
double d1 = Math.sqrt(
Math.pow(point.x - line.getX1(), 2) +
Math.pow(point.y - line.getY1(), 2)
);
double d2 = Math.sqrt(
Math.pow(point.x - line.getX2(), 2) +
Math.pow(point.y - line.getY2(), 2)
);
// 允许5个像素的误差
return Math.abs(d1 + d2 - lineLength) <= 5;
} else if (shape instanceof com.example.graphics.model.Triangle) {
com.example.graphics.model.Triangle triangle = (com.example.graphics.model.Triangle) shape;
// 使用重心坐标法判断点是否在三角形内
int x1 = triangle.getX1();
int y1 = triangle.getY1();
int x2 = triangle.getX2();
int y2 = triangle.getY2();
int x3 = triangle.getX3();
int y3 = triangle.getY3();
double denominator = ((y2 - y3) * (x1 - x3) + (x3 - x2) * (y1 - y3));
double a = ((y2 - y3) * (point.x - x3) + (x3 - x2) * (point.y - y3)) / denominator;
double b = ((y3 - y1) * (point.x - x3) + (x1 - x3) * (point.y - y3)) / denominator;
double c = 1 - a - b;
// 如果重心坐标都在0到1之间,则点在三角形内
return a >= 0 && a <= 1 && b >= 0 && b <= 1 && c >= 0 && c <= 1;
}
return false;
}
private void createNewShape(Point point) {
Shape newShape = null;
switch (currentShapeType) {
case "Circle":
newShape = shapeFactory.createCircle(point.x, point.y, 50);
statusLabel.setText("已添加圆形");
break;
case "Rectangle":
newShape = shapeFactory.createRectangle(point.x, point.y, 100, 80);
statusLabel.setText("已添加矩形");
break;
// Line和Triangle在mousePressed中处理
}
if (newShape != null) {
drawing.addShape(newShape);
drawing.render();
}
}
private void clearDrawing() {
// 清除所有形状
for (Shape shape : drawing.getShapes().toArray(new Shape[0])) {
drawing.removeShape(shape);
}
drawing.render();
statusLabel.setText("已清除所有图形");
}
private void newDrawing() {
if (!drawing.getShapes().isEmpty()) {
int option = JOptionPane.showConfirmDialog(this,
"是否保存当前图形?",
"新建",
JOptionPane.YES_NO_CANCEL_OPTION);
if (option == JOptionPane.YES_OPTION) {
saveDrawing();
} else if (option == JOptionPane.CANCEL_OPTION) {
return;
}
}
clearDrawing();
}
private void openDrawing() {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("打开图形文件");
// 添加文件过滤器
FileNameExtensionFilter binFilter = new FileNameExtensionFilter("二进制文件 (*.bin)", "bin");
FileNameExtensionFilter jsonFilter = new FileNameExtensionFilter("JSON文件 (*.json)", "json");
fileChooser.addChoosableFileFilter(binFilter);
fileChooser.addChoosableFileFilter(jsonFilter);
fileChooser.setFileFilter(binFilter); // 默认选择二进制文件
int result = fileChooser.showOpenDialog(this);
if (result == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
String filePath = file.getAbsolutePath();
try {
List<Shape> shapes;
if (filePath.toLowerCase().endsWith(".json")) {
shapes = fileManager.loadDrawingJson(filePath);
} else {
// 默认为二进制文件
shapes = fileManager.loadDrawingBinary(filePath);
}
// 清除当前图形
clearDrawing();
// 添加加载的图形
for (Shape shape : shapes) {
drawing.addShape(shape);
}
drawing.render();
statusLabel.setText("已加载图形文件: " + file.getName());
} catch (Exception e) {
JOptionPane.showMessageDialog(this,
"加载文件失败: " + e.getMessage(),
"错误",
JOptionPane.ERROR_MESSAGE);
statusLabel.setText("加载文件失败");
}
}
}
private void saveDrawing() {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("保存图形文件");
// 添加文件过滤器
FileNameExtensionFilter binFilter = new FileNameExtensionFilter("二进制文件 (*.bin)", "bin");
fileChooser.addChoosableFileFilter(binFilter);
fileChooser.setFileFilter(binFilter);
int result = fileChooser.showSaveDialog(this);
if (result == JFileChooser.APPROVE_OPTION) {
File file = fileChooser.getSelectedFile();
String filePath = file.getAbsolutePath();
// 确保文件有正确的扩展名
if (!filePath.toLowerCase().endsWith(".bin")) {
filePath += ".bin";
}
try {
fileManager.saveDrawingBinary(drawing, filePath);
statusLabel.setText("已保存图形文件: " + new File(filePath).getName());
} catch (IOException e) {
JOptionPane.showMessageDialog(this,
"保存文件失败: " + e.getMessage(),
"错误",
JOptionPane.ERROR_MESSAGE);
statusLabel.setText("保存文件失败");
}
}
}
private void exportToJson() {
JsonExportVisitor visitor = new JsonExportVisitor();
drawing.acceptVisitor(visitor);
saveToFile(visitor.getJsonOutput(), "json");
}
private void exportToXml() {
XmlExportVisitor visitor = new XmlExportVisitor();
drawing.acceptVisitor(visitor);
saveToFile(visitor.getXmlOutput(), "xml");
}
private void saveToFile(String content, String extension) {
JFileChooser fileChooser = new JFileChooser();
fileChooser.setDialogTitle("保存为" + extension.toUpperCase() + "文件");
int userSelection = fileChooser.showSaveDialog(this);
if (userSelection == JFileChooser.APPROVE_OPTION) {
File fileToSave = fileChooser.getSelectedFile();
String filePath = fileToSave.getAbsolutePath();
// 确保文件有正确的扩展名
if (!filePath.toLowerCase().endsWith("." + extension)) {
filePath += "." + extension;
}
try {
if (extension.equals("json")) {
fileManager.saveDrawingJson(content, filePath);
} else if (extension.equals("xml")) {
fileManager.saveDrawingXml(content, filePath);
} else {
try (FileWriter writer = new FileWriter(filePath)) {
writer.write(content);
}
}
JOptionPane.showMessageDialog(this,
"文件已保存至:" + filePath,
"保存成功", JOptionPane.INFORMATION_MESSAGE);
statusLabel.setText("已导出为" + extension.toUpperCase() + "文件");
} catch (IOException e) {
JOptionPane.showMessageDialog(this,
"保存文件失败:" + e.getMessage(),
"错误", JOptionPane.ERROR_MESSAGE);
statusLabel.setText("导出失败");
}
}
}
public static void main(String[] args) {
// 使用Swing线程
SwingUtilities.invokeLater(SwingGraphicsApp::new);
}
}