[qet] [2027] Implemented a primitive decorator, allowing groups of primitives to be easily resized. |
[ Thread Index |
Date Index
| More lists.tuxfamily.org/qet Archives
]
Revision: 2027
Author: xavier
Date: 2013-02-08 23:05:15 +0100 (Fri, 08 Feb 2013)
Log Message:
-----------
Implemented a primitive decorator, allowing groups of primitives to be easily resized.
Modified Paths:
--------------
trunk/sources/editor/customelementpart.cpp
trunk/sources/editor/customelementpart.h
trunk/sources/editor/editorcommands.cpp
trunk/sources/editor/editorcommands.h
trunk/sources/editor/elementscene.cpp
trunk/sources/editor/elementscene.h
trunk/sources/editor/partarc.cpp
trunk/sources/editor/partcircle.cpp
trunk/sources/editor/partellipse.cpp
trunk/sources/editor/partline.cpp
trunk/sources/editor/partpolygon.cpp
trunk/sources/editor/partrectangle.cpp
trunk/sources/editor/partterminal.cpp
trunk/sources/editor/parttext.cpp
trunk/sources/editor/parttext.h
trunk/sources/editor/parttextfield.cpp
trunk/sources/editor/parttextfield.h
trunk/sources/qet.cpp
trunk/sources/qet.h
Added Paths:
-----------
trunk/sources/editor/elementprimitivedecorator.cpp
trunk/sources/editor/elementprimitivedecorator.h
Modified: trunk/sources/editor/customelementpart.cpp
===================================================================
--- trunk/sources/editor/customelementpart.cpp 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/customelementpart.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -44,7 +44,62 @@
return(elementScene() -> undoStack());
}
+/// @return this primitive as a QGraphicsItem
+QGraphicsItem *CustomElementPart::toItem() {
+ return(dynamic_cast<QGraphicsItem *>(this));
+}
+
/**
+ This method is called by the decorator when it manages only a single
+ primitive. This brings the possibility to implement custom behaviour, such
+ as text edition, points edition or specific resizing.
+ The default implementation does nothing.
+*/
+void CustomElementPart::setDecorator(ElementPrimitiveDecorator *decorator) {
+ Q_UNUSED(decorator)
+}
+
+/**
+ This method is called by the decorator when it manages only a single
+ primitive and it received a mouse press event.
+ The implementation should return true if the primitive accepts the event, false otherwise.
+ The default implementation returns false.
+*/
+bool CustomElementPart::singleItemPressEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *) {
+ return(false);
+}
+
+/**
+ This method is called by the decorator when it manages only a single
+ primitive and it received a mouse move event.
+ The implementation should return true if the primitive accepts the event, false otherwise.
+ The default implementation returns false.
+*/
+bool CustomElementPart::singleItemMoveEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *) {
+ return(false);
+}
+
+/**
+ This method is called by the decorator when it manages only a single
+ primitive and it received a mouse release event.
+ The implementation should return true if the primitive accepts the event, false otherwise.
+ The default implementation returns false.
+*/
+bool CustomElementPart::singleItemReleaseEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *) {
+ return(false);
+}
+
+/**
+ This method is called by the decorator when it manages only a single
+ primitive and it received a mouse double click event.
+ The implementation should return true if the primitive accepts the event, false otherwise.
+ The default implementation returns false.
+*/
+bool CustomElementPart::singleItemDoubleClickEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *) {
+ return(false);
+}
+
+/**
Helper method to map points in CustomElementPart::handleUserTransformation()
@param initial_selection_rect Selection rectangle when the movement started, in scene coordinates
@param new_selection_rect New selection rectangle, in scene coordinates
Modified: trunk/sources/editor/customelementpart.h
===================================================================
--- trunk/sources/editor/customelementpart.h 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/customelementpart.h 2013-02-08 22:05:15 UTC (rev 2027)
@@ -21,8 +21,10 @@
#include <QtXml>
#include <QImage>
class CustomElement;
+class ElementPrimitiveDecorator;
+class ElementScene;
class QETElementEditor;
-class ElementScene;
+
/**
This abstract class represents a primitive of the visual representation of an
electrical element. The Element, FixedElement and CustomElement classes do not
@@ -95,6 +97,14 @@
/// @return the name that will be used as XML tag when exporting the primitive
virtual QString xmlName() const = 0;
+ virtual QGraphicsItem *toItem();
+
+ virtual void setDecorator(ElementPrimitiveDecorator *);
+ virtual bool singleItemPressEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *);
+ virtual bool singleItemMoveEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *);
+ virtual bool singleItemReleaseEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *);
+ virtual bool singleItemDoubleClickEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *);
+
protected:
QList<QPointF> mapPoints(const QRectF &, const QRectF &, const QList<QPointF> &);
};
Modified: trunk/sources/editor/editorcommands.cpp
===================================================================
--- trunk/sources/editor/editorcommands.cpp 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/editorcommands.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -684,3 +684,104 @@
void ChangeInformationsCommand::redo() {
editor_scene_ -> setInformations(new_informations_);
}
+
+/**
+ Constructor
+ @param scene Modified ElementScene
+ @param parent Parent QUndoCommand
+*/
+ScalePartsCommand::ScalePartsCommand(ElementScene *scene, QUndoCommand * parent) :
+ ElementEditionCommand(scene, 0, parent),
+ first_redo(true)
+{
+}
+
+/**
+ Destructor
+*/
+ScalePartsCommand::~ScalePartsCommand() {
+}
+
+/**
+ Undo the scaling operation
+*/
+void ScalePartsCommand::undo() {
+ scale(new_rect_, original_rect_);
+}
+
+/**
+ Redo the scaling operation
+*/
+void ScalePartsCommand::redo() {
+ if (first_redo) {
+ first_redo = false;
+ return;
+ }
+ scale(original_rect_, new_rect_);
+}
+
+/**
+ @return the element editor/scene the command should take place on
+*/
+ElementScene *ScalePartsCommand::elementScene() const {
+ return(editor_scene_);
+}
+
+/**
+ Set \a primitives as the list of primitives to be scaled by this command
+*/
+void ScalePartsCommand::setScaledPrimitives(const QList<CustomElementPart *> &primitives) {
+ scaled_primitives_ = primitives;
+ adjustText();
+}
+
+/**
+ @return the list of primitives to be scaled by this command
+*/
+QList<CustomElementPart *> ScalePartsCommand::scaledPrimitives() const {
+ return(scaled_primitives_);
+}
+
+/**
+ Define the transformation applied by this command
+ @param original_rect Bounding rectangle for all scaled primitives before the operation
+ @param original_rect Bounding rectangle for all scaled primitives after the operation
+*/
+void ScalePartsCommand::setTransformation(const QRectF &original_rect, const QRectF &new_rect) {
+ original_rect_ = original_rect;
+ new_rect_ = new_rect;
+}
+
+/**
+ @return the transformation applied by this command. The returned rectangles
+ are the bounding rectangles for all scaled primitives respectively before
+ and after the operation.
+*/
+QPair<QRectF, QRectF> ScalePartsCommand::transformation() {
+ return(QPair<QRectF, QRectF>(original_rect_, new_rect_));
+}
+
+/**
+ Apply the scaling operation from \a before to \a after.
+*/
+void ScalePartsCommand::scale(const QRectF &before, const QRectF &after) {
+ if (!scaled_primitives_.count()) return;
+ if (before == after) return;
+ if (!before.width() || !before.height()) return; // cowardly flee division by zero FIXME?
+
+ foreach (CustomElementPart *part_item, scaled_primitives_) {
+ part_item -> startUserTransformation(before);
+ part_item -> handleUserTransformation(before, after);
+ }
+}
+
+/**
+ Generate the text describing what this command does exactly.
+*/
+void ScalePartsCommand::adjustText() {
+ if (scaled_primitives_.count() == 1) {
+ setText(QObject::tr("redimensionnement %1", "undo caption -- %1 is the resized primitive type name").arg(scaled_primitives_.first() -> name()));
+ } else {
+ setText(QObject::tr("redimensionnement de %1 primitives", "undo caption -- %1 always > 1").arg(scaled_primitives_.count()));
+ }
+}
Modified: trunk/sources/editor/editorcommands.h
===================================================================
--- trunk/sources/editor/editorcommands.h 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/editorcommands.h 2013-02-08 22:05:15 UTC (rev 2027)
@@ -391,4 +391,42 @@
/// New information
QString new_informations_;
};
+
+/**
+ This command scales primitives when editing an electrical element.
+*/
+class ScalePartsCommand : public ElementEditionCommand {
+ // constructors, destructor
+ public:
+ ScalePartsCommand(ElementScene * = 0, QUndoCommand * = 0);
+ virtual ~ScalePartsCommand();
+ private:
+ ScalePartsCommand(const ScalePartsCommand &);
+
+ // methods
+ public:
+ virtual void undo();
+ virtual void redo();
+ ElementScene *elementScene() const;
+ void setScaledPrimitives(const QList<CustomElementPart *> &);
+ QList<CustomElementPart *> scaledPrimitives() const;
+ void setTransformation(const QRectF &, const QRectF &);
+ QPair<QRectF, QRectF> transformation();
+
+ protected:
+ void scale(const QRectF &before, const QRectF &after);
+ void adjustText();
+
+ // attributes
+ private:
+ /// List of moved primitives
+ QList<CustomElementPart *> scaled_primitives_;
+ /// original rect items fit in
+ QRectF original_rect_;
+ /// new rect items should fit in
+ QRectF new_rect_;
+ /// Prevent the first call to redo()
+ bool first_redo;
+};
+
#endif
Added: trunk/sources/editor/elementprimitivedecorator.cpp
===================================================================
--- trunk/sources/editor/elementprimitivedecorator.cpp (rev 0)
+++ trunk/sources/editor/elementprimitivedecorator.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -0,0 +1,614 @@
+#include "elementprimitivedecorator.h"
+#include "elementscene.h"
+#include "customelementpart.h"
+#include "editorcommands.h"
+#include "qet.h"
+#include <QPainter>
+#include <QDebug>
+#include <QGraphicsSceneHoverEvent>
+#include <QStyleOptionGraphicsItem>
+#include <QGraphicsScene>
+#include <QTransform>
+
+/**
+ Constructor
+ @param parent Parent QGraphicsItem
+*/
+ElementPrimitiveDecorator::ElementPrimitiveDecorator(QGraphicsItem *parent):
+ QGraphicsObject(parent)
+{
+ init();
+}
+
+/**
+ Destructor
+*/
+ElementPrimitiveDecorator::~ElementPrimitiveDecorator() {
+}
+
+/**
+ @return the internal bouding rect, i.e. the smallest rectangle containing
+ the bounding rectangle of every selected item.
+*/
+QRectF ElementPrimitiveDecorator::internalBoundingRect() const {
+ if (!decorated_items_.count() || !scene()) return(QRectF());
+
+ QRectF rect = getSceneBoundingRect(decorated_items_.first() -> toItem());
+ foreach (CustomElementPart *item, decorated_items_) {
+ rect = rect.united(getSceneBoundingRect(item -> toItem()));
+ }
+ return(rect);
+}
+
+/**
+ @return the outer bounds of the decorator as a rectangle.
+*/
+QRectF ElementPrimitiveDecorator::boundingRect() const {
+ const qreal additional_margin = 2.5;
+
+ QRectF rect = effective_bounding_rect_;
+ rect.adjust(-additional_margin, -additional_margin, additional_margin, additional_margin);
+ return(rect);
+}
+
+/**
+ Paint the contents of an item in local coordinates, using \a painter, with
+ respect to \a option and
+ @param option The option parameter provides style options for the item, such
+ as its state, exposed area and its level-of-detail hints.
+ @param The widget argument is optional. If provided, it points to the
+ widget that is being painted on; otherwise, it is 0. For cached painting,
+ widget is always 0.
+*/
+void ElementPrimitiveDecorator::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
+ Q_UNUSED(option)
+ Q_UNUSED(widget)
+ painter -> save();
+
+ // paint the original bounding rect
+ painter -> setPen(Qt::DashLine);
+ //QGraphicsItemGroup::paint(painter, option, widget);
+ painter -> drawRect(modified_bounding_rect_);
+ drawSquares(painter, option, widget);
+
+ // uncomment to draw the real bouding rect (=adjusted internal bounding rect)
+ // painter -> setBrush(QBrush(QColor(240, 0, 0, 127)));
+ // painter -> drawRect(boundingRect());
+ painter -> restore();
+}
+
+/**
+ @param items the new list of items this decorator is suposed to manipulate.
+*/
+void ElementPrimitiveDecorator::setItems(const QList<CustomElementPart *> &items) {
+ if (CustomElementPart *single_item = singleItem()) {
+ if (items.count() == 1 && items.first() == single_item) {
+ // no actual change
+ goto end_setItems;
+ }
+
+ // break any connection between the former single selected item (if any) and
+ // the decorator
+ single_item -> setDecorator(0);
+ if (QGraphicsObject *single_object = dynamic_cast<QGraphicsObject *>(single_item)) {
+ disconnect(single_object, 0, this, 0);
+ }
+ }
+
+ decorated_items_ = items;
+
+ // when only a single primitive is selected, the decorator behaves specially
+ // to enable extra features, such as text edition, internal points movements,
+ // etc.
+ if (CustomElementPart *single_item = singleItem()) {
+ single_item -> setDecorator(this);
+ }
+
+ end_setItems:
+ adjust();
+ show();
+ grabKeyboard();
+}
+
+/**
+ @param items the new list of items this decorator is suposed to manipulate.
+*/
+void ElementPrimitiveDecorator::setItems(const QList<QGraphicsItem *> &items) {
+ QList<CustomElementPart *> primitives;
+ foreach (QGraphicsItem *item, items) {
+ if (CustomElementPart *part_item = dynamic_cast<CustomElementPart *>(item)) {
+ primitives << part_item;
+ }
+ }
+ setItems(primitives);
+}
+
+/**
+ @return the list of items this decorator is supposed to manipulate
+*/
+QList<CustomElementPart *> ElementPrimitiveDecorator::items() const {
+ return(decorated_items_);
+}
+
+/**
+ @return the list of items this decorator is supposed to manipulate
+*/
+QList<QGraphicsItem *> ElementPrimitiveDecorator::graphicsItems() const {
+ QList<QGraphicsItem *> list;
+ foreach (CustomElementPart *part_item, decorated_items_) {
+ if (QGraphicsItem *item = dynamic_cast<QGraphicsItem *>(part_item)) {
+ list << item;
+ }
+ }
+ return(list);
+}
+
+/**
+ Adjust the visual decorator according to the currently assigned items.
+ It is notably called by setItems().
+*/
+void ElementPrimitiveDecorator::adjust() {
+ saveOriginalBoundingRect();
+ modified_bounding_rect_ = original_bounding_rect_;
+ adjustEffectiveBoundingRect();
+}
+
+/**
+ Handle events generated when the mouse hovers over the decorator.
+ @param event Object describing the hover event.
+*/
+void ElementPrimitiveDecorator::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
+ QList<QRectF> rects = getResizingSquares();
+ QPointF pos = event -> pos();
+
+ if (rects.at(QET::ResizeFromTopLeftCorner).contains(pos) || rects.at(QET::ResizeFromBottomRightCorner).contains(pos)) {
+ setCursor(Qt::SizeFDiagCursor);
+ } else if (rects.at(QET::ResizeFromTopRightCorner).contains(pos) || rects.at(QET::ResizeFromBottomLeftCorner).contains(pos)) {
+ setCursor(Qt::SizeBDiagCursor);
+ } else if (rects.at(QET::ResizeFromTopCenterCorner).contains(pos) || rects.at(QET::ResizeFromBottomCenterCorner).contains(pos)) {
+ setCursor(Qt::SizeVerCursor);
+ } else if (rects.at(QET::ResizeFromMiddleLeftCorner).contains(pos) || rects.at(QET::ResizeFromMiddleRightCorner).contains(pos)) {
+ setCursor(Qt::SizeHorCursor);
+ } else if (internalBoundingRect().contains(pos)) {
+ setCursor(Qt::SizeAllCursor);
+ } else {
+ setCursor(Qt::ArrowCursor);
+ }
+}
+
+/**
+ Handle event generated when mouse buttons are pressed.
+ @param event Object describing the mouse event
+*/
+void ElementPrimitiveDecorator::mousePressEvent(QGraphicsSceneMouseEvent *event) {
+ qDebug() << Q_FUNC_INFO << event << zValue();
+ QList<QRectF> rects = getResizingSquares();
+ QPointF pos = event -> pos();
+
+ current_operation_square_ = resizingSquareAtPos(pos);
+ bool accept = false;
+ if (current_operation_square_ != QET::NoOperation) {
+ accept = true;
+ } else {
+ if (internalBoundingRect().contains(pos)) {
+ if (CustomElementPart *single_item = singleItem()) {
+ bool event_accepted = single_item -> singleItemPressEvent(this, event);
+ if (event_accepted) {
+ event -> ignore();
+ return;
+ }
+ }
+ current_operation_square_ = QET::MoveArea;
+ accept = true;
+ }
+ }
+
+ if (accept) {
+ if (current_operation_square_ > QET::NoOperation) {
+ first_pos_ = latest_pos_ = mapToScene(rects.at(current_operation_square_).center());
+ } else {
+ first_pos_ = decorated_items_.at(0) -> toItem() -> scenePos();
+ latest_pos_ = event -> scenePos();
+ mouse_offset_ = event -> scenePos() - first_pos_;
+ }
+ startMovement();
+ event -> accept();
+ } else {
+ event -> ignore();
+ }
+}
+
+/**
+ Handle events generated when mouse buttons are double clicked.
+ @param event Object describing the mouse event
+*/
+void ElementPrimitiveDecorator::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) {
+ //QGraphicsObject::mouseDoubleClickEvent(event);
+ if (CustomElementPart *single_item = singleItem()) {
+ bool event_accepted = single_item -> singleItemDoubleClickEvent(this, event);
+ if (event_accepted) {
+ event -> ignore();
+ return;
+ }
+ }
+}
+
+/**
+ Handle event generated when the mouse is moved and the decorator is the mouse grabber item.
+ @param event Object describing the mouse event
+ @see QGraphicsScene::mouseGrabberItem()
+*/
+void ElementPrimitiveDecorator::mouseMoveEvent(QGraphicsSceneMouseEvent *event) {
+ QList<QRectF> rects = getResizingSquares();
+
+ QPointF scene_pos = event -> scenePos();
+ QPointF movement = scene_pos - latest_pos_;
+
+ // snap the movement to a non-visible grid when scaling selection
+ if (mustSnapToGrid(event)) {
+ // We now want some point belonging to the selection to be snapped to this rounded position
+ if (current_operation_square_ > QET::NoOperation) {
+ // real, non-rounded movement from the mouse press event
+ QPointF global_movement = scene_pos - first_pos_;
+
+ // real, rounded movement from the mouse press event
+ QPointF rounded_global_movement = snapConstPointToGrid(global_movement);
+
+ // rounded position of the current mouse move event
+ QPointF rounded_scene_pos = first_pos_ + rounded_global_movement;
+
+ // when scaling the selection, consider the center of the currently dragged resizing rectangle
+ QPointF current_position = mapToScene(rects.at(current_operation_square_).center());
+ // determine the final, effective movement
+ movement = rounded_scene_pos - current_position;
+ } else if (current_operation_square_ == QET::MoveArea) {
+ // when moving the selection, consider the position of the first selected item
+ QPointF current_position = scene_pos - mouse_offset_;
+ QPointF rounded_current_position = snapConstPointToGrid(current_position);
+ movement = rounded_current_position - decorated_items_.at(0) -> toItem() -> scenePos();
+ } else {
+ if (CustomElementPart *single_item = singleItem()) {
+ bool event_accepted = single_item -> singleItemMoveEvent(this, event);
+ if (event_accepted) {
+ event -> ignore();
+ return;
+ }
+ }
+ }
+ }
+
+ QRectF bounding_rect = modified_bounding_rect_;
+ applyMovementToRect(current_operation_square_, movement, modified_bounding_rect_);
+ if (modified_bounding_rect_ != bounding_rect) {
+ adjustEffectiveBoundingRect();
+ }
+ latest_pos_ = event -> scenePos();
+
+ if (current_operation_square_ == QET::MoveArea) {
+ translateItems(movement);
+ } else {
+ scaleItems(original_bounding_rect_, modified_bounding_rect_);
+ }
+}
+
+/**
+ Handle event generated when a mouse buttons are releaseis moved and the
+ decorator is the mouse grabber item.
+ @param event Object describing the mouse event
+ @see QGraphicsScene::mouseGrabberItem()
+*/
+void ElementPrimitiveDecorator::mouseReleaseEvent(QGraphicsSceneMouseEvent *event) {
+ Q_UNUSED(event)
+
+ ElementEditionCommand *command = 0;
+ if (current_operation_square_ > QET::NoOperation) {
+ ScalePartsCommand *scale_command = new ScalePartsCommand();
+ scale_command -> setScaledPrimitives(items());
+ scale_command -> setTransformation(
+ mapToScene(original_bounding_rect_).boundingRect(),
+ mapToScene(modified_bounding_rect_).boundingRect()
+ );
+ command = scale_command;
+ } else if (current_operation_square_ == QET::MoveArea) {
+ QPointF movement = mapToScene(modified_bounding_rect_.topLeft()) - mapToScene(original_bounding_rect_.topLeft());
+ if (!movement.isNull()) {
+ MovePartsCommand *move_command = new MovePartsCommand(movement, 0, graphicsItems());
+ command = move_command;
+ }
+ } else {
+ if (CustomElementPart *single_item = singleItem()) {
+ bool event_accepted = single_item -> singleItemReleaseEvent(this, event);
+ if (event_accepted) {
+ event -> ignore();
+ return;
+ }
+ }
+ }
+
+ if (command) {
+ emit(actionFinished(command));
+ }
+
+ if (current_operation_square_ != QET::NoOperation) {
+ adjust();
+ }
+
+ current_operation_square_ = QET::NoOperation;
+}
+
+/**
+ Initialize an ElementPrimitiveDecorator
+*/
+void ElementPrimitiveDecorator::init() {
+ //setAcceptedMouseButtons(Qt::LeftButton);
+ grid_step_x_ = grid_step_y_ = 1;
+ setAcceptHoverEvents(true);
+}
+
+/**
+ Save the original bounding rectangle.
+*/
+void ElementPrimitiveDecorator::saveOriginalBoundingRect() {
+ original_bounding_rect_ = internalBoundingRect();
+}
+
+/**
+ Adjust the effective bounding rect. This method should be called after the
+ modified_bouding_rect_ attribute was modified.
+*/
+void ElementPrimitiveDecorator::adjustEffectiveBoundingRect() {
+ prepareGeometryChange();
+ effective_bounding_rect_ = modified_bounding_rect_ | effective_bounding_rect_;
+ update();
+}
+
+/**
+ Start a movement (i.e. either a move or scaling operation)
+*/
+void ElementPrimitiveDecorator::startMovement() {
+ adjust();
+
+ foreach(CustomElementPart *item, decorated_items_) {
+ qDebug() << Q_FUNC_INFO << "starting movement with rect" << original_bounding_rect_ << "i.e. in scene coordinates " << mapToScene(original_bounding_rect_).boundingRect();
+ item -> startUserTransformation(mapToScene(original_bounding_rect_).boundingRect());
+ }
+}
+
+/**
+ Apply the movement described by \a movement_type and \a movement to \a rect.
+*/
+void ElementPrimitiveDecorator::applyMovementToRect(int movement_type, const QPointF &movement, QRectF &rect) {
+ qreal new_value;
+ QPointF new_point;
+
+ switch (movement_type) {
+ case QET::MoveArea:
+ rect.translate(movement.x(), movement.y());
+ break;
+ case QET::ResizeFromTopLeftCorner:
+ new_point = rect.topLeft() + movement;
+ rect.setTopLeft(new_point);
+ break;
+ case QET::ResizeFromTopCenterCorner:
+ new_value = rect.top() + movement.y();
+ rect.setTop(new_value);
+ break;
+ case QET::ResizeFromTopRightCorner:
+ new_point = rect.topRight() + movement;
+ rect.setTopRight(new_point);
+ break;
+ case QET::ResizeFromMiddleLeftCorner:
+ new_value = rect.left() + movement.x();
+ rect.setLeft(new_value);
+ break;
+ case QET::ResizeFromMiddleRightCorner:
+ new_value = rect.right() + movement.x();
+ rect.setRight(new_value);
+ break;
+ case QET::ResizeFromBottomLeftCorner:
+ new_point = rect.bottomLeft() + movement;
+ rect.setBottomLeft(new_point);
+ break;
+ case QET::ResizeFromBottomCenterCorner:
+ new_value = rect.bottom() + movement.y();
+ rect.setBottom(new_value);
+ break;
+ case QET::ResizeFromBottomRightCorner:
+ new_point = rect.bottomRight() + movement;
+ rect.setBottomRight(new_point);
+ break;
+ }
+}
+
+CustomElementPart *ElementPrimitiveDecorator::singleItem() const {
+ if (decorated_items_.count() == 1) {
+ return(decorated_items_.first());
+ }
+ return(0);
+}
+
+/**
+ Translated the managed items by the \a movement
+*/
+void ElementPrimitiveDecorator::translateItems(const QPointF &movement) {
+ if (!decorated_items_.count()) return;
+
+ foreach(QGraphicsItem *qgi, graphicsItems()) {
+ // this is a naive, proof-of-concept implementation; we actually need to take
+ // the grid into account and create a command object in mouseReleaseEvent()
+ qgi -> moveBy(movement.x(), movement.y());
+ }
+}
+
+
+/**
+ Scale the managed items, provided they originally fit within \a
+ original_rect and they should now fit \a new_rect
+*/
+void ElementPrimitiveDecorator::scaleItems(const QRectF &original_rect, const QRectF &new_rect) {
+ if (!decorated_items_.count()) return;
+ if (original_rect == new_rect) return;
+ if (!original_rect.width() || !original_rect.height()) return; // cowardly flee division by zero FIXME?
+
+ QRectF scene_original_rect = mapToScene(original_rect).boundingRect();
+ QRectF scene_new_rect = mapToScene(new_rect).boundingRect();
+ qDebug() << Q_FUNC_INFO << "from " << original_rect << " to " << new_rect;
+ qDebug() << Q_FUNC_INFO << "from " << scene_original_rect << " to " << scene_new_rect << "(scene coordinates)";
+
+ foreach(CustomElementPart *item, decorated_items_) {
+ item -> handleUserTransformation(scene_original_rect, scene_new_rect);
+ }
+}
+
+/**
+ @return the bounding rectangle of \a item, in scene coordinates
+*/
+QRectF ElementPrimitiveDecorator::getSceneBoundingRect(QGraphicsItem *item) const {
+ if (!item) return(QRectF());
+ return(item -> mapRectToScene(item -> boundingRect()));
+}
+
+/**
+ Draw all known resizing squares using \a painter.
+ @param option The option parameter provides style options for the item, such
+ as its state, exposed area and its level-of-detail hints.
+ @param The widget argument is optional. If provided, it points to the
+ widget that is being painted on; otherwise, it is 0. For cached painting,
+ widget is always 0.
+*/
+void ElementPrimitiveDecorator::drawSquares(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) {
+ foreach (QRectF rect, getResizingSquares()) {
+ drawResizeSquare(painter, option, widget, rect);
+ }
+}
+
+/**
+ Draw the provided resizing square \a rect using \a painter.
+ @param option The option parameter provides style options for the item, such
+ as its state, exposed area and its level-of-detail hints.
+ @param The widget argument is optional. If provided, it points to the
+ widget that is being painted on; otherwise, it is 0. For cached painting,
+ widget is always 0.
+*/
+void ElementPrimitiveDecorator::drawResizeSquare(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget, const QRectF &rect) {
+ QColor inner(0xFF, 0xFF, 0xFF);
+ QColor outer(0x00, 0x61, 0xFF);
+ if (decorated_items_.count() > 1) {
+ outer = QColor(0x1A, 0x5C, 0x14);
+ }
+ drawGenericSquare(painter, option, widget, rect, inner, outer);
+}
+
+/**
+ Draw a generic square \a rect using \a painter.
+ @param inner Color used to fill the square
+ @param outer Color usd to draw the outline
+ @param option The option parameter provides style options for the item, such
+ as its state, exposed area and its level-of-detail hints.
+ @param The widget argument is optional. If provided, it points to the
+ widget that is being painted on; otherwise, it is 0. For cached painting,
+ widget is always 0.
+*/
+void ElementPrimitiveDecorator::drawGenericSquare(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget, const QRectF &rect, const QColor &inner, const QColor &outer) {
+ Q_UNUSED(option)
+ Q_UNUSED(widget)
+ // 1.0px will end up to level_of_details px once rendered
+ // qreal level_of_details = option->levelOfDetailFromTransform(painter -> transform());
+
+ painter -> save();
+ painter -> setBrush(QBrush(inner));
+ QPen square_pen(QBrush(outer), 2.0, Qt::SolidLine, Qt::SquareCap, Qt::MiterJoin);
+ square_pen.setCosmetic(true);
+ painter -> setPen(square_pen);
+ painter -> drawRect(rect);
+ painter -> restore();
+}
+
+/**
+ @return A list containing all known resizing squares, based on the
+ modified_bounding_rect_ attribute. They are ordered following
+ QET::OperationAreas, so it is possible to use positive values from this enum
+ to fetch the corresponding square in the list.
+ @see QET::OperationAreas
+*/
+QList<QRectF> ElementPrimitiveDecorator::getResizingSquares() {
+ QRectF primitive_rect = modified_bounding_rect_;
+ QRectF half_primitive_rect1 = primitive_rect;
+ half_primitive_rect1.setHeight(half_primitive_rect1.height() / 2.0);
+ QRectF half_primitive_rect2 = primitive_rect;
+ half_primitive_rect2.setWidth(half_primitive_rect2.width() / 2.0);
+
+ QList<QRectF> rects;
+
+ rects << getGenericSquare(primitive_rect.topLeft());
+ rects << getGenericSquare(half_primitive_rect2.topRight());
+ rects << getGenericSquare(primitive_rect.topRight());
+ rects << getGenericSquare(half_primitive_rect1.bottomLeft());
+ rects << getGenericSquare(half_primitive_rect1.bottomRight());
+ rects << getGenericSquare(primitive_rect.bottomLeft());
+ rects << getGenericSquare(half_primitive_rect2.bottomRight());
+ rects << getGenericSquare(primitive_rect.bottomRight());
+
+ /// TODO cache the rects instead of calculating them again and again?
+ return(rects);
+}
+
+/**
+ @return the square to be drawn to represent \a position
+*/
+QRectF ElementPrimitiveDecorator::getGenericSquare(const QPointF &position) {
+ const qreal square_half_size = 0.5;
+ return(
+ QRectF(
+ position.x() - square_half_size,
+ position.y() - square_half_size,
+ square_half_size * 2.0,
+ square_half_size * 2.0
+ )
+ );
+}
+
+/**
+ @return the index of the square containing the \a position point or -1 if
+ none matches.
+*/
+int ElementPrimitiveDecorator::resizingSquareAtPos(const QPointF &position) {
+ QList<QRectF> rects = getResizingSquares();
+ int current_square = QET::NoOperation;
+ for (int i = 0 ; i < rects.count() ; ++ i) {
+ if (rects.at(i).contains(position)) {
+ current_square = i;
+ }
+ }
+ return(current_square);
+}
+
+/**
+ Round the coordinates of \a point so it is snapped to the grid defined by the
+ grid_step_x_ and grid_step_y_ attributes.
+*/
+QPointF ElementPrimitiveDecorator::snapConstPointToGrid(const QPointF &point) const {
+ return(
+ QPointF(
+ qRound(point.x() / grid_step_x_) * grid_step_x_,
+ qRound(point.y() / grid_step_y_) * grid_step_y_
+ )
+ );
+}
+
+/**
+ Round the coordinates of \a point so it is snapped to the grid defined by the
+ grid_step_x_ and grid_step_y_ attributes.
+*/
+void ElementPrimitiveDecorator::snapPointToGrid(QPointF &point) const {
+ point.rx() = qRound(point.x() / grid_step_x_) * grid_step_x_;
+ point.ry() = qRound(point.y() / grid_step_y_) * grid_step_y_;
+}
+
+/**
+ @return whether the current operation should take the grid into account
+ according to the state of the provided \a event
+*/
+bool ElementPrimitiveDecorator::mustSnapToGrid(QGraphicsSceneMouseEvent *event) {
+ return(!(event -> modifiers() & Qt::ControlModifier));
+}
Added: trunk/sources/editor/elementprimitivedecorator.h
===================================================================
--- trunk/sources/editor/elementprimitivedecorator.h (rev 0)
+++ trunk/sources/editor/elementprimitivedecorator.h 2013-02-08 22:05:15 UTC (rev 2027)
@@ -0,0 +1,90 @@
+#ifndef ELEMENTPRIMITIVEDECORATOR_H
+#define ELEMENTPRIMITIVEDECORATOR_H
+#include <QGraphicsObject>
+class ElementEditionCommand;
+class ElementScene;
+class CustomElementPart;
+
+/**
+ This class represents a decorator rendered above selected items so users
+ can manipulate (move, resize, ...) them.
+
+ The implementation considers four kinds of bounding rects:
+ - the actual, effective bounding rect as returned by the boundingRect() method
+ - the original bounding rect, i.e. the rect containing all selected items at
+ the beginning of operations (or after a command object was generated)
+ - the new bounding rect, after the user moved or resized items
+ - the former bounding rect, due to implementation details
+*/
+class ElementPrimitiveDecorator : public QGraphicsObject {
+ Q_OBJECT
+
+ public:
+ ElementPrimitiveDecorator(QGraphicsItem * = 0);
+ virtual ~ElementPrimitiveDecorator();
+
+ enum { Type = UserType + 2200 };
+
+ // methods
+ QRectF internalBoundingRect() const;
+ virtual QRectF boundingRect () const;
+ virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget * = 0);
+ virtual int type() const { return Type; }
+ void setItems(const QList<QGraphicsItem *> &);
+ void setItems(const QList<CustomElementPart *> &);
+ QList<CustomElementPart *> items() const;
+ QList<QGraphicsItem *> graphicsItems() const;
+
+ public slots:
+ void adjust();
+
+ signals:
+ void actionFinished(ElementEditionCommand *);
+
+ protected:
+ void hoverMoveEvent(QGraphicsSceneHoverEvent *);
+ void mousePressEvent(QGraphicsSceneMouseEvent *);
+ void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *);
+ void mouseMoveEvent(QGraphicsSceneMouseEvent *);
+ void mouseReleaseEvent(QGraphicsSceneMouseEvent *);
+ QPointF snapConstPointToGrid(const QPointF &) const;
+ void snapPointToGrid(QPointF &) const;
+ bool mustSnapToGrid(QGraphicsSceneMouseEvent *);
+
+ private:
+ void init();
+ void saveOriginalBoundingRect();
+ void adjustEffectiveBoundingRect();
+ void startMovement();
+ void applyMovementToRect(int, const QPointF &, QRectF &);
+ CustomElementPart *singleItem() const;
+ void translateItems(const QPointF &);
+ void scaleItems(const QRectF &, const QRectF &);
+ QRectF getSceneBoundingRect(QGraphicsItem *) const;
+ void drawSquares(QPainter *, const QStyleOptionGraphicsItem *, QWidget *);
+ void drawResizeSquare(QPainter *, const QStyleOptionGraphicsItem *, QWidget *, const QRectF &);
+ void drawGenericSquare(QPainter *, const QStyleOptionGraphicsItem *, QWidget *, const QRectF &, const QColor &, const QColor &);
+ QList<QRectF> getResizingSquares();
+ QRectF getGenericSquare(const QPointF &);
+ int resizingSquareAtPos(const QPointF &);
+
+ // attributes
+ private:
+ QList<CustomElementPart *> decorated_items_;
+ QRectF effective_bounding_rect_; ///< actual, effective bounding rect -- never shrinks
+ QRectF original_bounding_rect_; ///< original bounding rect
+ QRectF modified_bounding_rect_; ///< new bounding rect, after the user moved or resized items
+
+ /**
+ Index of the square leading the current operation (resizing, etc.) or -1 if no
+ operation is occurring, -2 for a move operation.
+ */
+ int current_operation_square_;
+ int grid_step_x_; ///< Grid horizontal step
+ int grid_step_y_; ///< Grid horizontal step
+ QPointF first_pos_; ///< First point involved within the current resizing operation
+ QPointF latest_pos_; ///< Latest point involved within the current resizing operation
+ QPointF mouse_offset_; ///< Offset between the mouse position and the point to be snapped to grid when moving selection
+};
+
+#endif
Modified: trunk/sources/editor/elementscene.cpp
===================================================================
--- trunk/sources/editor/elementscene.cpp 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/elementscene.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -17,6 +17,7 @@
*/
#include "elementscene.h"
#include "qetelementeditor.h"
+#include "elementprimitivedecorator.h"
#include <cmath>
#include "partline.h"
#include "partrectangle.h"
@@ -44,17 +45,22 @@
_hotspot(15, 35),
internal_connections(false),
qgi_manager(this),
- element_editor(editor)
+ element_editor(editor),
+ decorator_(0)
{
setItemIndexMethod(NoIndex);
current_polygon = NULL;
setGrid(1, 1);
initPasteArea();
undo_stack.setClean();
+ decorator_lock_ = new QMutex(QMutex::NonRecursive);
+ connect(&undo_stack, SIGNAL(indexChanged(int)), this, SLOT(managePrimitivesGroups()));
+ connect(this, SIGNAL(selectionChanged()), this, SLOT(managePrimitivesGroups()));
}
/// Destructeur
ElementScene::~ElementScene() {
+ delete decorator_lock_;
}
/**
@@ -187,25 +193,7 @@
break;
case Normal:
default:
- QList<QGraphicsItem *> selected_items = selectedItems();
- if (!selected_items.isEmpty()) {
- // mouvement de souris realise depuis le dernier press event
- QPointF mouse_movement = e -> scenePos() - moving_press_pos;
-
- // application de ce mouvement a la fsi_pos enregistre dans le dernier press event
- QPointF new_fsi_pos = fsi_pos + mouse_movement;
-
- // snap eventuel de la nouvelle fsi_pos
- if (mustSnapToGrid(e)) snapToGrid(new_fsi_pos);
-
- // difference entre la fsi_pos finale et la fsi_pos courante = mouvement a appliquer
-
- QPointF current_fsi_pos = selected_items.first() -> scenePos();
- QPointF final_movement = new_fsi_pos - current_fsi_pos;
- foreach(QGraphicsItem *qgi, selected_items) {
- qgi -> moveBy(final_movement.x(), final_movement.y());
- }
- }
+ QGraphicsScene::mouseMoveEvent(e);
}
} else if (behavior == Polygon && current_polygon != NULL) {
temp_polygon = current_polygon -> polygon();
@@ -263,16 +251,6 @@
case Normal:
default:
QGraphicsScene::mousePressEvent(e);
- // gestion des deplacements de parties
- if (!selectedItems().isEmpty()) {
- fsi_pos = selectedItems().first() -> scenePos();
- moving_press_pos = e -> scenePos();
- moving_parts_ = true;
- } else {
- fsi_pos = QPoint();
- moving_press_pos = QPoint();
- moving_parts_ = false;
- }
}
} else QGraphicsScene::mousePressEvent(e);
}
@@ -358,12 +336,6 @@
case Normal:
default:
// detecte les deplacements de parties
- if (!selectedItems().isEmpty() && moving_parts_) {
- QPointF movement = selectedItems().first() -> scenePos() - fsi_pos;
- if (!movement.isNull()) {
- undo_stack.push(new MovePartsCommand(movement, this, selectedItems()));
- }
- }
QGraphicsScene::mouseReleaseEvent(e);
moving_parts_ = false;
}
@@ -657,7 +629,7 @@
*/
QRectF ElementScene::sceneContent() const {
qreal adjustment = 5.0;
- return(itemsBoundingRect().unite(borderRect()).adjusted(-adjustment, -adjustment, adjustment, adjustment));
+ return(elementContentBoundingRect(items()).unite(borderRect()).adjusted(-adjustment, -adjustment, adjustment, adjustment));
}
/**
@@ -666,7 +638,7 @@
l'element.
*/
bool ElementScene::borderContainsEveryParts() const {
- return(borderRect().contains(itemsBoundingRect()));
+ return(borderRect().contains(elementContentBoundingRect(items())));
}
/**
@@ -800,7 +772,10 @@
// efface tout ce qui est selectionne
undo_stack.push(new DeletePartsCommand(this, selected_items));
+
+ // removing items does not trigger QGraphicsScene::selectionChanged()
emit(partsRemoved());
+ emit(selectionChanged());
}
/**
@@ -825,7 +800,7 @@
hotspot_editor -> setElementHeight(static_cast<uint>(height() / 10));
hotspot_editor -> setHotspot(hotspot());
hotspot_editor -> setOldHotspot(hotspot());
- hotspot_editor -> setPartsRect(itemsBoundingRect());
+ hotspot_editor -> setPartsRect(elementContentBoundingRect(items()));
hotspot_editor -> setPartsRectEnabled(true);
hotspot_editor -> setReadOnly(is_read_only);
dialog_layout -> addWidget(hotspot_editor);
@@ -1022,6 +997,19 @@
}
/**
+ @return the list of primitives currently present on the scene.
+*/
+QList<CustomElementPart *> ElementScene::primitives() const {
+ QList<CustomElementPart *> primitives_list;
+ foreach (QGraphicsItem *item, items()) {
+ if (CustomElementPart *primitive = dynamic_cast<CustomElementPart *>(item)) {
+ primitives_list << primitive;
+ }
+ }
+ return(primitives_list);
+}
+
+/**
@param include_terminals true pour inclure les bornes, false sinon
@return les parties de l'element ordonnes par zValue croissante
*/
@@ -1032,8 +1020,14 @@
// enleve les bornes
QList<QGraphicsItem *> terminals;
foreach(QGraphicsItem *qgi, all_items_list) {
- if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
+ if (
+ qgi -> type() == ElementPrimitiveDecorator::Type ||
+ qgi -> type() == QGraphicsRectItem::Type
+ ) {
all_items_list.removeAt(all_items_list.indexOf(qgi));
+ }
+ else if (qgraphicsitem_cast<PartTerminal *>(qgi)) {
+ all_items_list.removeAt(all_items_list.indexOf(qgi));
terminals << qgi;
}
}
@@ -1089,9 +1083,12 @@
@return le boundingRect de ces parties, exprime dans les coordonnes de la
scene
*/
-QRectF ElementScene::elementContentBoundingRect(const ElementContent &content) {
+QRectF ElementScene::elementContentBoundingRect(const ElementContent &content) const {
QRectF bounding_rect;
foreach(QGraphicsItem *qgi, content) {
+ // skip non-primitives QGraphicsItems (paste area, selection decorator)
+ if (qgi -> type() == ElementPrimitiveDecorator::Type) continue;
+ if (qgi -> type() == QGraphicsRectItem::Type) continue;
bounding_rect |= qgi -> sceneBoundingRect();
}
return(bounding_rect);
@@ -1225,7 +1222,7 @@
ElementContent ElementScene::addContent(const ElementContent &content, QString *error_message) {
Q_UNUSED(error_message);
foreach(QGraphicsItem *part, content) {
- addItem(part);
+ addPrimitive(part);
}
return(content);
}
@@ -1249,12 +1246,21 @@
// ajoute les parties avec le decalage adequat
foreach(QGraphicsItem *part, content) {
part -> setPos(part -> pos() + offset);
- addItem(part);
+ addPrimitive(part);
}
return(content);
}
/**
+ Add a primitive to the scene by wrapping it within an
+ ElementPrimitiveDecorator group.
+*/
+void ElementScene::addPrimitive(QGraphicsItem *primitive) {
+ if (!primitive) return;
+ addItem(primitive);
+}
+
+/**
Initialise la zone de collage
*/
void ElementScene::initPasteArea() {
@@ -1300,3 +1306,61 @@
bool ElementScene::zValueLessThan(QGraphicsItem *item1, QGraphicsItem *item2) {
return(item1-> zValue() < item2 -> zValue());
}
+
+/**
+ Ensure the decorator is adequately shown, hidden or updated so it always
+ represents the current selection.
+*/
+void ElementScene::managePrimitivesGroups() {
+ if (!decorator_lock_ -> tryLock()) return;
+
+ if (!decorator_) {
+ decorator_ = new ElementPrimitiveDecorator();
+ connect(decorator_, SIGNAL(actionFinished(ElementEditionCommand*)), this, SLOT(stackAction(ElementEditionCommand *)));
+ addItem(decorator_);
+ decorator_ -> hide();
+ }
+
+ QList<QGraphicsItem *> selected_items;
+ foreach (QGraphicsItem *item, items()) {
+ if (item -> type() == ElementPrimitiveDecorator::Type) continue;
+ if (item -> type() == QGraphicsRectItem::Type) continue;
+ if (item -> isSelected()) {
+ selected_items << item;
+ }
+ }
+ /// TODO export the above code to a proper method
+
+
+ // should we hide the decorator?
+ if (!selected_items.count()) {
+ decorator_ -> hide();
+ } else {
+ decorator_ -> setZValue(1000000);
+ decorator_ -> setPos(0, 0);
+ decorator_ -> setItems(selected_items);
+ }
+ decorator_lock_ -> unlock();
+}
+
+/**
+ Push the provided \a command on the undo stack.
+*/
+void ElementScene::stackAction(ElementEditionCommand *command) {
+ if (command -> elementScene()) {
+ if (command -> elementScene() != this) return;
+ } else {
+ command -> setElementScene(this);
+ }
+
+ if (!command -> elementView()) {
+ foreach (QGraphicsView *view, views()) {
+ if (ElementView *element_view = dynamic_cast<ElementView *>(view)) {
+ command -> setElementView(element_view);
+ break;
+ }
+ }
+ }
+
+ undoStack().push(command);
+}
Modified: trunk/sources/editor/elementscene.h
===================================================================
--- trunk/sources/editor/elementscene.h 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/elementscene.h 2013-02-08 22:05:15 UTC (rev 2027)
@@ -23,6 +23,9 @@
#include "orientationsetwidget.h"
#include "qgimanager.h"
#include "elementcontent.h"
+class CustomElementPart;
+class ElementEditionCommand;
+class ElementPrimitiveDecorator;
class QETElementEditor;
class PartLine;
class PartRectangle;
@@ -95,6 +98,9 @@
/// Variables to handle copy/paste with offset
QString last_copied_;
+ /// Decorator item displayed when at least one item is selected
+ ElementPrimitiveDecorator *decorator_;
+
///< Size of the horizontal grid step
int x_grid;
///< Size of the vertical grid step
@@ -123,6 +129,7 @@
virtual QRectF boundingRectFromXml(const QDomDocument &);
virtual void fromXml(const QDomDocument &, const QPointF & = QPointF(), bool = true, ElementContent * = 0);
virtual void reset();
+ virtual QList<CustomElementPart *> primitives() const;
virtual QList<QGraphicsItem *> zItems(bool = false) const;
virtual ElementContent selectedContent() const;
virtual void getPasteArea(const QRectF &);
@@ -149,15 +156,17 @@
virtual void endCurrentBehavior(const QGraphicsSceneMouseEvent *);
private:
- QRectF elementContentBoundingRect(const ElementContent &);
+ QRectF elementContentBoundingRect(const ElementContent &) const;
bool applyInformations(const QDomDocument &, QString * = 0);
ElementContent loadContent(const QDomDocument &, QString * = 0);
ElementContent addContent(const ElementContent &, QString * = 0);
ElementContent addContentAtPos(const ElementContent &, const QPointF &, QString * = 0);
+ void addPrimitive(QGraphicsItem *);
void initPasteArea();
void snapToGrid(QPointF &);
bool mustSnapToGrid(QGraphicsSceneMouseEvent *);
static bool zValueLessThan(QGraphicsItem *, QGraphicsItem *);
+ QMutex *decorator_lock_;
public slots:
void slot_move();
@@ -183,6 +192,8 @@
void slot_raise();
void slot_lower();
void slot_sendBackward();
+ void managePrimitivesGroups();
+ void stackAction(ElementEditionCommand *);
signals:
/**
Modified: trunk/sources/editor/partarc.cpp
===================================================================
--- trunk/sources/editor/partarc.cpp 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/partarc.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -29,7 +29,7 @@
_angle(-90),
start_angle(0)
{
- setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
+ setFlags(QGraphicsItem::ItemIsSelectable);
#if QT_VERSION >= 0x040600
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
#endif
Modified: trunk/sources/editor/partcircle.cpp
===================================================================
--- trunk/sources/editor/partcircle.cpp 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/partcircle.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -24,7 +24,7 @@
@param scene La scene sur laquelle figure ce cercle
*/
PartCircle::PartCircle(QETElementEditor *editor, QGraphicsItem *parent, QGraphicsScene *scene) : QGraphicsEllipseItem(parent, scene), CustomElementGraphicPart(editor) {
- setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
+ setFlags(QGraphicsItem::ItemIsSelectable);
#if QT_VERSION >= 0x040600
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
#endif
Modified: trunk/sources/editor/partellipse.cpp
===================================================================
--- trunk/sources/editor/partellipse.cpp 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/partellipse.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -24,7 +24,7 @@
@param scene La scene sur laquelle figure cette ellipse
*/
PartEllipse::PartEllipse(QETElementEditor *editor, QGraphicsItem *parent, QGraphicsScene *scene) : QGraphicsEllipseItem(parent, scene), CustomElementGraphicPart(editor) {
- setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
+ setFlags(QGraphicsItem::ItemIsSelectable);
#if QT_VERSION >= 0x040600
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
#endif
Modified: trunk/sources/editor/partline.cpp
===================================================================
--- trunk/sources/editor/partline.cpp 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/partline.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -32,7 +32,7 @@
second_end(QET::None),
second_length(1.5)
{
- setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
+ setFlags(QGraphicsItem::ItemIsSelectable);
#if QT_VERSION >= 0x040600
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
#endif
Modified: trunk/sources/editor/partpolygon.cpp
===================================================================
--- trunk/sources/editor/partpolygon.cpp 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/partpolygon.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -29,7 +29,7 @@
CustomElementGraphicPart(editor),
closed(false)
{
- setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
+ setFlags(QGraphicsItem::ItemIsSelectable);
#if QT_VERSION >= 0x040600
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
#endif
Modified: trunk/sources/editor/partrectangle.cpp
===================================================================
--- trunk/sources/editor/partrectangle.cpp 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/partrectangle.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -24,7 +24,7 @@
@param scene La scene sur laquelle figure ce rectangle
*/
PartRectangle::PartRectangle(QETElementEditor *editor, QGraphicsItem *parent, QGraphicsScene *scene) : QGraphicsRectItem(parent, scene), CustomElementGraphicPart(editor) {
- setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
+ setFlags(QGraphicsItem::ItemIsSelectable);
#if QT_VERSION >= 0x040600
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
#endif
Modified: trunk/sources/editor/partterminal.cpp
===================================================================
--- trunk/sources/editor/partterminal.cpp 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/partterminal.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -30,7 +30,7 @@
_orientation(QET::North)
{
updateSecondPoint();
- setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
+ setFlags(QGraphicsItem::ItemIsSelectable);
#if QT_VERSION >= 0x040600
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
#endif
Modified: trunk/sources/editor/parttext.cpp
===================================================================
--- trunk/sources/editor/parttext.cpp 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/parttext.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -18,6 +18,7 @@
#include "parttext.h"
#include "texteditor.h"
#include "editorcommands.h"
+#include "elementprimitivedecorator.h"
#include "elementscene.h"
#include "qetapp.h"
@@ -29,17 +30,21 @@
*/
PartText::PartText(QETElementEditor *editor, QGraphicsItem *parent, ElementScene *scene) :
QGraphicsTextItem(parent, scene),
- CustomElementPart(editor)
+ CustomElementPart(editor),
+ previous_text(),
+ decorator_(0)
{
#if QT_VERSION >= 0x040500
document() -> setDocumentMargin(1.0);
#endif
setDefaultTextColor(Qt::black);
setFont(QETApp::diagramTextsFont());
- setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
+ real_font_size_ = font().pointSize();
+ setFlags(QGraphicsItem::ItemIsSelectable);
#if QT_VERSION >= 0x040600
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
#endif
+ setAcceptHoverEvents(true);
setDefaultTextColor(Qt::black);
setPlainText(QObject::tr("T", "default text when adding a text in the element editor"));
@@ -63,7 +68,7 @@
if (!ok || font_size < 1) font_size = 20;
setBlack(xml_element.attribute("color") != "white");
- setFont(QETApp::diagramTextsFont(font_size));
+ setProperty("size" , font_size);
setPlainText(xml_element.attribute("text"));
qreal default_rotation_angle = 0.0;
@@ -153,31 +158,24 @@
}
/**
- Permet a l'element texte de redevenir deplacable a la fin de l'edition de texte
- @param e Le QFocusEvent decrivant la perte de focus
+ @reimp QGraphicsItem::focusInEvent(QFocusEvent *)
+ @param e The QFocusEvent object describing the focus gain.
+ Start text edition when the item gains focus.
*/
+void PartText::focusInEvent(QFocusEvent *e) {
+ startEdition();
+ QGraphicsTextItem::focusInEvent(e);
+}
+
+
+/**
+ @reimp QGraphicsItem::focusOutEvent(QFocusEvent *)
+ @param e The QFocusEvent object describing the focus loss.
+ End text edition when the item loses focus.
+*/
void PartText::focusOutEvent(QFocusEvent *e) {
QGraphicsTextItem::focusOutEvent(e);
- if (previous_text != toPlainText()) {
- undoStack().push(
- new ChangePartCommand(
- TextEditor::tr("contenu") + " " + name(),
- this,
- "text",
- previous_text,
- toPlainText()
- )
- );
- previous_text = toPlainText();
- }
-
- // deselectionne le texte
- QTextCursor qtc = textCursor();
- qtc.clearSelection();
- setTextCursor(qtc);
-
- setTextInteractionFlags(Qt::NoTextInteraction);
- setFlag(QGraphicsItem::ItemIsFocusable, false);
+ endEdition();
}
/**
@@ -185,11 +183,10 @@
@param e Le QGraphicsSceneMouseEvent qui decrit le double-clic
*/
void PartText::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *e) {
- setFlag(QGraphicsItem::ItemIsFocusable, true);
- setTextInteractionFlags(Qt::TextEditorInteraction);
- previous_text = toPlainText();
QGraphicsTextItem::mouseDoubleClickEvent(e);
- setFocus(Qt::MouseFocusReason);
+ if (e -> button() == Qt::LeftButton) {
+ setEditable(true);
+ }
}
/**
@@ -212,6 +209,11 @@
} else if (property == "size") {
if (!value.canConvert(QVariant::Int)) return;
setFont(QETApp::diagramTextsFont(value.toInt()));
+ real_font_size_ = value.toInt();
+ } else if (property == "real_size") {
+ if (!value.canConvert(QVariant::Double)) return;
+ setFont(QETApp::diagramTextsFont(value.toInt()));
+ real_font_size_ = value.toDouble();
} else if (property == "text") {
setPlainText(value.toString());
} else if (property == "rotation angle") {
@@ -241,6 +243,8 @@
return(pos().y());
} else if (property == "size") {
return(font().pointSize());
+ } else if (property == "real_size") {
+ return(real_font_size_);
} else if (property == "text") {
return(toPlainText());
} else if (property == "rotation angle") {
@@ -292,7 +296,7 @@
void PartText::startUserTransformation(const QRectF &rect) {
Q_UNUSED(rect)
saved_point_ = pos(); // scene coordinates, no need to mapFromScene()
- saved_font_size_ = font().pointSize();
+ saved_font_size_ = real_font_size_;
}
/**
@@ -303,12 +307,10 @@
QPointF new_pos = mapPoints(initial_selection_rect, new_selection_rect, QList<QPointF>() << saved_point_).first();
setPos(new_pos);
- // adjust the font size following the smallest scale factor
- qreal sx = new_selection_rect.width() / initial_selection_rect.width();
+ // adjust the font size following the vertical scale factor
qreal sy = new_selection_rect.height() / initial_selection_rect.height();
- qreal smallest_scale_factor = sx > sy ? sy : sx;
- qreal new_font_size = saved_font_size_ * smallest_scale_factor;
- setProperty("size", qMax(1, qRound(new_font_size)));
+ qreal new_font_size = saved_font_size_ * sy;
+ setProperty("real_size", qMax(1, qRound(new_font_size)));
}
/**
@@ -318,8 +320,13 @@
@param widget Widget sur lequel on dessine (facultatif)
*/
void PartText::paint(QPainter *painter, const QStyleOptionGraphicsItem *qsogi, QWidget *widget) {
- QGraphicsTextItem::paint(painter, qsogi, widget);
+ // According to the source code of QGraphicsTextItem::paint(), this should
+ // avoid the drawing of the dashed rectangle around the text.
+ QStyleOptionGraphicsItem our_qsogi(*qsogi);
+ our_qsogi.state = QStyle::State_None;
+ QGraphicsTextItem::paint(painter, &our_qsogi, widget);
+
#ifdef QET_DEBUG_EDITOR_TEXTS
painter -> setPen(Qt::blue);
painter -> drawRect(boundingRect());
@@ -333,6 +340,124 @@
}
/**
+ Handle context menu events.
+ @param event Object describing the context menu event to handle.
+*/
+void PartText::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) {
+ Q_UNUSED(event);
+}
+
+/**
+ Handle events generated when the mouse hovers over the decorator.
+ @param event Object describing the hover event.
+*/
+void PartText::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
+ // force the cursor when the text is being edited
+ if (hasFocus() && decorator_) {
+ decorator_ -> setCursor(Qt::IBeamCursor);
+ }
+ QGraphicsTextItem::hoverMoveEvent(event);
+}
+
+/**
+ @reimp CustomElementPart::setDecorator(ElementPrimitiveDecorator *)
+ Install or remove a sceneEventFilter on the decorator and ensure it will
+ adjust itself while the text is being edited.
+*/
+void PartText::setDecorator(ElementPrimitiveDecorator *decorator) {
+ if (decorator) {
+ decorator -> installSceneEventFilter(this);
+ // ensure the decorator will adjust itself when the text area expands or shrinks
+ connect(document(), SIGNAL(contentsChanged()), decorator, SLOT(adjust()));
+ }
+ else {
+ decorator_ -> removeSceneEventFilter(this);
+ endEdition();
+ }
+ decorator_ = decorator;
+}
+
+/**
+ @reimp QGraphicsItem::sceneEventFilter(QGraphicsItem *, QEvent *).
+ Intercepts events before they reach the watched target, i.e. typically the
+ primitives decorator.
+ This method mainly works with key strokes (F2, escape) and double clicks to
+ begin or end text edition.
+*/
+bool PartText::sceneEventFilter(QGraphicsItem *watched, QEvent *event) {
+ if (watched != decorator_) return(false);
+
+ QPointF event_scene_pos = QET::graphicsSceneEventPos(event);
+ if (!event_scene_pos.isNull()) {
+ if (contains(mapFromScene(event_scene_pos))) {
+ if (hasFocus()) {
+ return sceneEvent(event); // manually deliver the event to this item
+ return(true); // prevent this event from being delivered to any item
+ } else {
+ if (event -> type() == QEvent::GraphicsSceneMouseDoubleClick) {
+ mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent *>(event));
+ }
+ }
+ }
+ }
+ else if (event -> type() == QEvent::KeyRelease || event -> type() == QEvent::KeyPress) {
+ // Intercept F2 and escape keystrokes to focus in and out
+ QKeyEvent *key_event = static_cast<QKeyEvent *>(event);
+ if (key_event -> key() == Qt::Key_F2) {
+ setEditable(true);
+ QTextCursor qtc = textCursor();
+ qtc.setPosition(qMax(0, document()->characterCount() - 1));
+ setTextCursor(qtc);
+ } else if (hasFocus() && key_event -> key() == Qt::Key_Escape) {
+ endEdition();
+ }
+ sceneEvent(event); // manually deliver the event to this item
+ return(true); // prevent this event from being delivered to any item
+ }
+ return(false);
+}
+
+/**
+ Accept the mouse \a event relayed by \a decorator if this text item has focus.
+*/
+bool PartText::singleItemPressEvent(ElementPrimitiveDecorator *decorator, QGraphicsSceneMouseEvent *event) {
+ Q_UNUSED(decorator)
+ Q_UNUSED(event)
+ return(hasFocus());
+}
+
+/**
+ Accept the mouse \a event relayed by \a decorator if this text item has focus.
+*/
+bool PartText::singleItemMoveEvent(ElementPrimitiveDecorator *decorator, QGraphicsSceneMouseEvent *event) {
+ Q_UNUSED(decorator)
+ Q_UNUSED(event)
+ return(hasFocus());
+}
+
+/**
+ Accept the mouse \a event relayed by \a decorator if this text item has focus.
+*/
+bool PartText::singleItemReleaseEvent(ElementPrimitiveDecorator *decorator, QGraphicsSceneMouseEvent *event) {
+ Q_UNUSED(decorator)
+ Q_UNUSED(event)
+ return(hasFocus());
+}
+
+/**
+ Accept the mouse \a event relayed by \a decorator if this text item has focus.
+*/
+bool PartText::singleItemDoubleClickEvent(ElementPrimitiveDecorator *decorator, QGraphicsSceneMouseEvent *event) {
+ Q_UNUSED(decorator)
+ // calling mouseDoubleClickEvent() will set this text item editable and grab keyboard focus
+ if (event -> button() == Qt::LeftButton) {
+ mouseDoubleClickEvent(event);
+ return(true);
+ }
+ return(false);
+}
+
+/**
Cette methode s'assure que la position du champ de texte est coherente
en repositionnant son origine (c-a-d le milieu du bord gauche du champ de
texte) a la position originale. Cela est notamment utile lorsque le champ
@@ -349,6 +474,59 @@
setTransformOriginPoint(origin_offset);
}
+/**
+ @param editable Whether this text item should be interactively editable.
+*/
+void PartText::setEditable(bool editable) {
+ if (editable) {
+ setFlag(QGraphicsItem::ItemIsFocusable, true);
+ setTextInteractionFlags(Qt::TextEditorInteraction);
+ setFocus(Qt::MouseFocusReason);
+ }
+ else {
+ setTextInteractionFlags(Qt::NoTextInteraction);
+ setFlag(QGraphicsItem::ItemIsFocusable, false);
+ }
+}
+
+/**
+ Start text edition by storing the former value of the text.
+*/
+void PartText::startEdition() {
+ // !previous_text.isNull() means the text is being edited
+ previous_text = toPlainText();
+}
+
+/**
+ End text edition, potentially generating a ChangePartCommand if the text
+ has changed.
+*/
+void PartText::endEdition() {
+ if (!previous_text.isNull()) {
+ // the text was being edited
+ QString new_text = toPlainText();
+ if (previous_text != new_text) {
+ // the text was changed
+ ChangePartCommand *text_change = new ChangePartCommand(
+ TextEditor::tr("contenu") + " " + name(),
+ this,
+ "text",
+ previous_text,
+ new_text
+ );
+ previous_text = QString();
+ undoStack().push(text_change);
+ }
+ }
+
+ // deselectionne le texte
+ QTextCursor qtc = textCursor();
+ qtc.clearSelection();
+ setTextCursor(qtc);
+
+ setEditable(false);
+}
+
#ifdef QET_DEBUG_EDITOR_TEXTS
/**
Dessine deux petites fleches pour mettre un point en valeur
Modified: trunk/sources/editor/parttext.h
===================================================================
--- trunk/sources/editor/parttext.h 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/parttext.h 2013-02-08 22:05:15 UTC (rev 2027)
@@ -20,6 +20,7 @@
#include <QtGui>
#include "customelementpart.h"
class TextEditor;
+class ElementPrimitiveDecorator;
/**
This class represents an static text primitive which may be used to compose
the drawing of an electrical element within the element editor.
@@ -59,10 +60,23 @@
virtual void handleUserTransformation(const QRectF &, const QRectF &);
virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget * = 0 );
+ virtual void setDecorator(ElementPrimitiveDecorator *);
+ virtual bool singleItemPressEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *);
+ virtual bool singleItemMoveEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *);
+ virtual bool singleItemReleaseEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *);
+ virtual bool singleItemDoubleClickEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *);
+
public slots:
void adjustItemPosition(int = 0);
+ void setEditable(bool);
+ void startEdition();
+ void endEdition();
protected:
+ virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent *);
+ virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *);
+ virtual bool sceneEventFilter(QGraphicsItem *, QEvent *);
+ virtual void focusInEvent(QFocusEvent *);
virtual void focusOutEvent(QFocusEvent *);
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *);
virtual QVariant itemChange(GraphicsItemChange, const QVariant &);
@@ -74,7 +88,9 @@
void drawPoint(QPainter *, const QPointF &);
#endif
QString previous_text;
+ qreal real_font_size_;
QPointF saved_point_;
- int saved_font_size_;
+ qreal saved_font_size_;
+ QGraphicsItem *decorator_;
};
#endif
Modified: trunk/sources/editor/parttextfield.cpp
===================================================================
--- trunk/sources/editor/parttextfield.cpp 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/parttextfield.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -18,6 +18,7 @@
#include "parttextfield.h"
#include "textfieldeditor.h"
#include "editorcommands.h"
+#include "elementprimitivedecorator.h"
#include "qetapp.h"
/**
@@ -29,14 +30,18 @@
PartTextField::PartTextField(QETElementEditor *editor, QGraphicsItem *parent, QGraphicsScene *scene) :
QGraphicsTextItem(parent, scene),
CustomElementPart(editor),
- follow_parent_rotations(true)
+ follow_parent_rotations(true),
+ previous_text(),
+ decorator_(0)
{
setDefaultTextColor(Qt::black);
setFont(QETApp::diagramTextsFont());
- setFlags(QGraphicsItem::ItemIsMovable | QGraphicsItem::ItemIsSelectable);
+ real_font_size_ = font().pointSize();
+ setFlags(QGraphicsItem::ItemIsSelectable);
#if QT_VERSION >= 0x040600
setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
#endif
+ setAcceptHoverEvents(true);
setPlainText(QObject::tr("_", "default text when adding a textfield in the element editor"));
adjustItemPosition(1);
@@ -58,7 +63,7 @@
int font_size = xml_element.attribute("size").toInt(&ok);
if (!ok || font_size < 1) font_size = 20;
- setFont(QETApp::diagramTextsFont(font_size));
+ setProperty("size", font_size);
setPlainText(xml_element.attribute("text"));
qreal default_rotation_angle = 0.0;
@@ -135,31 +140,82 @@
}
/**
+ Handle context menu events.
+ @param event Object describing the context menu event to handle.
+*/
+void PartTextField::contextMenuEvent(QGraphicsSceneContextMenuEvent *event) {
+ Q_UNUSED(event);
+}
+
+/**
+ Handle events generated when the mouse hovers over the decorator.
+ @param event Object describing the hover event.
+*/
+void PartTextField::hoverMoveEvent(QGraphicsSceneHoverEvent *event) {
+ // force the cursor when the text is being edited
+ if (hasFocus() && decorator_) {
+ decorator_ -> setCursor(Qt::IBeamCursor);
+ }
+ QGraphicsTextItem::hoverMoveEvent(event);
+}
+
+/**
+ @reimp QGraphicsItem::sceneEventFilter(QGraphicsItem *, QEvent *).
+ Intercepts events before they reach the watched target, i.e. typically the
+ primitives decorator.
+ This method mainly works with key strokes (F2, escape) and double clicks to
+ begin or end text edition.
+*/
+bool PartTextField::sceneEventFilter(QGraphicsItem *watched, QEvent *event) {
+ if (watched != decorator_) return(false);
+
+ QPointF event_scene_pos = QET::graphicsSceneEventPos(event);
+ if (!event_scene_pos.isNull()) {
+ if (contains(mapFromScene(event_scene_pos))) {
+ if (hasFocus()) {
+ return sceneEvent(event); // manually deliver the event to this item
+ return(true); // prevent this event from being delivered to any item
+ } else {
+ if (event -> type() == QEvent::GraphicsSceneMouseDoubleClick) {
+ mouseDoubleClickEvent(static_cast<QGraphicsSceneMouseEvent *>(event));
+ }
+ }
+ }
+ }
+ else if (event -> type() == QEvent::KeyRelease || event -> type() == QEvent::KeyPress) {
+ // Intercept F2 and escape keystrokes to focus in and out
+ QKeyEvent *key_event = static_cast<QKeyEvent *>(event);
+ if (key_event -> key() == Qt::Key_F2) {
+ setEditable(true);
+ QTextCursor qtc = textCursor();
+ qtc.setPosition(qMax(0, document()->characterCount() - 1));
+ setTextCursor(qtc);
+ } else if (hasFocus() && key_event -> key() == Qt::Key_Escape) {
+ endEdition();
+ }
+ sceneEvent(event); // manually deliver the event to this item
+ return(true); // prevent this event from being delivered to any item
+ }
+ return(false);
+}
+
+/*
+ @reimp QGraphicsItem::focusInEvent(QFocusEvent *)
+ @param e The QFocusEvent object describing the focus gain.
+ Start text edition when the item gains focus.
+*/
+void PartTextField::focusInEvent(QFocusEvent *e) {
+ startEdition();
+ QGraphicsTextItem::focusInEvent(e);
+}
+
+/**
Permet a l'element texte de redevenir deplacable a la fin de l'edition de texte
@param e Le QFocusEvent decrivant la perte de focus
*/
void PartTextField::focusOutEvent(QFocusEvent *e) {
QGraphicsTextItem::focusOutEvent(e);
- if (previous_text != toPlainText()) {
- undoStack().push(
- new ChangePartCommand(
- TextFieldEditor::tr("contenu") + " " + name(),
- this,
- "text",
- previous_text,
- toPlainText()
- )
- );
- previous_text = toPlainText();
- }
-
- // deselectionne le texte
- QTextCursor qtc = textCursor();
- qtc.clearSelection();
- setTextCursor(qtc);
-
- setTextInteractionFlags(Qt::NoTextInteraction);
- setFlag(QGraphicsItem::ItemIsFocusable, false);
+ endEdition();
}
/**
@@ -167,11 +223,10 @@
@param e Le QGraphicsSceneMouseEvent qui decrit le double-clic
*/
void PartTextField::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *e) {
- setFlag(QGraphicsItem::ItemIsFocusable, true);
- setTextInteractionFlags(Qt::TextEditorInteraction);
- previous_text = toPlainText();
QGraphicsTextItem::mouseDoubleClickEvent(e);
- setFocus(Qt::MouseFocusReason);
+ if (e -> button() == Qt::LeftButton) {
+ setEditable(true);
+ }
}
/**
@@ -194,6 +249,11 @@
} else if (property == "size") {
if (!value.canConvert(QVariant::Int)) return;
setFont(QETApp::diagramTextsFont(value.toInt()));
+ real_font_size_ = value.toInt();
+ } else if (property == "real_size") {
+ if (!value.canConvert(QVariant::Double)) return;
+ setFont(QETApp::diagramTextsFont(value.toInt()));
+ real_font_size_ = value.toDouble();
} else if (property == "text") {
setPlainText(value.toString());
} else if (property == "rotation angle") {
@@ -223,6 +283,8 @@
return(pos().y());
} else if (property == "size") {
return(font().pointSize());
+ } else if (property == "real_size") {
+ return(real_font_size_);
} else if (property == "text") {
return(toPlainText());
} else if (property == "rotation angle") {
@@ -275,7 +337,7 @@
void PartTextField::startUserTransformation(const QRectF &initial_selection_rect) {
Q_UNUSED(initial_selection_rect)
saved_point_ = pos(); // scene coordinates, no need to mapFromScene()
- saved_font_size_ = font().pointSize();
+ saved_font_size_ = real_font_size_;
}
/**
@@ -286,12 +348,10 @@
QPointF new_pos = mapPoints(initial_selection_rect, new_selection_rect, QList<QPointF>() << saved_point_).first();
setPos(new_pos);
- // adjust the font size following the smallest scale factor
- qreal sx = new_selection_rect.width() / initial_selection_rect.width();
+ // adjust the font size following the vertical scale factor
qreal sy = new_selection_rect.height() / initial_selection_rect.height();
- qreal smallest_scale_factor = sx > sy ? sy : sx;
- qreal new_font_size = saved_font_size_ * smallest_scale_factor;
- setProperty("size", qMax(1, qRound(new_font_size)));
+ qreal new_font_size = saved_font_size_ * sy;
+ setProperty("real_size", qMax(1, qRound(new_font_size)));
}
/**
Dessine le texte statique.
@@ -300,8 +360,12 @@
@param widget Widget sur lequel on dessine (facultatif)
*/
void PartTextField::paint(QPainter *painter, const QStyleOptionGraphicsItem *qsogi, QWidget *widget) {
- QGraphicsTextItem::paint(painter, qsogi, widget);
+ // According to the source code of QGraphicsTextItem::paint(), this should
+ // avoid the drawing of the dashed rectangle around the text.
+ QStyleOptionGraphicsItem our_qsogi(*qsogi);
+ our_qsogi.state = QStyle::State_None;
+ QGraphicsTextItem::paint(painter, &our_qsogi, widget);
#ifdef QET_DEBUG_EDITOR_TEXTS
painter -> setPen(Qt::blue);
painter -> drawRect(boundingRect());
@@ -315,6 +379,64 @@
}
/**
+ @reimp CustomElementPart::setDecorator(ElementPrimitiveDecorator *)
+ Install or remove a sceneEventFilter on the decorator and ensure it will
+ adjust itself while the text is being edited.
+*/
+void PartTextField::setDecorator(ElementPrimitiveDecorator *decorator) {
+ if (decorator) {
+ decorator -> installSceneEventFilter(this);
+ // ensure the decorator will adjust itself when the text area expands or shrinks
+ connect(document(), SIGNAL(contentsChanged()), decorator, SLOT(adjust()));
+ }
+ else {
+ decorator_ -> removeSceneEventFilter(this);
+ endEdition();
+ }
+ decorator_ = decorator;
+}
+
+/**
+ Accept the mouse \a event relayed by \a decorator if this text item has focus.
+*/
+bool PartTextField::singleItemPressEvent(ElementPrimitiveDecorator *decorator, QGraphicsSceneMouseEvent *event) {
+ Q_UNUSED(decorator)
+ Q_UNUSED(event)
+ return(hasFocus());
+}
+
+/**
+ Accept the mouse \a event relayed by \a decorator if this text item has focus.
+*/
+bool PartTextField::singleItemMoveEvent(ElementPrimitiveDecorator *decorator, QGraphicsSceneMouseEvent *event) {
+ Q_UNUSED(decorator)
+ Q_UNUSED(event)
+ return(hasFocus());
+}
+
+/**
+ Accept the mouse \a event relayed by \a decorator if this text item has focus.
+*/
+bool PartTextField::singleItemReleaseEvent(ElementPrimitiveDecorator *decorator, QGraphicsSceneMouseEvent *event) {
+ Q_UNUSED(decorator)
+ Q_UNUSED(event)
+ return(hasFocus());
+}
+
+/**
+ Accept the mouse \a event relayed by \a decorator if this text item has focus.
+*/
+bool PartTextField::singleItemDoubleClickEvent(ElementPrimitiveDecorator *decorator, QGraphicsSceneMouseEvent *event) {
+ Q_UNUSED(decorator)
+ // calling mouseDoubleClickEvent() will set this text item editable and grab keyboard focus
+ if (event -> button() == Qt::LeftButton) {
+ mouseDoubleClickEvent(event);
+ return(true);
+ }
+ return(false);
+}
+
+/**
Cette methode s'assure que la position du champ de texte est coherente
en repositionnant son origine (c-a-d le milieu du bord gauche du champ de
texte) a la position originale. Cela est notamment utile lorsque le champ
@@ -331,6 +453,59 @@
setTransformOriginPoint(0.0, origin_offset);
}
+/**
+ @param editable Whether this text item should be interactively editable.
+*/
+void PartTextField::setEditable(bool editable) {
+ if (editable) {
+ setFlag(QGraphicsItem::ItemIsFocusable, true);
+ setTextInteractionFlags(Qt::TextEditorInteraction);
+ setFocus(Qt::MouseFocusReason);
+ }
+ else {
+ setTextInteractionFlags(Qt::NoTextInteraction);
+ setFlag(QGraphicsItem::ItemIsFocusable, false);
+ }
+}
+
+/**
+ Start text edition by storing the former value of the text.
+*/
+void PartTextField::startEdition() {
+ // !previous_text.isNull() means the text is being edited
+ previous_text = toPlainText();
+}
+
+/**
+ End text edition, potentially generating a ChangePartCommand if the text
+ has changed.
+*/
+void PartTextField::endEdition() {
+ if (!previous_text.isNull()) {
+ // the text was being edited
+ QString new_text = toPlainText();
+ if (previous_text != new_text) {
+ // the text was changed
+ ChangePartCommand *text_change = new ChangePartCommand(
+ TextFieldEditor::tr("contenu") + " " + name(),
+ this,
+ "text",
+ previous_text,
+ new_text
+ );
+ previous_text = QString();
+ undoStack().push(text_change);
+ }
+ }
+
+ // deselectionne le texte
+ QTextCursor qtc = textCursor();
+ qtc.clearSelection();
+ setTextCursor(qtc);
+
+ setEditable(false);
+}
+
#ifdef QET_DEBUG_EDITOR_TEXTS
/**
Dessine deux petites fleches pour mettre un point en valeur
Modified: trunk/sources/editor/parttextfield.h
===================================================================
--- trunk/sources/editor/parttextfield.h 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/editor/parttextfield.h 2013-02-08 22:05:15 UTC (rev 2027)
@@ -21,6 +21,7 @@
#include "customelementpart.h"
class TextFieldEditor;
class QETElementEditor;
+class ElementPrimitiveDecorator;
/**
This class represents an editable text field which may be used to compose the
drawing of an electrical element within the element editor. Users may specify
@@ -65,10 +66,23 @@
virtual void handleUserTransformation(const QRectF &, const QRectF &);
virtual void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget * = 0 );
+ virtual void setDecorator(ElementPrimitiveDecorator *);
+ virtual bool singleItemPressEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *);
+ virtual bool singleItemMoveEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *);
+ virtual bool singleItemReleaseEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *);
+ virtual bool singleItemDoubleClickEvent(ElementPrimitiveDecorator *, QGraphicsSceneMouseEvent *);
+
public slots:
void adjustItemPosition(int = 0);
+ void setEditable(bool);
+ void startEdition();
+ void endEdition();
protected:
+ virtual void contextMenuEvent(QGraphicsSceneContextMenuEvent *);
+ virtual void hoverMoveEvent(QGraphicsSceneHoverEvent *);
+ virtual bool sceneEventFilter(QGraphicsItem *, QEvent *);
+ virtual void focusInEvent(QFocusEvent *);
virtual void focusOutEvent(QFocusEvent *);
virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *);
virtual QVariant itemChange(GraphicsItemChange, const QVariant &);
@@ -80,7 +94,9 @@
void drawPoint(QPainter *, const QPointF &);
#endif
QString previous_text;
+ qreal real_font_size_;
QPointF saved_point_;
- int saved_font_size_;
+ qreal saved_font_size_;
+ QGraphicsItem *decorator_;
};
#endif
Modified: trunk/sources/qet.cpp
===================================================================
--- trunk/sources/qet.cpp 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/qet.cpp 2013-02-08 22:05:15 UTC (rev 2027)
@@ -17,6 +17,7 @@
*/
#include "qet.h"
#include <limits>
+#include <QGraphicsSceneContextMenuEvent>
/**
Permet de convertir une chaine de caracteres ("n", "s", "e" ou "w")
@@ -577,3 +578,59 @@
return(true);
}
+
+/**
+ @return the scene position where \a event occurred, provided it is
+ QGraphicsScene-related event; otherwise, this function returns a null
+ QPointF.
+*/
+QPointF QET::graphicsSceneEventPos(QEvent *event) {
+ QPointF event_scene_pos;
+ if (event -> type() < QEvent::GraphicsSceneContextMenu) return(event_scene_pos);
+ if (event -> type() > QEvent::GraphicsSceneWheel) return(event_scene_pos);
+
+ switch (event -> type()) {
+ case QEvent::GraphicsSceneContextMenu: {
+ QGraphicsSceneContextMenuEvent *qgs_event = static_cast<QGraphicsSceneContextMenuEvent *>(event);
+ event_scene_pos = qgs_event -> scenePos();
+ break;
+ }
+ case QEvent::GraphicsSceneDragEnter:
+ case QEvent::GraphicsSceneDragLeave:
+ case QEvent::GraphicsSceneDragMove:
+ case QEvent::GraphicsSceneDrop: {
+ QGraphicsSceneDragDropEvent *qgs_event = static_cast<QGraphicsSceneDragDropEvent *>(event);
+ event_scene_pos = qgs_event -> scenePos();
+ break;
+ }
+ case QEvent::GraphicsSceneHelp: {
+ QGraphicsSceneHelpEvent *qgs_event = static_cast<QGraphicsSceneHelpEvent *>(event);
+ event_scene_pos = qgs_event -> scenePos();
+ break;
+ }
+
+ case QEvent::GraphicsSceneHoverEnter:
+ case QEvent::GraphicsSceneHoverLeave:
+ case QEvent::GraphicsSceneHoverMove: {
+ QGraphicsSceneHoverEvent *qgs_event = static_cast<QGraphicsSceneHoverEvent *>(event);
+ event_scene_pos = qgs_event -> scenePos();
+ break;
+ }
+ case QEvent::GraphicsSceneMouseDoubleClick:
+ case QEvent::GraphicsSceneMouseMove:
+ case QEvent::GraphicsSceneMousePress:
+ case QEvent::GraphicsSceneMouseRelease: {
+ QGraphicsSceneMouseEvent *qgs_event = static_cast<QGraphicsSceneMouseEvent *>(event);
+ event_scene_pos = qgs_event -> scenePos();
+ break;
+ }
+ case QEvent::GraphicsSceneWheel: {
+ QGraphicsSceneWheelEvent *qgs_event = static_cast<QGraphicsSceneWheelEvent *>(event);
+ event_scene_pos = qgs_event -> scenePos();
+ break;
+ }
+ default:
+ break;
+ }
+ return(event_scene_pos);
+}
Modified: trunk/sources/qet.h
===================================================================
--- trunk/sources/qet.h 2013-02-08 22:05:12 UTC (rev 2026)
+++ trunk/sources/qet.h 2013-02-08 22:05:15 UTC (rev 2027)
@@ -43,6 +43,22 @@
ToNorthWest
};
+ /// List areas related to some common operations
+ enum OperationAreas {
+ ChangeInnerPoints = -4,
+ RotateArea = -3,
+ MoveArea = -2,
+ NoOperation = -1,
+ ResizeFromTopLeftCorner = 0,
+ ResizeFromTopCenterCorner = 1,
+ ResizeFromTopRightCorner = 2,
+ ResizeFromMiddleLeftCorner = 3,
+ ResizeFromMiddleRightCorner = 4,
+ ResizeFromBottomLeftCorner = 5,
+ ResizeFromBottomCenterCorner = 6,
+ ResizeFromBottomRightCorner = 7
+ };
+
/// Known kinds of conductor segments
enum ConductorSegmentType {
Horizontal = 1, ///< Horizontal segment
@@ -149,5 +165,6 @@
bool compareCanonicalFilePaths(const QString &, const QString &);
QString titleBlockColumnLengthToString(const TitleBlockColumnLength &);
bool writeXmlFile(QDomDocument &, const QString &, QString * = 0);
+ QPointF graphicsSceneEventPos(QEvent *);
}
#endif