⏺ 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:
parent
cd77e8cb58
commit
c8f71cb9e3
|
|
@ -2,6 +2,7 @@ build/
|
|||
.vscode/
|
||||
.qtcreator/
|
||||
.claude/
|
||||
docs/
|
||||
|
||||
# ---> CC
|
||||
CLAUDE.md
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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*);
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -23,6 +23,8 @@ public:
|
|||
QGraphicsScene* getQGraphicsScene();
|
||||
DesignerScene* getDesignerScene();
|
||||
|
||||
void setScene(DesignerScene* scene);
|
||||
|
||||
void grahpicsViewZoomIn();
|
||||
void grahpicsViewZoomOut();
|
||||
void grahpicsViewZoomFit();
|
||||
|
|
|
|||
|
|
@ -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&);
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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,10 +55,12 @@ 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);
|
||||
QMainWindow::closeEvent(event);
|
||||
}
|
||||
|
||||
void CMainWindow::changeEvent(QEvent* 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -44,6 +44,7 @@ void EditingSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, Designe
|
|||
if(item && s_ptMouseLast != s_ptMouseDown)
|
||||
{
|
||||
item->updateCoordinate();
|
||||
scene->notifyDocumentModified();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -60,6 +60,7 @@ void ScalingSelector::mouseReleaseEvent(QGraphicsSceneMouseEvent* event, Designe
|
|||
if(item && s_ptMouseLast != s_ptMouseDown)
|
||||
{
|
||||
item->updateCoordinate();
|
||||
scene->notifyDocumentModified();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue