Unverified Commit e328e392 authored by Whitfield, Ross's avatar Whitfield, Ross Committed by GitHub
Browse files

Merge pull request #32535 from rosswhitfield/sans772_instrument-view-mask-rotation-feature

Add ability to rotate Ellipse and Rectangle shapes in Instrument Viewer Widget
parents fc4be440 68ad7f64
......@@ -8,6 +8,8 @@ Mantid Workbench Changes
New and Improved
----------------
- Add ability to rotate Ellipse and Rectangle shapes in :ref:`InstrumentViewer`.
Bugfixes
--------
- Fixed arbitrary values not being accepted as the "Start Time" in StartLiveDataDialog.
......
......@@ -150,8 +150,8 @@ private:
/// Load masks applied to the view but not to the workspace
void loadMaskViewFromProject(const std::string &name);
/// Run the LoadMask algorithm to get a MaskWorkspace
std::shared_ptr<Mantid::API::MatrixWorkspace>
loadMask(const std::string &fileName);
std::shared_ptr<Mantid::API::MatrixWorkspace> loadMask(const std::string &fileName);
bool isRotationSupported();
protected:
/// Is it used?
......@@ -214,6 +214,7 @@ protected:
QtProperty *m_top;
QtProperty *m_right;
QtProperty *m_bottom;
QtProperty *m_rotation;
QMap<QtProperty *, QString> m_doublePropertyMap;
QMap<QString, QtProperty *> m_pointPropertyMap;
......
......@@ -171,6 +171,18 @@ public:
m_maskShapes.setCurrentBoundingRect(rect);
}
/// Return bounding rotation of the currently selected shape in the "original"
/// coord system.
/// It doesn't depend on the zooming of the surface
double getCurrentBoundingRotation() const { return m_maskShapes.getCurrentBoundingRotation(); }
/// Set new bounding rect of the currently selected shape in the "original"
/// coord system.
/// This method resizes the shape to fit into the new rectangle.
void setCurrentBoundingRotation(const double rotation) { m_maskShapes.setCurrentBoundingRotation(rotation); }
std::string getCurrentShapeType() const { return m_maskShapes.getCurrentShapeType(); }
/// Initialize interactive shape creation.
/// @param type :: Type of the shape. For available types see code of
/// Shape2DCollection::createShape(const QString& type,int x,int y) const
......
......@@ -88,6 +88,10 @@ public:
double dy2);
/// Set new bounding rect.
virtual void setBoundingRect(const RectF &rect);
/// Return the bounding rotation of the shape.
virtual double getBoundingRotation() const { return m_boundingRotation; }
/// Set new bounding rotation
virtual void setBoundingRotation(const double rotation) { m_boundingRotation = rotation; };
/// will the shape be selected if clicked at a point? By default return false.
virtual bool selectAt(const QPointF & /*unused*/) const { return false; }
/// is a point inside the shape (closed line)? By default return false.
......@@ -179,6 +183,7 @@ protected:
static const size_t NCommonCP;
static const qreal sizeCP;
RectF m_boundingRect;
double m_boundingRotation = 0.0;
QColor m_color;
QColor m_fill_color;
bool m_scalable; ///< shape can be scaled when zoomed
......
......@@ -71,6 +71,9 @@ public:
RectF getCurrentBoundingRect() const;
void setCurrentBoundingRect(const RectF &rect);
double getCurrentBoundingRotation() const;
void setCurrentBoundingRotation(const double rotation);
std::string getCurrentShapeType() const;
// double properties
QStringList getCurrentDoubleNames() const;
double getCurrentDouble(const QString &prop) const;
......@@ -96,8 +99,7 @@ public:
/// Save shape collection to a Table workspace
void saveToTableWorkspace();
/// Load shape collectio from a Table workspace
void
loadFromTableWorkspace(const Mantid::API::ITableWorkspace_const_sptr &ws);
void loadFromTableWorkspace(const Mantid::API::ITableWorkspace_const_sptr &ws);
/// Load settings for the shape 2D collection from a project file
virtual void loadFromProject(const std::string &lines);
/// Save settings for the shape 2D collection to a project file
......@@ -114,10 +116,8 @@ signals:
void cleared();
public slots:
void addShape(const QString &type, int x, int y, const QColor &borderColor,
const QColor &fillColor);
void addFreeShape(const QPolygonF & /*poly*/, const QColor &borderColor,
const QColor &fillColor);
void addShape(const QString &type, int x, int y, const QColor &borderColor, const QColor &fillColor);
void addFreeShape(const QPolygonF & /*poly*/, const QColor &borderColor, const QColor &fillColor);
void deselectAll();
void moveRightBottomTo(int /*x*/, int /*y*/);
void selectShapeOrControlPointAt(int x, int y);
......@@ -149,19 +149,16 @@ protected:
QList<Shape2D *> getSelectedShapes() const { return m_selectedShapes; }
QList<Shape2D *> m_shapes;
mutable RectF
m_surfaceRect; ///< original surface window in "real" coordinates
mutable RectF m_surfaceRect; ///< original surface window in "real" coordinates
mutable double m_wx, m_wy;
mutable int m_h; ///< original screen viewport height
mutable QRect m_viewport; ///< current screen viewport
mutable QTransform m_transform; ///< current transform
Shape2D *m_currentShape; ///< shape selected to edit (change size/shape)
size_t m_currentCP; ///< control point of m_currentShape selected to edit
QList<Shape2D *>
m_selectedShapes; ///< A list of selected shapes (can be moved or deleted)
QList<Shape2D *>
m_copiedShapes; ///< A list of shapes to be pasted if requiered
Shape2D *m_currentShape; ///< shape selected to edit (change size/shape)
size_t m_currentCP; ///< control point of m_currentShape selected to edit
QList<Shape2D *> m_selectedShapes; ///< A list of selected shapes (can be moved or deleted)
QList<Shape2D *> m_copiedShapes; ///< A list of shapes to be pasted if requiered
bool m_overridingCursor;
friend class InstrumentWidgetEncoder;
friend class InstrumentWidgetDecoder;
......
......@@ -307,8 +307,11 @@ InstrumentWidgetDecoder::decodeEllipse(const QMap<QString, QVariant> &map) {
const auto radius2 = map[QString("radius2")].toDouble();
const auto x = map[QString("x")].toDouble();
const auto y = map[QString("y")].toDouble();
const auto rot = map[QString("rotation")].toDouble();
return new Shape2DEllipse(QPointF(x, y), radius1, radius2);
auto shape = new Shape2DEllipse(QPointF(x, y), radius1, radius2);
shape->setBoundingRotation(rot);
return shape;
}
Shape2D *
......@@ -317,10 +320,13 @@ InstrumentWidgetDecoder::decodeRectangle(const QMap<QString, QVariant> &map) {
const auto y0 = map[QString("y0")].toDouble();
const auto x1 = map[QString("x1")].toDouble();
const auto y1 = map[QString("y1")].toDouble();
const auto rot = map[QString("rotation")].toDouble();
const QPointF point1(x0, y0);
const QPointF point2(x1, y1);
return new Shape2DRectangle(point1, point2);
auto shape = new Shape2DRectangle(point1, point2);
shape->setBoundingRotation(rot);
return shape;
}
Shape2D *
......
......@@ -368,6 +368,7 @@ InstrumentWidgetEncoder::encodeEllipse(const Shape2DEllipse *obj) {
const double radius1 = obj->getDouble("radius1");
const double radius2 = obj->getDouble("radius2");
const auto centre = obj->getPoint("centre");
const auto rot = obj->getBoundingRotation();
QMap<QString, QVariant> map;
......@@ -375,6 +376,7 @@ InstrumentWidgetEncoder::encodeEllipse(const Shape2DEllipse *obj) {
map.insert(QString("radius2"), QVariant(radius2));
map.insert(QString("x"), QVariant(centre.x()));
map.insert(QString("y"), QVariant(centre.y()));
map.insert(QString("rotation"), QVariant(rot));
return map;
}
......@@ -385,6 +387,7 @@ InstrumentWidgetEncoder::encodeRectangle(const Shape2DRectangle *obj) {
const auto x1 = obj->m_boundingRect.x1();
const auto y0 = obj->m_boundingRect.y0();
const auto y1 = obj->m_boundingRect.y1();
const auto rot = obj->getBoundingRotation();
QMap<QString, QVariant> map;
......@@ -392,6 +395,7 @@ InstrumentWidgetEncoder::encodeRectangle(const Shape2DRectangle *obj) {
map.insert(QString("y0"), QVariant(y0));
map.insert(QString("x1"), QVariant(x1));
map.insert(QString("y1"), QVariant(y1));
map.insert(QString("rotation"), QVariant(rot));
return map;
}
......
......@@ -74,11 +74,9 @@ using Mantid::API::AlgorithmManager;
namespace MantidQt {
namespace MantidWidgets {
InstrumentWidgetMaskTab::InstrumentWidgetMaskTab(InstrumentWidget *instrWidget)
: InstrumentWidgetTab(instrWidget), m_activity(Select),
m_hasMaskToApply(false), m_maskBins(false), m_userEditing(true),
m_groupManager(nullptr), m_stringManager(nullptr),
m_doubleManager(nullptr), m_browser(nullptr), m_left(nullptr),
m_top(nullptr), m_right(nullptr), m_bottom(nullptr) {
: InstrumentWidgetTab(instrWidget), m_activity(Select), m_hasMaskToApply(false), m_maskBins(false),
m_userEditing(true), m_groupManager(nullptr), m_stringManager(nullptr), m_doubleManager(nullptr),
m_browser(nullptr), m_left(nullptr), m_top(nullptr), m_right(nullptr), m_bottom(nullptr), m_rotation(nullptr) {
// main layout
QVBoxLayout *layout = new QVBoxLayout(this);
......@@ -628,10 +626,9 @@ void InstrumentWidgetMaskTab::shapeChanged() {
m_doubleManager->setValue(m_top, std::max(rect.y0(), rect.y1()));
m_doubleManager->setValue(m_right, std::max(rect.x0(), rect.x1()));
m_doubleManager->setValue(m_bottom, std::min(rect.y0(), rect.y1()));
for (QMap<QtProperty *, QString>::iterator it = m_doublePropertyMap.begin();
it != m_doublePropertyMap.end(); ++it) {
m_doubleManager->setValue(
it.key(), m_instrWidget->getSurface()->getCurrentDouble(it.value()));
for (QMap<QtProperty *, QString>::iterator it = m_doublePropertyMap.begin(); it != m_doublePropertyMap.end(); ++it) {
m_doubleManager->setValue(it.key(), m_instrWidget->getSurface()->getCurrentDouble(it.value()));
}
for (QMap<QString, QtProperty *>::iterator it = m_pointPropertyMap.begin();
it != m_pointPropertyMap.end(); ++it) {
......@@ -676,6 +673,7 @@ void InstrumentWidgetMaskTab::clearProperties() {
m_top = nullptr;
m_right = nullptr;
m_bottom = nullptr;
m_rotation = nullptr;
}
void InstrumentWidgetMaskTab::setProperties() {
......@@ -689,11 +687,17 @@ void InstrumentWidgetMaskTab::setProperties() {
m_top = addDoubleProperty("top");
m_right = addDoubleProperty("right");
m_bottom = addDoubleProperty("bottom");
boundingRectGroup->addSubProperty(m_left);
boundingRectGroup->addSubProperty(m_top);
boundingRectGroup->addSubProperty(m_right);
boundingRectGroup->addSubProperty(m_bottom);
if (isRotationSupported()) {
m_rotation = addDoubleProperty("rotation");
boundingRectGroup->addSubProperty(m_rotation);
}
// point properties
QStringList pointProperties =
m_instrWidget->getSurface()->getCurrentPointNames();
......@@ -718,6 +722,10 @@ void InstrumentWidgetMaskTab::setProperties() {
m_doublePropertyMap[prop] = name;
}
// rotation property
if (isRotationSupported())
m_doubleManager->setValue(m_rotation, m_instrWidget->getSurface()->getCurrentBoundingRotation());
shapeChanged();
}
......@@ -732,7 +740,7 @@ void InstrumentWidgetMaskTab::doubleChanged(QtProperty *prop) {
if (!m_userEditing)
return;
if (prop == m_left || prop == m_top || prop == m_right || prop == m_bottom) {
if (prop == m_left || prop == m_top || prop == m_right || prop == m_bottom || prop == m_rotation) {
m_userEditing = false;
double x0 = std::min(m_doubleManager->value(m_left),
m_doubleManager->value(m_right));
......@@ -746,6 +754,9 @@ void InstrumentWidgetMaskTab::doubleChanged(QtProperty *prop) {
QRectF rect(QPointF(x0, y0), QPointF(x1, y1));
m_instrWidget->getSurface()->setCurrentBoundingRect(RectF(rect));
if (isRotationSupported())
m_instrWidget->getSurface()->setCurrentBoundingRotation(m_doubleManager->value(m_rotation));
} else {
QString name = m_doublePropertyMap[prop];
if (!name.isEmpty()) {
......@@ -1548,5 +1559,10 @@ bool InstrumentWidgetMaskTab::saveMaskViewToProject(
return true;
}
bool InstrumentWidgetMaskTab::isRotationSupported() {
const auto shapeType = m_instrWidget->getSurface()->getCurrentShapeType();
return shapeType == "rectangle" || shapeType == "ellipse";
}
} // namespace MantidWidgets
} // namespace MantidQt
......@@ -48,8 +48,13 @@ void Shape2D::draw(QPainter &painter) const {
painter.setPen(QPen(m_color, 0));
this->drawShape(painter);
if (m_editing || m_selected) {
QRectF drawRect = m_boundingRect.translated(-m_boundingRect.center()).toQRectF();
painter.save();
painter.rotate(m_boundingRotation);
painter.translate(QTransform().rotate(-m_boundingRotation).map(m_boundingRect.center()));
painter.setPen(QPen(QColor(255, 255, 255, 100), 0));
painter.drawRect(m_boundingRect.toQRectF());
painter.drawRect(drawRect);
painter.restore();
size_t np = NCommonCP;
double rsize = 2;
int alpha = 100;
......@@ -91,7 +96,8 @@ QPointF Shape2D::getControlPoint(size_t i) const {
}
if (i < 4)
return m_boundingRect.vertex(i);
return QTransform().rotate(m_boundingRotation).map(m_boundingRect.vertex(i) - m_boundingRect.center()) +
m_boundingRect.center();
return getShapeControlPoint(i - NCommonCP);
}
......@@ -102,7 +108,8 @@ void Shape2D::setControlPoint(size_t i, const QPointF &pos) {
}
if (i < 4) {
m_boundingRect.setVertex(i, pos);
m_boundingRect.setVertex(i, QTransform().rotate(-m_boundingRotation).map(pos - m_boundingRect.center()) +
m_boundingRect.center());
refit();
}
......@@ -159,7 +166,8 @@ void Shape2D::setBoundingRect(const RectF &rect) {
* @param p :: Point to check.
*/
bool Shape2D::isMasked(const QPointF &p) const {
return m_fill_color != QColor() && contains(p);
return m_fill_color != QColor() &&
contains(QTransform().rotate(-m_boundingRotation).map(p - m_boundingRect.center()) + m_boundingRect.center());
}
/** Load shape 2D state from a Mantid project file
......@@ -262,13 +270,17 @@ Shape2DEllipse::Shape2DEllipse(const QPointF &center, double radius1,
}
void Shape2DEllipse::drawShape(QPainter &painter) const {
QRectF drawRect = m_boundingRect.toQRectF();
QRectF drawRect = m_boundingRect.translated(-m_boundingRect.center()).toQRectF();
painter.save();
painter.rotate(m_boundingRotation);
painter.translate(QTransform().rotate(-m_boundingRotation).map(m_boundingRect.center()));
painter.drawEllipse(drawRect);
if (m_fill_color != QColor()) {
QPainterPath path;
path.addEllipse(drawRect);
painter.fillPath(path, m_fill_color);
}
painter.restore();
}
void Shape2DEllipse::addToPath(QPainterPath &path) const {
......@@ -277,7 +289,8 @@ void Shape2DEllipse::addToPath(QPainterPath &path) const {
bool Shape2DEllipse::selectAt(const QPointF &p) const {
if (m_fill_color != QColor()) { // filled ellipse
return contains(p);
return contains(QTransform().rotate(-m_boundingRotation).map(p - m_boundingRect.center()) +
m_boundingRect.center());
}
double a = m_boundingRect.xSpan() / 2;
......@@ -395,7 +408,8 @@ Shape2DRectangle::Shape2DRectangle(const QPointF &p0, const QSizeF &size) {
bool Shape2DRectangle::selectAt(const QPointF &p) const {
if (m_fill_color != QColor()) { // filled rectangle
return contains(p);
return contains(QTransform().rotate(-m_boundingRotation).map(p - m_boundingRect.center()) +
m_boundingRect.center());
}
RectF outer(m_boundingRect);
......@@ -406,13 +420,17 @@ bool Shape2DRectangle::selectAt(const QPointF &p) const {
}
void Shape2DRectangle::drawShape(QPainter &painter) const {
QRectF drawRect = m_boundingRect.toQRectF();
QRectF drawRect = m_boundingRect.translated(-m_boundingRect.center()).toQRectF();
painter.save();
painter.rotate(m_boundingRotation);
painter.translate(QTransform().rotate(-m_boundingRotation).map(m_boundingRect.center()));
painter.drawRect(drawRect);
if (m_fill_color != QColor()) {
QPainterPath path;
path.addRect(drawRect);
painter.fillPath(path, m_fill_color);
}
painter.restore();
}
void Shape2DRectangle::addToPath(QPainterPath &path) const {
......
......@@ -588,6 +588,13 @@ void Shape2DCollection::clear() {
emit shapesDeselected();
}
std::string Shape2DCollection::getCurrentShapeType() const {
if (m_currentShape) {
return m_currentShape->type();
}
return "none";
}
QStringList Shape2DCollection::getCurrentDoubleNames() const {
if (m_currentShape) {
return m_currentShape->getDoubleNames();
......@@ -645,6 +652,20 @@ void Shape2DCollection::setCurrentBoundingRect(const RectF &rect) {
}
}
double Shape2DCollection::getCurrentBoundingRotation() const {
if (m_currentShape) {
return m_currentShape->getBoundingRotation();
}
return 0.0;
}
void Shape2DCollection::setCurrentBoundingRotation(const double rotation) {
if (m_currentShape) {
m_currentShape->setBoundingRotation(rotation);
emit shapeChanged();
}
}
bool Shape2DCollection::isMasked(double x, double y) const {
QPointF p(x, y);
foreach (Shape2D *shape, m_shapes) {
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment