⏺ feat: add Document layer with serialization, dirty tracking, and file actions

Introduce a Document QObject that composes DesignerScene + QUndoStack,
  following "composition over inheritance" and unidirectional signal-slot
  communication per Qt Graphics View Framework conventions.

  New files:
  - include/document.h — Document class declaration
  - source/document.cpp — full implementation (~450 lines)

  Core capabilities:
  - JSON serialization/deserialization for rect, roundRect, polygon, group
    (uses dynamic_cast dispatch since subclasses don't override type())
  - Two-path dirty tracking:
    1. QUndoStack::indexChanged for undoable ops (add/delete/group/ungroup)
    2. Explicit markDirty() via DesignerScene::notifyDocumentModified()
       called by selectors after successful move/scale/rotate/edit
  - save()/saveAs()/load()/clear() with maybeSave() prompt on close
  - File actions (New/Open/Save/Save As) wired to toolbar and File menu;
    window title tracks filename + modified indicator

  Integration changes:
  - DrawingPanel now receives scene via setScene() instead of creating it
  - CMainWindow routes all modifications through Document::execute()
  - DesignerScene gains notifyDocumentModified() for selector dirty marking
  - GraphicsRectItem gains isRound()/ratioX()/ratioY() accessors
  - CreatingSelector: fix missing signalAddItem emit for polygon completion
  - Scene lifecycle uses clear() instead of delete+recreate to avoid
    dangling view pointer

  Moved QUndoStack ownership from CMainWindow into Document.
This commit is contained in:
Jesse Qu 2026-07-08 16:57:46 +08:00
parent cd77e8cb58
commit c8f71cb9e3
16 changed files with 635 additions and 30 deletions

1
.gitignore vendored
View File

@ -2,6 +2,7 @@ build/
.vscode/
.qtcreator/
.claude/
docs/
# ---> CC
CLAUDE.md

View File

@ -44,6 +44,7 @@ set(H_HEADER_FILES
include/designerScene.h
include/designerView.h
include/operationCommand.h
include/document.h
include/propertyType/CustomGadget.h
include/propertyType/CustomType.h
@ -71,6 +72,7 @@ set(CPP_SOURCE_FILES
source/designerScene.cpp
source/designerView.cpp
source/operationCommand.cpp
source/document.cpp
source/propertyType/PropertyTypeCustomization_CustomType.cpp

View File

@ -21,6 +21,9 @@ public:
GraphicsItemGroup* createGroup();
void destroyGroup();
// Notify Document that a non-undo scene change occurred (move/scale/rotate/edit)
void notifyDocumentModified();
signals:
void signalAddItem(QGraphicsItem*);

65
include/document.h Normal file
View File

@ -0,0 +1,65 @@
#ifndef DOCUMENT_H
#define DOCUMENT_H
#include <QObject>
#include <QJsonObject>
class DesignerScene;
class QGraphicsItem;
class QUndoCommand;
class QUndoStack;
class Document : public QObject
{
Q_OBJECT
Q_PROPERTY(bool modified READ isModified NOTIFY modifiedChanged)
public:
explicit Document(QObject *parent = nullptr);
~Document();
// Serialization
bool save();
bool saveAs(const QString &filePath);
bool load(const QString &filePath);
// State
bool isModified() const;
QString filePath() const;
// Unified command entry point
void execute(QUndoCommand *cmd);
// Accessors (read-only, no ownership transfer)
DesignerScene *scene() const;
QUndoStack *undoStack() const;
// Reset to empty document
void clear();
// Called by selectors to mark dirty for non-undo operations
// (move, scale, rotate, edit-shape)
void markDirty();
signals:
void modifiedChanged(bool modified);
void filePathChanged(const QString &filePath);
void saved();
void loaded();
private:
void setModified(bool modified);
void setupNewScene();
// Serialization helpers
QJsonObject serializeItem(QGraphicsItem *item) const;
QGraphicsItem *deserializeItem(const QJsonObject &obj);
DesignerScene *m_pScene;
QUndoStack *m_pUndoStack;
bool m_bModified;
int m_nSavedIndex;
QString m_sFilePath;
};
#endif // DOCUMENT_H

View File

@ -23,6 +23,8 @@ public:
QGraphicsScene* getQGraphicsScene();
DesignerScene* getDesignerScene();
void setScene(DesignerScene* scene);
void grahpicsViewZoomIn();
void grahpicsViewZoomOut();
void grahpicsViewZoomFit();

View File

@ -13,6 +13,11 @@ public:
void resize(int,double, double, const QPointF&);
void updateCoordinate();
bool isRound() const { return m_bIsRound; }
double ratioX() const { return m_dRatioX; }
double ratioY() const { return m_dRatioY; }
void setRatioX(double rx) { m_dRatioX = rx; }
void setRatioY(double ry) { m_dRatioY = ry; }
void move(const QPointF&);
void editShape(int, const QPointF&);

View File

@ -18,7 +18,7 @@ namespace Ui { class CMainWindow; }
QT_END_NAMESPACE
class QGraphicsItem;
class QUndoStack;
class Document;
class DrawingPanel;
class GraphicElementsPanel;
class DesignerScene;
@ -38,8 +38,14 @@ protected:
private:
void initializeDockUi();
void initializeAction();
bool maybeSave();
void updateWindowTitle();
private slots:
void onAction_new();
void onAction_open();
void onAction_save();
void onAction_saveAs();
void onAction_zoomIn();
void onAction_zoomOut();
void onAction_zoomFit();
@ -54,7 +60,7 @@ private:
QWidgetAction* m_pPerspectiveListAction = nullptr;
QComboBox* m_pPerspectiveComboBox = nullptr;
QUndoStack* m_pUndoStack;
Document* m_pDocument;
Ui::CMainWindow *ui;

View File

@ -1,6 +1,7 @@
#include "designerScene.h"
#include "util/selectorManager.h"
#include "graphicsItem/graphicsItemGroup.h"
#include "document.h"
#include <QPainter>
#include <QGraphicsSceneMouseEvent>
@ -174,3 +175,10 @@ void DesignerScene::destroyGroup()
{
}
void DesignerScene::notifyDocumentModified()
{
Document *doc = qobject_cast<Document *>(parent());
if (doc)
doc->markDirty();
}

385
source/document.cpp Normal file
View File

@ -0,0 +1,385 @@
#include "document.h"
#include "designerScene.h"
#include "graphicsItem/graphicsBaseItem.h"
#include "graphicsItem/graphicsRectItem.h"
#include "graphicsItem/graphicsPolygonItem.h"
#include "graphicsItem/graphicsItemGroup.h"
#include "graphicsItem/itemControlHandle.h"
#include "global.h"
#include <QFile>
#include <QJsonDocument>
#include <QJsonArray>
#include <QGraphicsScene>
#include <QUndoStack>
#include <QUndoCommand>
Document::Document(QObject *parent)
: QObject(parent)
, m_pScene(nullptr)
, m_pUndoStack(nullptr)
, m_bModified(false)
, m_nSavedIndex(0)
{
setupNewScene();
m_pUndoStack = new QUndoStack(this);
connect(m_pUndoStack, &QUndoStack::indexChanged, this, [this](int idx) {
setModified(idx != m_nSavedIndex);
});
}
Document::~Document()
{
}
void Document::setupNewScene()
{
if (m_pScene) {
// Clear items only — do NOT delete the scene. The view in DrawingPanel
// still references it, and deleting would leave a dangling pointer.
m_pScene->clear();
} else {
m_pScene = new DesignerScene(this);
}
}
DesignerScene *Document::scene() const
{
return m_pScene;
}
QUndoStack *Document::undoStack() const
{
return m_pUndoStack;
}
bool Document::isModified() const
{
return m_bModified;
}
QString Document::filePath() const
{
return m_sFilePath;
}
void Document::setModified(bool modified)
{
if (m_bModified != modified) {
m_bModified = modified;
emit modifiedChanged(m_bModified);
}
}
void Document::execute(QUndoCommand *cmd)
{
m_pUndoStack->push(cmd);
}
void Document::markDirty()
{
setModified(true);
}
void Document::clear()
{
m_nSavedIndex = 0;
m_pUndoStack->clear();
setupNewScene();
m_sFilePath.clear();
setModified(false);
emit filePathChanged(m_sFilePath);
}
// -- Pen style string conversion ------------------------------------------
static QString penStyleToString(Qt::PenStyle style)
{
switch (style) {
case Qt::NoPen: return "none";
case Qt::SolidLine: return "solid";
case Qt::DashLine: return "dash";
case Qt::DotLine: return "dot";
case Qt::DashDotLine: return "dashDot";
case Qt::DashDotDotLine: return "dashDotDot";
default: return "solid";
}
}
static Qt::PenStyle penStyleFromString(const QString &str)
{
static const QHash<QString, Qt::PenStyle> map = {
{"none", Qt::NoPen},
{"solid", Qt::SolidLine},
{"dash", Qt::DashLine},
{"dot", Qt::DotLine},
{"dashDot", Qt::DashDotLine},
{"dashDotDot", Qt::DashDotDotLine}
};
return map.value(str, Qt::SolidLine);
}
// -- Write helpers ---------------------------------------------------------
static QJsonObject serializePen(const QPen &pen)
{
QJsonObject obj;
obj["color"] = pen.color().name(QColor::HexArgb);
obj["width"] = pen.widthF();
obj["style"] = penStyleToString(pen.style());
return obj;
}
static QJsonObject serializeBrush(const QBrush &brush)
{
QJsonObject obj;
obj["color"] = brush.color().name(QColor::HexArgb);
return obj;
}
static QJsonObject serializeCommon(QGraphicsItem *item, const QString &type)
{
QJsonObject obj;
obj["type"] = type;
obj["pos"] = QJsonObject{{"x", item->pos().x()}, {"y", item->pos().y()}};
obj["rotation"] = item->rotation();
obj["zValue"] = item->zValue();
return obj;
}
// -- Item serialization ----------------------------------------------------
QJsonObject Document::serializeItem(QGraphicsItem *item) const
{
// Groups use a different template instantiation (AbstractShapeType<QGraphicsItemGroup>),
// so qgraphicsitem_cast<AbstractShape*> will not match them.
// Check for groups first.
GraphicsItemGroup *group = dynamic_cast<GraphicsItemGroup *>(item);
if (group) {
QJsonObject obj = serializeCommon(item, "group");
obj["pen"] = serializePen(group->pen());
obj["brush"] = serializeBrush(group->brush());
QJsonArray children;
const auto kids = group->childItems();
for (QGraphicsItem *child : kids) {
if (qgraphicsitem_cast<ItemControlHandle *>(child))
continue;
QJsonObject childObj = serializeItem(child);
if (!childObj.isEmpty())
children.append(childObj);
}
obj["children"] = children;
return obj;
}
AbstractShape *shape = qgraphicsitem_cast<AbstractShape *>(item);
if (!shape)
return {};
// Use dynamic_cast for type dispatch — GraphicsBaseItem subclasses do not
// override QGraphicsItem::type(), so the GIT_* enum values are never returned.
if (GraphicsRectItem *rectItem = dynamic_cast<GraphicsRectItem *>(item)) {
bool isRound = rectItem->isRound();
QJsonObject obj = serializeCommon(item, isRound ? "roundRect" : "rect");
obj["width"] = shape->width();
obj["height"] = shape->height();
if (isRound) {
obj["ratioX"] = rectItem->ratioX();
obj["ratioY"] = rectItem->ratioY();
}
obj["pen"] = serializePen(shape->pen());
obj["brush"] = serializeBrush(shape->brush());
return obj;
}
if (GraphicPolygonItem *poly = dynamic_cast<GraphicPolygonItem *>(item)) {
QJsonObject obj = serializeCommon(item, "polygon");
QJsonArray pts;
for (const QPointF &pt : poly->getPoints()) {
pts.append(QJsonObject{{"x", pt.x()}, {"y", pt.y()}});
}
obj["points"] = pts;
obj["pen"] = serializePen(shape->pen());
obj["brush"] = serializeBrush(shape->brush());
return obj;
}
qWarning("Document::serializeItem: unrecognized item");
return {};
}
// -- Item deserialization --------------------------------------------------
static void applyCommon(QGraphicsItem *item, const QJsonObject &obj)
{
QJsonObject pos = obj["pos"].toObject();
item->setPos(pos["x"].toDouble(), pos["y"].toDouble());
item->setRotation(obj["rotation"].toDouble());
item->setZValue(obj["zValue"].toDouble());
}
template<typename T>
static void applyPen(AbstractShapeType<T> *shape, const QJsonObject &penObj)
{
QPen pen;
pen.setColor(QColor(penObj["color"].toString()));
pen.setWidthF(penObj["width"].toDouble());
pen.setStyle(penStyleFromString(penObj["style"].toString()));
shape->setPen(pen);
}
template<typename T>
static void applyBrush(AbstractShapeType<T> *shape, const QJsonObject &brushObj)
{
shape->setBrushColor(QColor(brushObj["color"].toString()));
}
QGraphicsItem *Document::deserializeItem(const QJsonObject &obj)
{
// Note: deserializeItem creates the item but does NOT add it to the scene.
// The caller (load() or a parent group) is responsible for scene addition.
QString type = obj["type"].toString();
if (type == "group") {
GraphicsItemGroup *group = new GraphicsItemGroup();
applyCommon(group, obj);
QJsonArray children = obj["children"].toArray();
QList<QGraphicsItem *> childItems;
for (const QJsonValue &val : children) {
QGraphicsItem *child = deserializeItem(val.toObject());
if (child)
childItems.append(child);
}
// Child positions in JSON are group-relative, but addToGroup()
// treats child pos() as scene-absolute. Convert before grouping.
for (QGraphicsItem *child : childItems)
child->setPos(group->mapToScene(child->pos()));
group->addItems(childItems);
applyPen(group, obj["pen"].toObject());
applyBrush(group, obj["brush"].toObject());
group->setHandleVisible(false);
return group;
}
if (type == "rect" || type == "roundRect") {
double w = obj["width"].toDouble();
double h = obj["height"].toDouble();
bool isRound = (type == "roundRect");
GraphicsRectItem *item = new GraphicsRectItem(
QRectF(-w / 2, -h / 2, w, h).toRect(), isRound);
applyCommon(item, obj);
applyPen(item, obj["pen"].toObject());
applyBrush(item, obj["brush"].toObject());
if (isRound) {
item->setRatioX(obj["ratioX"].toDouble(0.1));
item->setRatioY(obj["ratioY"].toDouble(0.1));
}
item->updateCoordinate();
item->setHandleVisible(false);
return item;
}
if (type == "polygon") {
GraphicPolygonItem *item = new GraphicPolygonItem();
QJsonArray pts = obj["points"].toArray();
for (const QJsonValue &val : pts) {
QJsonObject pt = val.toObject();
item->addPoint(QPointF(pt["x"].toDouble(), pt["y"].toDouble()));
}
item->endDrawing();
applyCommon(item, obj);
applyPen(item, obj["pen"].toObject());
applyBrush(item, obj["brush"].toObject());
item->updateCoordinate();
item->setHandleVisible(false);
return item;
}
return nullptr;
}
// -- Save / Load -----------------------------------------------------------
bool Document::save()
{
if (m_sFilePath.isEmpty())
return false;
return saveAs(m_sFilePath);
}
bool Document::saveAs(const QString &filePath)
{
QJsonArray items;
const auto sceneItems = m_pScene->items();
for (QGraphicsItem *item : sceneItems) {
// Only serialize top-level items (children of groups are serialized recursively)
if (item->parentItem())
continue;
QJsonObject obj = serializeItem(item);
if (!obj.isEmpty())
items.append(obj);
}
QJsonObject root;
root["version"] = "1.0";
root["items"] = items;
QFile file(filePath);
if (!file.open(QIODevice::WriteOnly))
return false;
file.write(QJsonDocument(root).toJson());
file.close();
m_sFilePath = filePath;
m_nSavedIndex = m_pUndoStack->index();
setModified(false);
emit filePathChanged(m_sFilePath);
emit saved();
return true;
}
bool Document::load(const QString &filePath)
{
QFile file(filePath);
if (!file.open(QIODevice::ReadOnly))
return false;
QByteArray data = file.readAll();
file.close();
QJsonParseError error;
QJsonDocument doc = QJsonDocument::fromJson(data, &error);
if (error.error != QJsonParseError::NoError)
return false;
QJsonObject root = doc.object();
// Clear current state without triggering undo
m_nSavedIndex = 0;
m_pUndoStack->clear();
setupNewScene();
QJsonArray items = root["items"].toArray();
for (const QJsonValue &val : items) {
QGraphicsItem *item = deserializeItem(val.toObject());
if (item)
m_pScene->addItem(item);
}
m_pScene->clearSelection();
m_sFilePath = filePath;
m_nSavedIndex = 0;
setModified(false);
emit filePathChanged(m_sFilePath);
emit loaded();
return true;
}

View File

@ -10,14 +10,9 @@ DrawingPanel::DrawingPanel(QWidget *parent)
{
ui->setupUi(this);
m_pGraphicsScene = new DesignerScene(this);
//设置场景大小.前两个参数为scene的坐标远点设置到view的中心点后无论view如何缩放secne的坐标原点都不会动方便后续的位置计算
m_pGraphicsScene->setSceneRect(-g_dGriaphicsScene_Width / 2, -g_dGriaphicsScene_Height / 2, g_dGriaphicsScene_Width, g_dGriaphicsScene_Height);
m_pGraphicsScene->setGridVisible(true);
m_pGraphicsScene = nullptr;
m_pGraphicsView = new DesignerView(this);
m_pGraphicsView->setScene(m_pGraphicsScene);
m_pGraphicsScene->setView(m_pGraphicsView);
ui->mainLayout->addWidget(m_pGraphicsView);
}
@ -69,3 +64,13 @@ void DrawingPanel::onSignal_addGraphicsItem(GraphicsItemType& itemType)
SelectorManager::getInstance()->setDrawGraphicsItem(itemType);
}
}
void DrawingPanel::setScene(DesignerScene* scene)
{
m_pGraphicsScene = scene;
m_pGraphicsScene->setSceneRect(-g_dGriaphicsScene_Width / 2, -g_dGriaphicsScene_Height / 2,
g_dGriaphicsScene_Width, g_dGriaphicsScene_Height);
m_pGraphicsScene->setGridVisible(true);
m_pGraphicsView->setScene(m_pGraphicsScene);
m_pGraphicsScene->setView(m_pGraphicsView);
}

View File

@ -9,6 +9,9 @@
#include <QUndoStack>
#include <QGraphicsScene>
#include <QGraphicsItem>
#include <QFileDialog>
#include <QMessageBox>
#include <QFileInfo>
#include "DockAreaWidget.h"
#include "DockAreaTitleBar.h"
@ -26,6 +29,7 @@
#include "designerScene.h"
#include "graphicElementsPanel.h"
#include "operationCommand.h"
#include "document.h"
using namespace ads;
@ -35,7 +39,7 @@ CMainWindow::CMainWindow(QWidget *parent)
, ui(new Ui::CMainWindow)
{
ui->setupUi(this);
m_pUndoStack = nullptr;
m_pDocument = new Document(this);
initializeDockUi();
initializeAction();
@ -51,8 +55,10 @@ CMainWindow::~CMainWindow()
void CMainWindow::closeEvent(QCloseEvent* event)
{
// Delete dock manager here to delete all floating widgets. This ensures
// that all top level windows of the dock manager are properly closed
if (!maybeSave()) {
event->ignore();
return;
}
m_pDockManager->deleteLater();
QMainWindow::closeEvent(event);
}
@ -72,7 +78,8 @@ void CMainWindow::initializeDockUi()
// Set central widget
m_pDrawingPanel = new DrawingPanel();
DesignerScene* designerScene = m_pDrawingPanel->getDesignerScene();
m_pDrawingPanel->setScene(m_pDocument->scene());
DesignerScene* designerScene = m_pDocument->scene();
connect(designerScene, SIGNAL(signalAddItem(QGraphicsItem*)), this, SLOT(onSignal_addItem(QGraphicsItem*)));
connect(designerScene, SIGNAL(selectionChanged()), this, SLOT(onSignal_selectionChanged()));
CDockWidget* CentralDockWidget = new CDockWidget("CentralWidget");
@ -98,7 +105,7 @@ void CMainWindow::initializeDockUi()
propertiesDockWidget->setMinimumSizeHintMode(CDockWidget::MinimumSizeHintFromDockWidget);
propertiesDockWidget->resize(550, 150);
propertiesDockWidget->setMinimumSize(500,150);
m_pPropertiesEditorView->setObject(m_pDrawingPanel->getQGraphicsScene());
m_pPropertiesEditorView->setObject(m_pDocument->scene());
m_pDockManager->addDockWidget(DockWidgetArea::RightDockWidgetArea, propertiesDockWidget, centralDockArea);
ui->menuView->addAction(propertiesDockWidget->toggleViewAction());
}
@ -106,17 +113,15 @@ void CMainWindow::initializeDockUi()
void CMainWindow::initializeAction()
{
//撤销、重做
m_pUndoStack = new QUndoStack(this);
ui->actionUndo = m_pUndoStack->createUndoAction(this, tr("撤销"));
QUndoStack *undoStack = m_pDocument->undoStack();
ui->actionUndo = undoStack->createUndoAction(this, tr("撤销"));
ui->actionUndo->setIcon(QIcon::fromTheme(QString::fromUtf8("edit-undo")));
ui->actionUndo->setShortcuts(QKeySequence::Undo);
ui->actionRedo = m_pUndoStack->createRedoAction(this, tr("重做"));
ui->actionRedo = undoStack->createRedoAction(this, tr("重做"));
ui->actionRedo->setIcon(QIcon::fromTheme(QString::fromUtf8("edit-redo")));
ui->actionRedo->setShortcuts(QKeySequence::Redo);
ui->toolBar->addAction(ui->actionUndo);
ui->toolBar->addAction(ui->actionRedo);
ui->actionUndo->setEnabled(m_pUndoStack->canUndo());
ui->actionRedo->setEnabled(m_pUndoStack->canRedo());
ui->actionDelete->setShortcut(QKeySequence::Delete);
@ -126,6 +131,30 @@ void CMainWindow::initializeAction()
connect(ui->actionZoomFit, SIGNAL(triggered()), this, SLOT(onAction_zoomFit()));
connect(ui->actionGroup, SIGNAL(triggered()), this, SLOT(onAction_createGroup()));
connect(ui->actionUngroup, SIGNAL(triggered()), this, SLOT(onAction_destroyGroup()));
// File actions
connect(ui->actionNew, SIGNAL(triggered()), this, SLOT(onAction_new()));
connect(ui->actionOpen, SIGNAL(triggered()), this, SLOT(onAction_open()));
connect(ui->actionSave, SIGNAL(triggered()), this, SLOT(onAction_save()));
// Save As — created in code since not in .ui
QAction *actionSaveAs = new QAction(QIcon::fromTheme("document-save-as"), tr("另存为..."), this);
actionSaveAs->setShortcut(QKeySequence::SaveAs);
ui->toolBar->addAction(actionSaveAs);
connect(actionSaveAs, SIGNAL(triggered()), this, SLOT(onAction_saveAs()));
// Populate File menu
ui->menuFile->addAction(ui->actionNew);
ui->menuFile->addAction(ui->actionOpen);
ui->menuFile->addAction(ui->actionSave);
ui->menuFile->addAction(actionSaveAs);
ui->menuFile->addSeparator();
ui->menuFile->addAction(ui->actionDelete);
// Document signals -> window title
connect(m_pDocument, &Document::modifiedChanged, this, [this]() { updateWindowTitle(); });
connect(m_pDocument, &Document::filePathChanged, this, [this](const QString &) { updateWindowTitle(); });
updateWindowTitle();
}
void CMainWindow::onAction_zoomIn()
@ -148,18 +177,18 @@ void CMainWindow::onAction_createGroup()
GraphicsItemGroup* group = m_pDrawingPanel->createItemGroup();
if(group)
{
QGraphicsScene* scene = m_pDrawingPanel->getQGraphicsScene();
QGraphicsScene* scene = m_pDocument->scene();
QUndoCommand* createItemGropu = new CreateItemGoupCommand(group, scene);
m_pUndoStack->push(createItemGropu);
m_pDocument->execute(createItemGropu);
}
}
void CMainWindow::onAction_destroyGroup()
{
QGraphicsScene* scene = m_pDrawingPanel->getQGraphicsScene();
QGraphicsScene* scene = m_pDocument->scene();
QList<QGraphicsItem*> listItem = scene->selectedItems();
if(listItem.count() != 1)
return; //只能选择一个解组
return;
QGraphicsItem* item = listItem.first();
if(!item)
@ -169,7 +198,7 @@ void CMainWindow::onAction_destroyGroup()
if(group)
{
QUndoCommand* destroyItemGropu = new DestroyItemGoupCommand(group, scene);
m_pUndoStack->push(destroyItemGropu);
m_pDocument->execute(destroyItemGropu);
}
}
@ -178,27 +207,111 @@ void CMainWindow::onSignal_addItem(QGraphicsItem* item)
if(item)
{
QUndoCommand* addItemCommand = new AddItemCommand(item, item->scene());
m_pUndoStack->push(addItemCommand);
m_pDocument->execute(addItemCommand);
}
}
void CMainWindow::onSignal_deleteItem()
{
QGraphicsScene* scene = m_pDrawingPanel->getQGraphicsScene();
QGraphicsScene* scene = m_pDocument->scene();
if (scene && scene->selectedItems().isEmpty())
return;
QUndoCommand* deleteItemCommand = new DeleteItemCommand(scene);
m_pUndoStack->push(deleteItemCommand); //push时会自动调用一次command的redo函数
m_pDocument->execute(deleteItemCommand);
}
void CMainWindow::onSignal_selectionChanged()
{
QList<QGraphicsItem*> selectedItems = m_pDrawingPanel->getQGraphicsScene()->selectedItems();
QList<QGraphicsItem*> selectedItems = m_pDocument->scene()->selectedItems();
if(selectedItems.count() != 1) {
m_pPropertiesEditorView->setObject(m_pDrawingPanel->getQGraphicsScene());
m_pPropertiesEditorView->setObject(m_pDocument->scene());
return;
}
GraphicsBaseItem *item = static_cast<GraphicsBaseItem*>(selectedItems.first());
m_pPropertiesEditorView->setObject(static_cast<QObject*>(item));
}
// -- File operations -------------------------------------------------------
bool CMainWindow::maybeSave()
{
if (!m_pDocument->isModified())
return true;
QMessageBox::StandardButton ret = QMessageBox::warning(
this, tr("PowerDesigner"),
tr("当前文档已修改,是否保存?"),
QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel);
switch (ret) {
case QMessageBox::Save:
if (m_pDocument->filePath().isEmpty())
onAction_saveAs();
else
m_pDocument->save();
return !m_pDocument->isModified();
case QMessageBox::Cancel:
return false;
default: // Discard
break;
}
return true;
}
void CMainWindow::updateWindowTitle()
{
QString title = "PowerDesigner";
QString path = m_pDocument->filePath();
if (!path.isEmpty()) {
title += " - " + QFileInfo(path).fileName();
} else {
title += " - " + tr("未命名");
}
if (m_pDocument->isModified())
title += " [*]";
setWindowTitle(title);
setWindowModified(m_pDocument->isModified()); // shows macOS dot indicator
}
void CMainWindow::onAction_new()
{
if (!maybeSave())
return;
m_pDocument->clear();
}
void CMainWindow::onAction_open()
{
if (!maybeSave())
return;
QString filePath = QFileDialog::getOpenFileName(
this, tr("打开文件"), QString(),
tr("JSON 文件 (*.json);;所有文件 (*)"));
if (filePath.isEmpty())
return;
m_pDocument->load(filePath);
}
void CMainWindow::onAction_save()
{
if (m_pDocument->filePath().isEmpty())
onAction_saveAs();
else
m_pDocument->save();
}
void CMainWindow::onAction_saveAs()
{
QString filePath = QFileDialog::getSaveFileName(
this, tr("另存为"), QString(),
tr("JSON 文件 (*.json);;所有文件 (*)"));
if (filePath.isEmpty())
return;
m_pDocument->saveAs(filePath);
}

View File

@ -138,7 +138,10 @@ void CreatingSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, Design
else if (m_pCreatingItem && m_creatingMethod == CM_click && event->button() == Qt::RightButton) //右键结束绘制
{
if(m_pCreatingItem->endDrawing())
{
m_pCreatingItem->updateCoordinate();
emit scene->signalAddItem(m_pCreatingItem);
}
else
{
m_pCreatingItem->setSelected(false);

View File

@ -44,6 +44,7 @@ void EditingSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, Designe
if(item && s_ptMouseLast != s_ptMouseDown)
{
item->updateCoordinate();
scene->notifyDocumentModified();
}
}

View File

@ -41,6 +41,9 @@ void MovingSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, Designer
item->removeOperationCopy();
}
if (s_ptMouseLast != s_ptMouseDown)
scene->notifyDocumentModified();
setCursor(scene, Qt::ArrowCursor);
scene->callParentEvent(event);
emit setWorkingSelector(ST_base);

View File

@ -58,9 +58,11 @@ void RotationSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, Design
{
item->removeOperationCopy();
}
}
if (s_ptMouseLast != s_ptMouseDown)
scene->notifyDocumentModified();
s_nDragHandle = H_none;
setCursor(scene, Qt::ArrowCursor);
emit setWorkingSelector(ST_base);

View File

@ -60,6 +60,7 @@ void ScalingSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, Designe
if(item && s_ptMouseLast != s_ptMouseDown)
{
item->updateCoordinate();
scene->notifyDocumentModified();
}
}