mapobjectitem.cpp
Upload User: chipsz
Upload Date: 2021-05-21
Package Size: 830k
Code Size: 12k
Category:

GIS program

Development Platform:

QT

  1. /*
  2.  * Tiled Map Editor (Qt)
  3.  * Copyright 2008-2009 Tiled (Qt) developers (see AUTHORS file)
  4.  *
  5.  * This file is part of Tiled (Qt).
  6.  *
  7.  * This program is free software; you can redistribute it and/or modify it
  8.  * under the terms of the GNU General Public License as published by the Free
  9.  * Software Foundation; either version 2 of the License, or (at your option)
  10.  * any later version.
  11.  *
  12.  * This program is distributed in the hope that it will be useful, but WITHOUT
  13.  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  14.  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
  15.  * more details.
  16.  *
  17.  * You should have received a copy of the GNU General Public License along with
  18.  * this program; if not, write to the Free Software Foundation, Inc., 59 Temple
  19.  * Place, Suite 330, Boston, MA 02111-1307, USA.
  20.  */
  21. #include "mapobjectitem.h"
  22. #include "addremovemapobject.h"
  23. #include "map.h"
  24. #include "mapdocument.h"
  25. #include "mapobject.h"
  26. #include "maprenderer.h"
  27. #include "mapscene.h"
  28. #include "movemapobject.h"
  29. #include "objectgroup.h"
  30. #include "objectgroupitem.h"
  31. #include "objectpropertiesdialog.h"
  32. #include "resizemapobject.h"
  33. #include "utils.h"
  34. #include <QApplication>
  35. #include <QGraphicsSceneMouseEvent>
  36. #include <QMenu>
  37. #include <QPainter>
  38. #include <QStyleOptionGraphicsItem>
  39. #include <QUndoStack>
  40. using namespace Tiled;
  41. using namespace Tiled::Internal;
  42. namespace Tiled {
  43. namespace Internal {
  44. /**
  45.  * A resize handle that allows resizing of a map object.
  46.  */
  47. class ResizeHandle : public QGraphicsItem
  48. {
  49. public:
  50.     ResizeHandle(MapObjectItem *mapObjectItem);
  51.     QRectF boundingRect() const;
  52.     void paint(QPainter *painter,
  53.                const QStyleOptionGraphicsItem *option,
  54.                QWidget *widget = 0);
  55. protected:
  56.     void mousePressEvent(QGraphicsSceneMouseEvent *event);
  57.     void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
  58.     QVariant itemChange(GraphicsItemChange change, const QVariant &value);
  59. private:
  60.     MapObjectItem *mMapObjectItem;
  61.     QSizeF mOldSize;
  62. };
  63. } // namespace Internal
  64. } // namespace Tiled
  65. ResizeHandle::ResizeHandle(MapObjectItem *mapObjectItem)
  66.     : QGraphicsItem(mapObjectItem)
  67.     , mMapObjectItem(mapObjectItem)
  68. {
  69.     setCursor(Qt::SizeFDiagCursor);
  70.     setFlag(QGraphicsItem::ItemIsMovable);
  71. #if QT_VERSION >= 0x040600
  72.     setFlag(QGraphicsItem::ItemSendsGeometryChanges);
  73. #endif
  74. }
  75. QRectF ResizeHandle::boundingRect() const
  76. {
  77.     return QRectF(-5, -5, 10 + 1, 10 + 1);
  78. }
  79. void ResizeHandle::paint(QPainter *painter,
  80.                          const QStyleOptionGraphicsItem *,
  81.                          QWidget *)
  82. {
  83.     painter->setBrush(mMapObjectItem->color());
  84.     painter->setPen(Qt::black);
  85.     painter->drawRect(QRectF(-5, -5, 10, 10));
  86. }
  87. void ResizeHandle::mousePressEvent(QGraphicsSceneMouseEvent *event)
  88. {
  89.     // Remember the old size since we may resize the object
  90.     if (event->button() == Qt::LeftButton)
  91.         mOldSize = mMapObjectItem->mapObject()->size();
  92.     QGraphicsItem::mousePressEvent(event);
  93. }
  94. void ResizeHandle::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
  95. {
  96.     QGraphicsItem::mouseReleaseEvent(event);
  97.     // If we resized the object, create an undo command
  98.     MapObject *obj = mMapObjectItem->mapObject();
  99.     if (event->button() == Qt::LeftButton && mOldSize != obj->size()) {
  100.         MapDocument *document = mMapObjectItem->mapDocument();
  101.         QUndoCommand *cmd = new ResizeMapObject(document, obj, mOldSize);
  102.         document->undoStack()->push(cmd);
  103.     }
  104. }
  105. QVariant ResizeHandle::itemChange(GraphicsItemChange change,
  106.                                   const QVariant &value)
  107. {
  108.     if (!mMapObjectItem->mSyncing) {
  109.         MapRenderer *renderer = mMapObjectItem->mapDocument()->renderer();
  110.         if (change == ItemPositionChange) {
  111.             // Calculate the absolute pixel position
  112.             const QPointF itemPos = mMapObjectItem->pos();
  113.             QPointF pixelPos = value.toPointF() + itemPos;
  114.             // Calculate the new coordinates in tiles
  115.             QPointF tileCoords = renderer->pixelToTileCoords(pixelPos);
  116.             const QPointF objectPos = mMapObjectItem->mapObject()->position();
  117.             tileCoords -= objectPos;
  118.             tileCoords.setX(qMax(tileCoords.x(), qreal(0)));
  119.             tileCoords.setY(qMax(tileCoords.y(), qreal(0)));
  120.             if (QApplication::keyboardModifiers() & Qt::ControlModifier)
  121.                 tileCoords = tileCoords.toPoint();
  122.             tileCoords += objectPos;
  123.             return renderer->tileToPixelCoords(tileCoords) - itemPos;
  124.         }
  125.         else if (change == ItemPositionHasChanged) {
  126.             // Update the size of the map object
  127.             const QPointF newPos = value.toPointF() + mMapObjectItem->pos();
  128.             QPointF tileCoords = renderer->pixelToTileCoords(newPos);
  129.             tileCoords -= mMapObjectItem->mapObject()->position();
  130.             mMapObjectItem->resize(QSizeF(tileCoords.x(), tileCoords.y()));
  131.         }
  132.     }
  133.     return QGraphicsItem::itemChange(change, value);
  134. }
  135. MapObjectItem::MapObjectItem(MapObject *object, MapDocument *mapDocument,
  136.                              ObjectGroupItem *parent):
  137.     QGraphicsItem(parent),
  138.     mObject(object),
  139.     mMapDocument(mapDocument),
  140.     mIsEditable(false),
  141.     mSyncing(false),
  142.     mResizeHandle(new ResizeHandle(this))
  143. {
  144.     syncWithMapObject();
  145.     mResizeHandle->setVisible(false);
  146. #if QT_VERSION >= 0x040600
  147.     setFlag(QGraphicsItem::ItemSendsGeometryChanges);
  148. #endif
  149. }
  150. void MapObjectItem::syncWithMapObject()
  151. {
  152.     // Update the whole object when the name or type has changed
  153.     if (mObject->name() != mName || mObject->type() != mType) {
  154.         mName = mObject->name();
  155.         mType = mObject->type();
  156.         update();
  157.         mResizeHandle->update();
  158.     }
  159.     QString toolTip = mName;
  160.     if (!mType.isEmpty())
  161.         toolTip += QLatin1String(" (") + mType + QLatin1String(")");
  162.     setToolTip(toolTip);
  163.     MapRenderer *renderer = mMapDocument->renderer();
  164.     const QPointF pixelPos = renderer->tileToPixelCoords(mObject->position());
  165.     QRectF bounds = renderer->boundingRect(mObject);
  166.     bounds.translate(-pixelPos);
  167.     mSyncing = true;
  168.     setPos(pixelPos);
  169.     if (mBoundingRect != bounds) {
  170.         // Notify the graphics scene about the geometry change in advance
  171.         prepareGeometryChange();
  172.         mBoundingRect = bounds;
  173.         const QPointF bottomRight = mObject->bounds().bottomRight();
  174.         const QPointF handlePos = renderer->tileToPixelCoords(bottomRight);
  175.         mResizeHandle->setPos(handlePos - pixelPos);
  176.     }
  177.     mSyncing = false;
  178. }
  179. void MapObjectItem::setEditable(bool editable)
  180. {
  181.     if (editable == mIsEditable)
  182.         return;
  183.     mIsEditable = editable;
  184.     setFlag(QGraphicsItem::ItemIsMovable, mIsEditable);
  185.     mResizeHandle->setVisible(mIsEditable);
  186. }
  187. QRectF MapObjectItem::boundingRect() const
  188. {
  189.     return mBoundingRect;
  190. }
  191. QPainterPath MapObjectItem::shape() const
  192. {
  193.     QPainterPath path = mMapDocument->renderer()->shape(mObject);
  194. #if QT_VERSION >= 0x040600
  195.     path.translate(-pos());
  196. #else
  197.     const QPointF p = pos();
  198.     const int elementCount = path.elementCount();
  199.     for (int i = 0; i < elementCount; i++) {
  200.         const QPainterPath::Element &element = path.elementAt(i);
  201.         path.setElementPositionAt(i, element.x - p.x(), element.y - p.y());
  202.     }
  203. #endif
  204.     return path;
  205. }
  206. void MapObjectItem::paint(QPainter *painter,
  207.                           const QStyleOptionGraphicsItem *,
  208.                           QWidget *)
  209. {
  210.     painter->translate(-pos());
  211.     const QColor color = MapObjectItem::color();
  212.     mMapDocument->renderer()->drawMapObject(painter, mObject, color);
  213. }
  214. /**
  215.  * Shows the context menu for map objects. The menu allows you to duplicate and
  216.  * remove the map object, or do edit its properties.
  217.  */
  218. void MapObjectItem::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
  219. {
  220.     if (!mIsEditable)
  221.         return;
  222.     QMenu menu;
  223.     QIcon dupIcon(QLatin1String(":images/16x16/stock-duplicate-16.png"));
  224.     QIcon delIcon(QLatin1String(":images/16x16/edit-delete.png"));
  225.     QIcon propIcon(QLatin1String(":images/16x16/document-properties.png"));
  226.     QAction *dupAction = menu.addAction(dupIcon, tr("&Duplicate"));
  227.     QAction *removeAction = menu.addAction(delIcon, tr("&Remove"));
  228.     menu.addSeparator();
  229.     QAction *propertiesAction = menu.addAction(propIcon, tr("&Properties..."));
  230.     Utils::setThemeIcon(removeAction, "edit-delete");
  231.     Utils::setThemeIcon(propertiesAction, "document-properties");
  232.     QAction *selectedAction = menu.exec(event->screenPos());
  233.     if (selectedAction == dupAction) {
  234.         MapDocument *doc = mMapDocument;
  235.         doc->undoStack()->push(new AddMapObject(doc,
  236.                                                 mObject->objectGroup(),
  237.                                                 mObject->clone()));
  238.     }
  239.     else if (selectedAction == removeAction) {
  240.         MapDocument *doc = mMapDocument;
  241.         doc->undoStack()->push(new RemoveMapObject(doc, mObject));
  242.     }
  243.     else if (selectedAction == propertiesAction) {
  244.         ObjectPropertiesDialog propertiesDialog(mMapDocument, mObject,
  245.                                                 event->widget());
  246.         propertiesDialog.exec();
  247.     }
  248. }
  249. void MapObjectItem::mousePressEvent(QGraphicsSceneMouseEvent *event)
  250. {
  251.     // Remember the old position since we may get moved
  252.     if (event->button() == Qt::LeftButton) {
  253.         mOldObjectPos = mObject->position();
  254.         mOldItemPos = pos();
  255.     }
  256.     QGraphicsItem::mousePressEvent(event);
  257. }
  258. void MapObjectItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
  259. {
  260.     QGraphicsItem::mouseReleaseEvent(event);
  261.     // If we got moved, create an undo command
  262.     if (event->button() == Qt::LeftButton
  263.         && mOldObjectPos != mObject->position()) {
  264.         MapDocument *document = mMapDocument;
  265.         QUndoCommand *cmd = new MoveMapObject(document, mObject, mOldObjectPos);
  266.         document->undoStack()->push(cmd);
  267.     }
  268. }
  269. QVariant MapObjectItem::itemChange(GraphicsItemChange change,
  270.                                    const QVariant &value)
  271. {
  272.     if (!mSyncing) {
  273.         MapRenderer *renderer = mMapDocument->renderer();
  274.         if (change == ItemPositionChange
  275.             && (QApplication::keyboardModifiers() & Qt::ControlModifier))
  276.         {
  277.             const QPointF pixelDiff = value.toPointF() - mOldItemPos;
  278.             const QPointF newPixelPos =
  279.                     renderer->tileToPixelCoords(mOldObjectPos) + pixelDiff;
  280.             // Snap the position to the grid
  281.             const QPointF newTileCoords =
  282.                     renderer->pixelToTileCoords(newPixelPos).toPoint();
  283.             return renderer->tileToPixelCoords(newTileCoords);
  284.         }
  285.         else if (change == ItemPositionHasChanged) {
  286.             // Update the position of the map object
  287.             const QPointF pixelDiff = value.toPointF() - mOldItemPos;
  288.             const QPointF newPixelPos =
  289.                     renderer->tileToPixelCoords(mOldObjectPos) + pixelDiff;
  290.             mObject->setPosition(renderer->pixelToTileCoords(newPixelPos));
  291.         }
  292.     }
  293.     return QGraphicsItem::itemChange(change, value);
  294. }
  295. void MapObjectItem::resize(const QSizeF &size)
  296. {
  297.     prepareGeometryChange();
  298.     mObject->setSize(size);
  299.     syncWithMapObject();
  300. }
  301. MapDocument *MapObjectItem::mapDocument() const
  302. {
  303.     return mMapDocument;
  304. }
  305. QColor MapObjectItem::color() const
  306. {
  307.     // Type color takes precedence
  308.     const Qt::GlobalColor typeColor = colorForType();
  309.     if (typeColor != Qt::transparent)
  310.         return typeColor;
  311.     // Get color from object group
  312.     const ObjectGroup *objectGroup = mObject->objectGroup();
  313.     if (objectGroup && objectGroup->color().isValid())
  314.         return objectGroup->color();
  315.     // Fallback color
  316.     return Qt::gray;
  317. }
  318. Qt::GlobalColor MapObjectItem::colorForType() const
  319. {
  320.     static const struct {
  321.         const char *type;
  322.         Qt::GlobalColor color;
  323.     } types[] = {
  324.         { "warp", Qt::cyan },
  325.         { "npc", Qt::yellow },
  326.         { "spawn", Qt::magenta },
  327.         { "particle_effect", Qt::green },
  328.         { 0, Qt::black }
  329.     };
  330.     Qt::GlobalColor color = Qt::transparent;
  331.     const QString &type = mType;
  332.     for (int i = 0; types[i].type; ++i) {
  333.         if (!type.compare(QLatin1String(types[i].type), Qt::CaseInsensitive)) {
  334.             color = types[i].color;
  335.             break;
  336.         }
  337.     }
  338.     return color;
  339. }