Unverified Commit 9c446bf8 authored by DavidFair's avatar DavidFair Committed by GitHub
Browse files

Merge pull request #32514 from mantidproject/sans772_instrument-view-mask-rotation-feature

Add ability to rotate Ellipse and Rectangle shapes in Instrument Viewer Widget
parents 0817568b c5d6530d
......@@ -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.
......
......@@ -146,6 +146,7 @@ private:
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);
bool isRotationSupported();
protected:
/// Is it used?
......@@ -208,6 +209,7 @@ protected:
QtProperty *m_top;
QtProperty *m_right;
QtProperty *m_bottom;
QtProperty *m_rotation;
QMap<QtProperty *, QString> m_doublePropertyMap;
QMap<QString, QtProperty *> m_pointPropertyMap;
......
......@@ -167,6 +167,18 @@ public:
/// This method resizes the shape to fit into the new rectangle.
void setCurrentBoundingRect(const RectF &rect) { 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
......
......@@ -87,6 +87,10 @@ public:
virtual void adjustBoundingRect(double dx1, double dy1, double dx2, 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.
......@@ -175,6 +179,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;
......
......@@ -282,8 +282,11 @@ Shape2D *InstrumentWidgetDecoder::decodeEllipse(const QMap<QString, QVariant> &m
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 *InstrumentWidgetDecoder::decodeRectangle(const QMap<QString, QVariant> &map) {
......@@ -291,10 +294,13 @@ Shape2D *InstrumentWidgetDecoder::decodeRectangle(const QMap<QString, QVariant>
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 *InstrumentWidgetDecoder::decodeRing(const QMap<QString, QVariant> &map) {
......
......@@ -323,6 +323,7 @@ QMap<QString, QVariant> InstrumentWidgetEncoder::encodeEllipse(const Shape2DElli
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;
......@@ -330,6 +331,7 @@ QMap<QString, QVariant> InstrumentWidgetEncoder::encodeEllipse(const Shape2DElli
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;
}
......@@ -339,6 +341,7 @@ QMap<QString, QVariant> InstrumentWidgetEncoder::encodeRectangle(const Shape2DRe
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;
......@@ -346,6 +349,7 @@ QMap<QString, QVariant> InstrumentWidgetEncoder::encodeRectangle(const Shape2DRe
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;
}
......
......@@ -75,7 +75,7 @@ namespace MantidQt::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) {
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);
......@@ -564,6 +564,7 @@ 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()));
}
......@@ -609,6 +610,7 @@ void InstrumentWidgetMaskTab::clearProperties() {
m_top = nullptr;
m_right = nullptr;
m_bottom = nullptr;
m_rotation = nullptr;
}
void InstrumentWidgetMaskTab::setProperties() {
......@@ -622,11 +624,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();
foreach (QString name, pointProperties) {
......@@ -649,6 +657,10 @@ void InstrumentWidgetMaskTab::setProperties() {
m_doublePropertyMap[prop] = name;
}
// rotation property
if (isRotationSupported())
m_doubleManager->setValue(m_rotation, m_instrWidget->getSurface()->getCurrentBoundingRotation());
shapeChanged();
}
......@@ -661,7 +673,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));
double x1 = std::max(m_doubleManager->value(m_left), m_doubleManager->value(m_right));
......@@ -671,6 +683,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()) {
......@@ -1422,4 +1437,9 @@ bool InstrumentWidgetMaskTab::saveMaskViewToProject(const std::string &name, con
return true;
}
bool InstrumentWidgetMaskTab::isRotationSupported() {
const auto shapeType = m_instrWidget->getSurface()->getCurrentShapeType();
return shapeType == "rectangle" || shapeType == "ellipse";
}
} // namespace MantidQt::MantidWidgets
......@@ -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;
......@@ -89,7 +94,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);
}
......@@ -100,7 +106,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();
}
......@@ -155,7 +162,10 @@ 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); }
bool Shape2D::isMasked(const QPointF &p) const {
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
* @param lines :: lines from the project file to load state from
......@@ -254,20 +264,25 @@ Shape2DEllipse::Shape2DEllipse(const QPointF &center, double radius1, double rad
}
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 { path.addEllipse(m_boundingRect.toQRectF()); }
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;
......@@ -381,7 +396,8 @@ Shape2DRectangle::Shape2DRectangle(const QPointF &p0, const QSizeF &size) { m_bo
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);
......@@ -392,13 +408,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 { path.addRect(m_boundingRect.toQRectF()); }
......
......@@ -578,6 +578,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();
......@@ -634,6 +641,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) {
......
Markdown is supported
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