Shape2D.cpp 42.5 KB
Newer Older
1
2
3
// Mantid Repository : https://github.com/mantidproject/mantid
//
// Copyright © 2018 ISIS Rutherford Appleton Laboratory UKRI,
4
5
//   NScD Oak Ridge National Laboratory, European Spallation Source,
//   Institut Laue - Langevin & CSNS, Institute of High Energy Physics, CAS
6
// SPDX - License - Identifier: GPL - 3.0 +
Roman Tolchenov's avatar
Roman Tolchenov committed
7
#include "MantidQtWidgets/InstrumentView/Shape2D.h"
8
#include "MantidQtWidgets/Common/TSVSerialiser.h"
9

LamarMoore's avatar
LamarMoore committed
10
#include <QMouseEvent>
11
12
13
14
15
#include <QPainter>
#include <QWheelEvent>

#include <QLine>
#include <QMap>
16
#include <QVector2D>
17
18
19

#include <algorithm>
#include <cmath>
20
#include <iostream>
LamarMoore's avatar
LamarMoore committed
21
#include <stdexcept>
22

23
24
25
26
27
28
29
30
31
namespace MantidQt {
namespace MantidWidgets {

// number of control points common for all shapes
const size_t Shape2D::NCommonCP = 4;
// size (== width/2 == height/2) of each control point
const double Shape2D::sizeCP = 3;

/**
LamarMoore's avatar
LamarMoore committed
32
33
34
 * Set default border color to red and fill color to default Qt color
 * (==QColor()).
 */
35
36
37
38
39
Shape2D::Shape2D()
    : m_color(Qt::red), m_fill_color(QColor()), m_scalable(true),
      m_editing(false), m_selected(false), m_visible(true) {}

/**
LamarMoore's avatar
LamarMoore committed
40
41
42
43
44
 * Calls virtual drawShape() method to draw the actial shape.
 * Draws bounding rect and control points if the shape is selected.
 *
 * @param painter :: QPainter used for drawing.
 */
45
46
47
void Shape2D::draw(QPainter &painter) const {
  if (!m_visible)
    return;
48
  painter.setPen(QPen(m_color, 0));
49
50
  this->drawShape(painter);
  if (m_editing || m_selected) {
Whitfield, Ross's avatar
Whitfield, Ross committed
51
    QRectF drawRect = m_boundingRect.translated(-m_boundingRect.center()).toQRectF();
Whitfield, Ross's avatar
Whitfield, Ross committed
52
    painter.save();
Whitfield, Ross's avatar
Whitfield, Ross committed
53
    painter.rotate(m_boundingRotation);
Whitfield, Ross's avatar
Whitfield, Ross committed
54
    painter.translate(QTransform().rotate(-m_boundingRotation).map(m_boundingRect.center()));
55
    painter.setPen(QPen(QColor(255, 255, 255, 100), 0));
Whitfield, Ross's avatar
Whitfield, Ross committed
56
57
    painter.drawRect(drawRect);
    painter.restore();
58
59
60
61
62
63
64
65
66
67
68
69
70
71
    size_t np = NCommonCP;
    double rsize = 2;
    int alpha = 100;
    if (m_editing) {
      // if editing show all CP, make them bigger and opaque
      np = getNControlPoints();
      rsize = sizeCP;
      alpha = 255;
    }
    for (size_t i = 0; i < np; ++i) {
      QPointF p = painter.transform().map(getControlPoint(i));
      QRectF r(p - QPointF(rsize, rsize), p + QPointF(rsize, rsize));
      painter.save();
      painter.resetTransform();
72
      painter.fillRect(r, QColor(255, 255, 255, alpha));
73
      r.adjust(-1, -1, 0, 0);
74
      painter.setPen(QPen(QColor(0, 0, 0, alpha), 0));
75
76
77
78
79
80
81
      painter.drawRect(r);
      painter.restore();
    }
  }
}

/**
LamarMoore's avatar
LamarMoore committed
82
83
 * Return total number of control points for this shape.
 */
84
85
86
87
88
size_t Shape2D::getNControlPoints() const {
  return NCommonCP + this->getShapeNControlPoints();
}

/**
LamarMoore's avatar
LamarMoore committed
89
90
91
92
 * Return coordinates of i-th control point.
 *
 * @param i :: Index of a control point. 0 <= i < getNControlPoints().
 */
93
94
95
96
97
QPointF Shape2D::getControlPoint(size_t i) const {
  if (i >= getNControlPoints()) {
    throw std::range_error("Control point index is out of range");
  }

Whitfield, Ross's avatar
Whitfield, Ross committed
98
  if (i < 4)
Whitfield, Ross's avatar
Whitfield, Ross committed
99
100
    return QTransform().rotate(m_boundingRotation).map(m_boundingRect.vertex(i) - m_boundingRect.center()) +
           m_boundingRect.center();
101
102
103
104
105
106
107
108
109
110

  return getShapeControlPoint(i - NCommonCP);
}

void Shape2D::setControlPoint(size_t i, const QPointF &pos) {
  if (i >= getNControlPoints()) {
    throw std::range_error("Control point index is out of range");
  }

  if (i < 4) {
Whitfield, Ross's avatar
Whitfield, Ross committed
111
112
    m_boundingRect.setVertex(i, QTransform().rotate(-m_boundingRotation).map(pos - m_boundingRect.center()) +
                                    m_boundingRect.center());
113

114
115
116
117
118
119
120
121
122
    refit();
  }
  // else ?
  else
    setShapeControlPoint(i - NCommonCP, pos);
  resetBoundingRect();
}

/**
LamarMoore's avatar
LamarMoore committed
123
124
125
126
 * Move the shape.
 *
 * @param dp :: The shift vector.
 */
127
128
129
130
131
132
void Shape2D::moveBy(const QPointF &dp) {
  m_boundingRect.translate(dp);
  refit();
}

/**
LamarMoore's avatar
LamarMoore committed
133
134
135
 * Adjust the bound of the bounding rect. Calls virtual method refit()
 * to resize the shape in order to fit into the new bounds.
 */
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
void Shape2D::adjustBoundingRect(double dx1, double dy1, double dx2,
                                 double dy2) {
  double dwidth = dx2 - dx1;
  if (dwidth <= -m_boundingRect.xSpan()) {
    double mu = m_boundingRect.xSpan() / fabs(dwidth);
    dx1 *= mu;
    dx2 *= mu;
  }
  double dheight = dy2 - dy1;
  if (dheight <= -m_boundingRect.ySpan()) {
    double mu = m_boundingRect.ySpan() / fabs(dheight);
    dy1 *= mu;
    dy2 *= mu;
  }
  m_boundingRect.adjust(QPointF(dx1, dy1), QPointF(dx2, dy2));
  refit();
}

/**
LamarMoore's avatar
LamarMoore committed
155
156
157
 * Assign new bounding rect. Calls virtual method refit()
 * to resize the shape in order to fit into the new bounds.
 */
158
159
160
161
162
163
void Shape2D::setBoundingRect(const RectF &rect) {
  m_boundingRect = rect;
  refit();
}

/**
LamarMoore's avatar
LamarMoore committed
164
165
166
167
 * Check if the shape masks a point.
 *
 * @param p :: Point to check.
 */
168
bool Shape2D::isMasked(const QPointF &p) const {
Whitfield, Ross's avatar
Whitfield, Ross committed
169
170
  return m_fill_color != QColor() &&
         contains(QTransform().rotate(-m_boundingRotation).map(p - m_boundingRect.center()) + m_boundingRect.center());
171
172
}

173
174
175
176
/** Load shape 2D state from a Mantid project file
 * @param lines :: lines from the project file to load state from
 * @return a new shape2D with old state applied
 */
177
Shape2D *Shape2D::loadFromProject(const std::string &lines) {
178
  API::TSVSerialiser tsv(lines);
Samuel Jackson's avatar
Samuel Jackson committed
179

180
181
  if (!tsv.selectLine("Type"))
    return nullptr;
Samuel Jackson's avatar
Samuel Jackson committed
182

183
184
  std::string type;
  tsv >> type;
Samuel Jackson's avatar
Samuel Jackson committed
185

186
187
188
  Shape2D *shape = loadShape2DFromType(type, lines);
  if (!shape)
    return nullptr;
Samuel Jackson's avatar
Samuel Jackson committed
189

190
191
192
  if (tsv.selectLine("Properties")) {
    bool scalable, editing, selected, visible;
    tsv >> scalable >> editing >> selected >> visible;
Samuel Jackson's avatar
Samuel Jackson committed
193

194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
    shape->setScalable(scalable);
    shape->edit(editing);
    shape->setSelected(selected);
    shape->setVisible(visible);
  }

  if (tsv.selectLine("Color")) {
    QColor color;
    tsv >> color;
    shape->setColor(color);
  }

  if (tsv.selectLine("FillColor")) {
    QColor color;
    tsv >> color;
    shape->setFillColor(color);
Samuel Jackson's avatar
Samuel Jackson committed
210
  }
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235

  return shape;
}

/**
 * Instantiate different types of Shape2D from a string
 *
 * @param type :: a string representing the type e.g. ellipse
 * @param lines :: Mantid project lines to parse state from
 * @return a new instance of a Shape2D
 */
Shape2D *Shape2D::loadShape2DFromType(const std::string &type,
                                      const std::string &lines) {
  Shape2D *shape = nullptr;

  if (type == "ellipse") {
    shape = Shape2DEllipse::loadFromProject(lines);
  } else if (type == "rectangle") {
    shape = Shape2DRectangle::loadFromProject(lines);
  } else if (type == "ring") {
    shape = Shape2DRing::loadFromProject(lines);
  } else if (type == "free") {
    shape = Shape2DFree::loadFromProject(lines);
  }

Samuel Jackson's avatar
Samuel Jackson committed
236
237
238
  return shape;
}

239
240
241
/** Save the state of the shape 2D to a Mantid project file
 * @return a string representing the state of the shape 2D
 */
242
std::string Shape2D::saveToProject() const {
243
  API::TSVSerialiser tsv;
244
  bool props[]{m_scalable, m_editing, m_selected, m_visible};
Samuel Jackson's avatar
Samuel Jackson committed
245
246

  tsv.writeLine("Properties");
247
  for (auto prop : props) {
Samuel Jackson's avatar
Samuel Jackson committed
248
249
250
251
    tsv << prop;
  }

  auto color = getColor();
252
  tsv.writeLine("Color") << color;
Samuel Jackson's avatar
Samuel Jackson committed
253
254

  auto fillColor = getFillColor();
255
  tsv.writeLine("FillColor") << fillColor;
Samuel Jackson's avatar
Samuel Jackson committed
256
257
258
259

  return tsv.outputLines();
}

260
261
262
263
264
265
266
267
268
269
270
271
272
// --- Shape2DEllipse --- //

Shape2DEllipse::Shape2DEllipse(const QPointF &center, double radius1,
                               double radius2)
    : Shape2D() {
  if (radius2 == 0) {
    radius2 = radius1;
  }
  QPointF dr(radius1, radius2);
  m_boundingRect = RectF(center - dr, center + dr);
}

void Shape2DEllipse::drawShape(QPainter &painter) const {
Whitfield, Ross's avatar
Whitfield, Ross committed
273
274
275
276
  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()));
277
278
279
280
281
282
  painter.drawEllipse(drawRect);
  if (m_fill_color != QColor()) {
    QPainterPath path;
    path.addEllipse(drawRect);
    painter.fillPath(path, m_fill_color);
  }
Whitfield, Ross's avatar
Whitfield, Ross committed
283
  painter.restore();
284
285
286
287
288
289
290
291
}

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
292
293
    return contains(QTransform().rotate(-m_boundingRotation).map(p - m_boundingRect.center()) +
                    m_boundingRect.center());
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
  }

  double a = m_boundingRect.xSpan() / 2;
  if (a == 0.0)
    a = 1.0;
  double b = m_boundingRect.ySpan() / 2;
  if (b == 0.0)
    b = 1.0;
  double xx = m_boundingRect.x0() + a - double(p.x());
  double yy = m_boundingRect.y0() + b - double(p.y());

  double f = fabs(xx * xx / (a * a) + yy * yy / (b * b) - 1);

  return f < 0.1;
}

bool Shape2DEllipse::contains(const QPointF &p) const {
  if (m_boundingRect.isEmpty())
    return false;
  QPointF pp = m_boundingRect.center() - p;
  double a = m_boundingRect.xSpan() / 2;
  if (a == 0.0)
    a = 1.0;
  double b = m_boundingRect.ySpan() / 2;
  if (b == 0.0)
    b = 1.0;
  double xx = pp.x();
  double yy = pp.y();

  double f = xx * xx / (a * a) + yy * yy / (b * b);

  return f <= 1.0;
}

QStringList Shape2DEllipse::getDoubleNames() const {
  QStringList res;
  res << "radius1"
      << "radius2";
  return res;
}

double Shape2DEllipse::getDouble(const QString &prop) const {
  if (prop == "radius1") {
    return m_boundingRect.width() / 2;
  } else if (prop == "radius2") {
    return m_boundingRect.height() / 2;
  }
  return 0.0;
}

void Shape2DEllipse::setDouble(const QString &prop, double value) {
  if (prop == "radius1") {
    if (value <= 0.0)
      value = 1.0;
    double d = value - m_boundingRect.width() / 2;
    adjustBoundingRect(-d, 0, d, 0);
  } else if (prop == "radius2") {
    if (value <= 0.0)
      value = 1.0;
    double d = value - m_boundingRect.height() / 2;
    adjustBoundingRect(0, -d, 0, d);
  }
}

QPointF Shape2DEllipse::getPoint(const QString &prop) const {
  if (prop == "center" || prop == "centre") {
    return m_boundingRect.center();
  }
  return QPointF();
}

void Shape2DEllipse::setPoint(const QString &prop, const QPointF &value) {
  if (prop == "center" || prop == "centre") {
    m_boundingRect.moveCenter(value);
  }
}

371
372
373
374
/** Load shape 2D state from a Mantid project file
 * @param lines :: lines from the project file to load state from
 * @return a new shape2D in the shape of a ellipse
 */
375
Shape2D *Shape2DEllipse::loadFromProject(const std::string &lines) {
376
  API::TSVSerialiser tsv(lines);
Samuel Jackson's avatar
Samuel Jackson committed
377
378
379
380
381
382
  tsv.selectLine("Parameters");
  double radius1, radius2, x, y;
  tsv >> radius1 >> radius2 >> x >> y;
  return new Shape2DEllipse(QPointF(x, y), radius1, radius2);
}

383
384
385
/** Save the state of the shape 2D ellipe to a Mantid project file
 * @return a string representing the state of the shape 2D
 */
386
std::string Shape2DEllipse::saveToProject() const {
387
  API::TSVSerialiser tsv;
Samuel Jackson's avatar
Samuel Jackson committed
388
389
390
391
392
393
394
395
396
  double radius1 = getDouble("radius1");
  double radius2 = getDouble("radius2");
  auto centre = getPoint("centre");

  tsv.writeLine("Type") << "ellipse";
  tsv.writeLine("Parameters") << radius1 << radius2 << centre.x(), centre.y();
  tsv.writeRaw(Shape2D::saveToProject());
  return tsv.outputLines();
}
397
398
399
400
401
402
403
404
405
406
407
408
409
410
// --- Shape2DRectangle --- //

Shape2DRectangle::Shape2DRectangle() { m_boundingRect = RectF(); }

Shape2DRectangle::Shape2DRectangle(const QPointF &p0, const QPointF &p1) {
  m_boundingRect = RectF(p0, p1);
}

Shape2DRectangle::Shape2DRectangle(const QPointF &p0, const QSizeF &size) {
  m_boundingRect = RectF(p0, size);
}

bool Shape2DRectangle::selectAt(const QPointF &p) const {
  if (m_fill_color != QColor()) { // filled rectangle
Whitfield, Ross's avatar
Whitfield, Ross committed
411
412
    return contains(QTransform().rotate(-m_boundingRotation).map(p - m_boundingRect.center()) +
                    m_boundingRect.center());
413
414
415
416
417
418
419
420
421
422
  }

  RectF outer(m_boundingRect);
  outer.adjust(QPointF(-2, -2), QPointF(2, 2));
  RectF inner(m_boundingRect);
  inner.adjust(QPointF(2, 2), QPointF(-2, -2));
  return outer.contains(p) && !inner.contains(p);
}

void Shape2DRectangle::drawShape(QPainter &painter) const {
Whitfield, Ross's avatar
Whitfield, Ross committed
423
  QRectF drawRect = m_boundingRect.translated(-m_boundingRect.center()).toQRectF();
424
  painter.save();
Whitfield, Ross's avatar
Whitfield, Ross committed
425
  painter.rotate(m_boundingRotation);
Whitfield, Ross's avatar
Whitfield, Ross committed
426
  painter.translate(QTransform().rotate(-m_boundingRotation).map(m_boundingRect.center()));
427
428
429
430
431
432
  painter.drawRect(drawRect);
  if (m_fill_color != QColor()) {
    QPainterPath path;
    path.addRect(drawRect);
    painter.fillPath(path, m_fill_color);
  }
433
  painter.restore();
434
435
436
437
438
439
}

void Shape2DRectangle::addToPath(QPainterPath &path) const {
  path.addRect(m_boundingRect.toQRectF());
}

440
441
442
443
/** Load shape 2D state from a Mantid project file
 * @param lines :: lines from the project file to load state from
 * @return a new shape2D in the shape of a rectangle
 */
444
Shape2D *Shape2DRectangle::loadFromProject(const std::string &lines) {
445
  API::TSVSerialiser tsv(lines);
Samuel Jackson's avatar
Samuel Jackson committed
446
447
448
449
450
451
452
453
  tsv.selectLine("Parameters");
  double x0, y0, x1, y1;
  tsv >> x0 >> y0 >> x1 >> y1;
  QPointF point1(x0, y0);
  QPointF point2(x1, y1);
  return new Shape2DRectangle(point1, point2);
}

454
455
456
/** Save the state of the shape 2D rectangle to a Mantid project file
 * @return a string representing the state of the shape 2D
 */
457
std::string Shape2DRectangle::saveToProject() const {
458
  API::TSVSerialiser tsv;
Samuel Jackson's avatar
Samuel Jackson committed
459
460
461
462
463
464
465
466
467
468
469
  auto x0 = m_boundingRect.x0();
  auto x1 = m_boundingRect.x1();
  auto y0 = m_boundingRect.y0();
  auto y1 = m_boundingRect.y1();

  tsv.writeLine("Type") << "rectangle";
  tsv.writeLine("Parameters") << x0 << y0 << x1 << y1;
  tsv.writeRaw(Shape2D::saveToProject());
  return tsv.outputLines();
}

470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
// --- Shape2DRing --- //

Shape2DRing::Shape2DRing(Shape2D *shape, double xWidth, double yWidth)
    : m_outer_shape(shape), m_xWidth(xWidth), m_yWidth(yWidth) {
  m_inner_shape = m_outer_shape->clone();
  m_inner_shape->getBoundingRect();
  m_inner_shape->adjustBoundingRect(m_xWidth, m_yWidth, -m_xWidth, -m_yWidth);
  resetBoundingRect();
  m_outer_shape->setFillColor(QColor());
  m_inner_shape->setFillColor(QColor());
}

Shape2DRing::Shape2DRing(const Shape2DRing &ring)
    : Shape2D(), m_outer_shape(ring.m_outer_shape->clone()),
      m_inner_shape(ring.m_inner_shape->clone()), m_xWidth(ring.m_xWidth),
      m_yWidth(ring.m_yWidth) {
  resetBoundingRect();
}

bool Shape2DRing::selectAt(const QPointF &p) const { return contains(p); }

bool Shape2DRing::contains(const QPointF &p) const {
  return m_outer_shape->contains(p) && !m_inner_shape->contains(p);
}

void Shape2DRing::drawShape(QPainter &painter) const {
  m_outer_shape->draw(painter);
  m_inner_shape->draw(painter);
  if (m_fill_color != QColor()) {
    QPainterPath path;
    m_outer_shape->addToPath(path);
    m_inner_shape->addToPath(path);
    painter.fillPath(path, m_fill_color);
  }
}

void Shape2DRing::refit() {
  if (m_xWidth <= 0)
    m_xWidth = 0.000001;
  if (m_yWidth <= 0)
    m_yWidth = 0.000001;
  double xWidth = m_xWidth;
  double yWidth = m_yWidth;
  double max_width = m_boundingRect.width() / 2;
  if (xWidth > max_width)
    xWidth = max_width;
  double max_height = m_boundingRect.height() / 2;
  if (yWidth > max_height)
    yWidth = max_height;
  m_outer_shape->setBoundingRect(m_boundingRect);
  m_inner_shape->setBoundingRect(m_boundingRect);
  m_inner_shape->adjustBoundingRect(xWidth, yWidth, -xWidth, -yWidth);
}

void Shape2DRing::resetBoundingRect() {
  m_boundingRect = m_outer_shape->getBoundingRect();
}

QPointF Shape2DRing::getShapeControlPoint(size_t i) const {
  RectF rect = m_inner_shape->getBoundingRect();
  switch (i) {
  case 0:
    return QPointF(rect.center().x(), rect.y1());
  case 1:
    return QPointF(rect.center().x(), rect.y0());
  case 2:
    return QPointF(rect.x0(), rect.center().y());
  case 3:
    return QPointF(rect.x1(), rect.center().y());
  }
  return QPointF();
}

void Shape2DRing::setShapeControlPoint(size_t i, const QPointF &pos) {
  QPointF dp = pos - getShapeControlPoint(i);

  switch (i) {
  case 0:
    m_yWidth -= dp.y();
    break;
  case 1:
    m_yWidth += dp.y();
    break;
  case 2:
    m_xWidth += dp.x();
    break;
  case 3:
    m_xWidth -= dp.x();
    break;
  }
  refit();
}

QStringList Shape2DRing::getDoubleNames() const {
  QStringList res;
  res << "xwidth"
      << "ywidth";
  return res;
}

double Shape2DRing::getDouble(const QString &prop) const {
  if (prop == "xwidth") {
    return m_xWidth;
  }
  if (prop == "ywidth") {
    return m_yWidth;
  }
  return 0.0;
}

void Shape2DRing::setDouble(const QString &prop, double value) {
  if (prop == "xwidth") {
    m_xWidth = value;
    refit();
  }
  if (prop == "ywidth") {
    m_yWidth = value;
    refit();
  }
}

QPointF Shape2DRing::getPoint(const QString &prop) const {
  if (prop == "center") {
    return m_boundingRect.center();
  }
  return QPointF();
}

void Shape2DRing::setPoint(const QString &prop, const QPointF &value) {
  if (prop == "center") {
    m_boundingRect.moveCenter(value);
  }
}

void Shape2DRing::setColor(const QColor &color) {
  m_inner_shape->setColor(color);
  m_outer_shape->setColor(color);
}

609
610
611
612
/** Load shape 2D state from a Mantid project file
 * @param lines :: lines from the project file to load state from
 * @return a new shape2D in the shape of a ring
 */
613
Shape2D *Shape2DRing::loadFromProject(const std::string &lines) {
614
  API::TSVSerialiser tsv(lines);
Samuel Jackson's avatar
Samuel Jackson committed
615
616
617
618
619
620
621
622
623
624
625
626
  tsv.selectLine("Parameters");
  double xWidth, yWidth;
  tsv >> xWidth >> yWidth;

  tsv.selectSection("shape");
  std::string baseShapeLines;
  tsv >> baseShapeLines;

  auto baseShape = Shape2D::loadFromProject(baseShapeLines);
  return new Shape2DRing(baseShape, xWidth, yWidth);
}

627
628
629
/** Save the state of the shape 2D ring to a Mantid project file
 * @return a string representing the state of the shape 2D
 */
630
std::string Shape2DRing::saveToProject() const {
631
  API::TSVSerialiser tsv;
Samuel Jackson's avatar
Samuel Jackson committed
632
633
634
635
636
637
638
639
640
641
642
  auto xWidth = getDouble("xwidth");
  auto yWidth = getDouble("ywidth");
  auto baseShape = getOuterShape();

  tsv.writeLine("Type") << "ring";
  tsv.writeLine("Parameters") << xWidth << yWidth;
  tsv.writeSection("shape", baseShape->saveToProject());
  tsv.writeRaw(Shape2D::saveToProject());
  return tsv.outputLines();
}

643
644
// --- Shape2DSector --- //

645
646
Shape2DSector::Shape2DSector(double innerRadius, double outerRadius,
                             double startAngle, double endAngle,
647
                             const QPointF &center) {
648
649
  m_innerRadius = std::min(innerRadius, outerRadius);
  m_outerRadius = std::max(innerRadius, outerRadius);
650

651
652
  m_startAngle = std::fmod(startAngle, 2 * M_PI);
  m_endAngle = std::fmod(endAngle, 2 * M_PI);
653
654
655
656
  m_center = center;
  resetBoundingRect();
}

Mathieu Tillet's avatar
Mathieu Tillet committed
657
658
659
660
Shape2DSector::Shape2DSector(const Shape2DSector &sector)
    : Shape2D(), m_innerRadius(sector.m_innerRadius),
      m_outerRadius(sector.m_outerRadius), m_startAngle(sector.m_startAngle),
      m_endAngle(sector.m_endAngle), m_center(sector.m_center) {
661
  setColor(sector.getColor());
Mathieu Tillet's avatar
Mathieu Tillet committed
662
  resetBoundingRect();
663
}
Mathieu Tillet's avatar
Mathieu Tillet committed
664

665
666
667
668
669
670
/**
 * @brief Shape2DSector::selectAt
 * Checks if the sector can be selected at a given point
 * @param p :: the position to check
 * @return
 */
671
672
bool Shape2DSector::selectAt(const QPointF &p) const { return contains(p); }

673
674
675
676
677
678
/**
 * @brief Shape2DSector::contains
 * Checks if a given point is inside the sector
 * @param p :: the position to check
 * @return
 */
679
680
681
bool Shape2DSector::contains(const QPointF &p) const {
  QPointF relPos = p - m_center;

682
  double distance = distanceBetween(relPos, QPointF(0, 0));
683
684
685
686
  if (distance < m_innerRadius || distance > m_outerRadius) {
    return false;
  }

687
  double angle = std::atan2(relPos.y(), relPos.x());
688
689
690
  if (angle < 0) {
    angle += 2 * M_PI;
  }
691

692
693
694
695
696
  return ((m_startAngle <= angle && angle <= m_endAngle) ||
          (m_startAngle > m_endAngle &&
           (angle <= m_endAngle || angle >= m_startAngle)));
}

697
698
699
700
701
/**
 * @brief Shape2DSector::drawShape
 * Uses Qt to actually draw the sector shape.
 * @param painter :: QPainter used for drawing.
 */
702
703
704
void Shape2DSector::drawShape(QPainter &painter) const {
  QPainterPath path;
  double to_degrees = 180 / M_PI;
705
706
  double x_origin = m_center.x() + std::cos(m_startAngle) * m_innerRadius;
  double y_origin = m_center.y() + std::sin(m_startAngle) * m_innerRadius;
707

708
709
  double x_arcEnd = m_center.x() + std::cos(m_endAngle) * m_outerRadius;
  double y_arcEnd = m_center.y() + std::sin(m_endAngle) * m_outerRadius;
710
711

  double sweepLength = (m_endAngle - m_startAngle) * to_degrees;
712
713
714
  if (sweepLength < 0) {
    sweepLength += 360;
  }
715
716

  path.moveTo(x_origin, y_origin);
717
718
  QRectF absoluteBBox(QPointF(-1, 1), QPointF(1, -1));

719
720
721
722
723
724
725
726
727
728
  path.arcTo(QRectF(absoluteBBox.topLeft() * m_innerRadius + m_center,
                    absoluteBBox.bottomRight() * m_innerRadius + m_center),
             m_startAngle * to_degrees, sweepLength);
  path.lineTo(x_arcEnd, y_arcEnd);
  path.arcTo(QRectF(absoluteBBox.topLeft() * m_outerRadius + m_center,
                    absoluteBBox.bottomRight() * m_outerRadius + m_center),
             m_endAngle * to_degrees, -sweepLength);
  path.closeSubpath();

  painter.drawPath(path);
729
730
731
  if (m_fill_color != QColor()) {
    painter.fillPath(path, m_fill_color);
  }
732
733
}

734
735
736
737
738
739
740
741
742
/**
 * Compute the bounding box of the sector defined by the attributes m_center,
 * m_startAngle, m_endAngle, m_innerRadius, m_outerRadius (and NOT using
 * m_boundingBox)
 **/
QRectF Shape2DSector::findSectorBoundingBox() {
  double xMin, xMax, yMin, yMax;

  // checking in turns the limits of the bounding box
743

744
745
746
747
748
749
  // the yMax value is the outerRaius if the sector reaches pi/2
  if ((m_startAngle <= M_PI / 2 && m_endAngle >= M_PI / 2) ||
      (m_startAngle > m_endAngle &&
       !(m_startAngle >= M_PI / 2 && m_endAngle <= M_PI / 2))) {
    yMax = m_outerRadius;
    // else it has to be computed
750
  } else {
751
752
    yMax = std::max(std::sin(m_startAngle), std::sin(m_endAngle));
    yMax = std::max(yMax * m_innerRadius, yMax * m_outerRadius);
753
754
  }

755
756
757
758
759
  // xMin is -outerRadius if the sector reaches pi
  if ((m_startAngle <= M_PI && m_endAngle >= M_PI) ||
      (m_startAngle > m_endAngle &&
       !(m_startAngle >= M_PI && m_endAngle <= M_PI))) {
    xMin = -m_outerRadius;
760
  } else {
761
762
    xMin = std::min(std::cos(m_startAngle), std::cos(m_endAngle));
    xMin = std::min(xMin * m_innerRadius, xMin * m_outerRadius);
763
764
  }

765
766
767
768
769
  // yMin is -outerRadius if the sector reaches 3pi/2
  if ((m_startAngle <= 3 * M_PI / 2 && m_endAngle >= 3 * M_PI / 2) ||
      (m_startAngle > m_endAngle &&
       !(m_startAngle >= 3 * M_PI / 2 && m_endAngle <= 3 * M_PI / 2))) {
    yMin = -m_outerRadius;
770
  } else {
771
772
    yMin = std::min(std::sin(m_startAngle), std::sin(m_endAngle));
    yMin = std::min(yMin * m_innerRadius, yMin * m_outerRadius);
773
774
  }

775
776
777
778
  // and xMax is outerRadius if the sector reaches 0 (which is equivalent to
  // this condition, given the constraints on the angles)
  if (m_startAngle > m_endAngle) {
    xMax = m_outerRadius;
779
  } else {
780
781
    xMax = std::max(std::cos(m_startAngle), std::cos(m_endAngle));
    xMax = std::max(xMax * m_innerRadius, xMax * m_outerRadius);
782
783
  }

784
785
786
  QPointF topLeft(xMin, yMax);
  QPointF bottomRight(xMax, yMin);
  return QRectF(topLeft + m_center, bottomRight + m_center);
787
788
}

789
790
791
792
793
/**
 * @brief Shape2DSector::refit
 * Enforce coherence between all the parameters defining the sector. Used when
 * it is updated, mostly when moved or scaled.
 */
794
void Shape2DSector::refit() {
795
796
797
798
799
  constexpr double epsilon = 1e-6;

  // current real bounding box of the sector, based on the attributes, before
  // the user's modifications take place
  QRectF BBox = findSectorBoundingBox();
800

801
  // corners of the user-modified bounding box
802
803
804
805
806
807
808
  QPointF bRectTopLeft(
      std::min(m_boundingRect.p0().x(), m_boundingRect.p1().x()),
      std::max(m_boundingRect.p0().y(), m_boundingRect.p1().y()));
  QPointF bRectBottomRight(
      std::max(m_boundingRect.p0().x(), m_boundingRect.p1().x()),
      std::min(m_boundingRect.p0().y(), m_boundingRect.p1().y()));

809
  // check if the bounding box has been modified
810
811
812
813

  // since the calculus are made on relatively small numbers, some errors can
  // progressively appear and stack, so we have to take a range rather than a
  // strict equality
814
  if (BBox.topLeft().x() != bRectTopLeft.x() &&
815
      BBox.topLeft().y() != bRectTopLeft.y() &&
816
817
      std::abs(BBox.bottomRight().x() - bRectBottomRight.x()) < epsilon &&
      std::abs(BBox.bottomRight().y() - bRectBottomRight.y()) < epsilon) {
818

819
    // top left corner is moving
Mathieu Tillet's avatar
Mathieu Tillet committed
820
    computeScaling(BBox.topLeft(), BBox.bottomRight(), bRectTopLeft, 0);
821

822
823
  } else if (BBox.topLeft().x() != bRectTopLeft.x() &&
             BBox.bottomRight().y() != bRectBottomRight.y() &&
824
825
826
             std::abs(BBox.bottomRight().x() - bRectBottomRight.x()) <
                 epsilon &&
             std::abs(BBox.topLeft().y() - bRectTopLeft.y()) < epsilon) {
827

828
829
830
    // bottom left corner is moving
    computeScaling(BBox.bottomLeft(), BBox.topRight(),
                   QPointF(bRectTopLeft.x(), bRectBottomRight.y()), 1);
831
832
833

  } else if (BBox.bottomRight().x() != bRectBottomRight.x() &&
             BBox.bottomRight().y() != bRectBottomRight.y() &&
834
835
             std::abs(BBox.topLeft().x() - bRectTopLeft.x()) < epsilon &&
             std::abs(BBox.topLeft().y() - bRectTopLeft.y()) < epsilon) {
836

837
838
    // bottom right corner is moving
    computeScaling(BBox.bottomRight(), BBox.topLeft(), bRectBottomRight, 2);
839
840
841

  } else if (BBox.bottomRight().x() != bRectBottomRight.x() &&
             BBox.topLeft().y() != bRectTopLeft.y() &&
842
843
844
             std::abs(BBox.topLeft().x() - bRectTopLeft.x()) < epsilon &&
             std::abs(BBox.bottomRight().y() - bRectBottomRight.y()) <
                 epsilon) {
845

846
847
848
    // top right corner is moving
    computeScaling(BBox.topRight(), BBox.bottomLeft(),
                   QPointF(bRectBottomRight.x(), bRectTopLeft.y()), 3);
849
850
  }

851
  // check if the shape has moved
852
853
  if ((BBox.bottomRight().x() != bRectBottomRight.x() &&
       BBox.topLeft().x() != bRectTopLeft.x() &&
854
855
       std::abs((BBox.bottomRight().x() - bRectBottomRight.x()) -
                (BBox.topLeft().x() - bRectTopLeft.x())) < epsilon) ||
856
857
      (BBox.bottomRight().y() != bRectBottomRight.y() &&
       BBox.topLeft().y() != bRectTopLeft.y() &&
858
859
       std::abs((BBox.bottomRight().y() - bRectBottomRight.y()) -
                (BBox.topLeft().y() - bRectTopLeft.y())) < epsilon)) {
860
861
862
863
864
865
    // every corner has moved by the same distance -> the shape is being moved
    qreal xDiff = bRectBottomRight.x() - BBox.bottomRight().x();
    qreal yDiff = bRectBottomRight.y() - BBox.bottomRight().y();

    m_center.setX(m_center.x() + xDiff);
    m_center.setY(m_center.y() + yDiff);
Mathieu Tillet's avatar
Mathieu Tillet committed
866
    resetBoundingRect();
867
  }
868
869
}

870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
/**
 * @brief Shape2DSector::computeScaling
 * Used when updating the bounding box after the user dragged a corner. Given
 * the constraints of a circular sector, the new bounding box cannot be exactly
 * the one drawed by the mouse of the user, and thus a number of corrections are
 * needed.
 * This method thus corrects this new value and then modifies the difining
 * parameters of the sector accordingly.
 *
 * @param BBoxCorner :: the corner modified by the user, before it has been
 * changed.
 * @param BBoxOpposedCorner :: the corner diagonally opposed to the one the user
 * modified, which has not changed
 * @param bRectCorner :: the new position of the corner moved by the user,
 * before any correction applies
 * @param vertexIndex :: the index of the vertex corresponding to the one
 * modified by the user in m_boundingRect
 */
void Shape2DSector::computeScaling(const QPointF &BBoxCorner,
                                   const QPointF &BBoxOpposedCorner,
                                   const QPointF &bRectCorner,
                                   int vertexIndex) {
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911

  // first we need to find the best projection of the new corner on the
  // diagonal line of the rectangle, so its shape won't be modified, only
  // scaled.
  QPointF xProj, yProj, proj;
  qreal xPos, yPos;
  QVector2D slope;

  slope = QVector2D(BBoxCorner - BBoxOpposedCorner);
  xPos = (bRectCorner - BBoxCorner).x();
  yPos = slope.y() * xPos / slope.x(); // TODO : check if non zero
  xProj.setX(xPos);
  xProj.setY(yPos);

  yPos = (bRectCorner - BBoxCorner).y();
  xPos = slope.x() * yPos / slope.y();

  yProj.setX(xPos);
  yProj.setY(yPos);

912
913
914
915
916
917
918
919
  if (slope.x() != 0 && slope.y() != 0) {
    if (distanceBetween(xProj, QPointF(0, 0)) <
        distanceBetween(yProj, QPointF(0, 0))) {
      proj = xProj;
    } else {
      proj = yProj;
    }
  } else if (slope.x() != 0) {
920
    proj = xProj;
921
  } else if (slope.y() != 0) {
922
    proj = yProj;
923
924
925
926
  } else {
    // case that is not supposed to happen; it means the sector has been reduced
    // to a point, which is not possible
    return;
927
928
  }
  proj += BBoxCorner;
929

930
931
932
933
934
935
936
  // then we need to adapt the shape to the new size
  qreal ratio = distanceBetween(proj, BBoxOpposedCorner) /
                distanceBetween(slope.toPointF(), QPointF(0, 0));

  m_boundingRect.setVertex(vertexIndex, proj);

  m_innerRadius *= ratio;
937
  m_outerRadius = ratio != 0 ? m_outerRadius * ratio : 1e-4;
938
939
940
941
  m_center.setX((m_center.x() - BBoxOpposedCorner.x()) * ratio +
                BBoxOpposedCorner.x());
  m_center.setY((m_center.y() - BBoxOpposedCorner.y()) * ratio +
                BBoxOpposedCorner.y());
942
943
}

944
945
946
947
948
949
950
951
952
/**
 * @brief Shape2DSector::distanceBetween
 * Helper method to calculate the distance between 2 QPointF points.
 * @param p0 :: the first point
 * @param p1 :: the second point
 * @return  the distance
 */
double Shape2DSector::distanceBetween(const QPointF &p0,
                                      const QPointF &p1) const {
953
  return sqrt(pow(p0.x() - p1.x(), 2) + pow(p0.y() - p1.y(), 2));
954
955
}

956
957
958
959
960
/**
 * @brief Shape2DSector::resetBoundingRect
 * Compute m_boundingBox using the geometrical parameters of the sector (ie
 * center, angles and radii)
 **/
961
962
void Shape2DSector::resetBoundingRect() {

963
964
965
  QRectF BBox = findSectorBoundingBox();
  // because of how Mantid's rectangles are defined, it is necessary to pass the
  // arguments in this precise order in order to have a smooth scaling when
966
  // creating a shape from top left corner
967
  m_boundingRect = RectF(BBox.bottomLeft(), BBox.topRight());
968
969
}

970
971
972
973
974
975
/**
 * Return coordinates of i-th control point.
 * 0 controls the outer radius, 1 the inner, 2 the starting angle and 3 the
 * ending one.
 * @param i :: Index of a control point. 0 <= i < getNControlPoints().
 */
976
977
978
QPointF Shape2DSector::getShapeControlPoint(size_t i) const {
  double halfAngle =
      m_startAngle < m_endAngle
979
980
          ? std::fmod((m_startAngle + m_endAngle) / 2., 2 * M_PI)
          : std::fmod((m_startAngle + m_endAngle + 2 * M_PI) / 2, 2 * M_PI);
981
982
983
984
  double halfLength = (m_outerRadius + m_innerRadius) / 2;

  switch (i) {
  case 0:
985
986
    return QPointF(m_center.x() + std::cos(halfAngle) * m_outerRadius,
                   m_center.y() + std::sin(halfAngle) * m_outerRadius);
987
  case 1:
988
989
    return QPointF(m_center.x() + std::cos(halfAngle) * m_innerRadius,
                   m_center.y() + std::sin(halfAngle) * m_innerRadius);
990
  case 2:
991
992
    return QPointF(m_center.x() + std::cos(m_startAngle) * halfLength,
                   m_center.y() + std::sin(m_startAngle) * halfLength);
993
  case 3:
994
995
996
997
    return QPointF(m_center.x() + std::cos(m_endAngle) * halfLength,
                   m_center.y() + std::sin(m_endAngle) * halfLength);
  default:
    return QPointF();
998
999
1000
  }
}

1001
1002
1003
1004
1005
1006
1007
1008
1009
/**
 * @brief Shape2DSector::setShapeControlPoint
 * Modify the sector when the i-th control point is moved.
 * 0 is outer radius, 1 is inner, 2 is starting angle, 3 is ending.
 * @param i :: index of the control point changed
 * @param pos :: the new position of the control point (might not be where it is
 * palced at the end of the update though, since there are constraints to take
 * into account)
 */
1010
1011
void Shape2DSector::setShapeControlPoint(size_t i, const QPointF &pos) {
  QPointF to_center = pos - m_center;
1012
  constexpr double epsilon = 1e-6;
Mathieu Tillet's avatar
Mathieu Tillet committed
1013
  double newAngle;
1014

1015
1016
  switch (i) {
  case 0:
1017
    m_outerRadius = distanceBetween(to_center, QPointF(0, 0));
Mathieu Tillet's avatar
Mathieu Tillet committed
1018
    if (m_outerRadius < m_innerRadius) {
1019
      m_outerRadius = m_innerRadius != 0 ? 1.01 * m_innerRadius : 1e-4;
Mathieu Tillet's avatar
Mathieu Tillet committed
1020
    }
1021
1022
    break;
  case 1:
1023
    m_innerRadius = distanceBetween(to_center, QPointF(0, 0));
Mathieu Tillet's avatar
Mathieu Tillet committed
1024
1025
1026
    if (m_outerRadius < m_innerRadius) {
      m_innerRadius = 0.99 * m_outerRadius;
    }
1027
1028
    break;
  case 2:
1029
    newAngle = std::atan2(to_center.y(), to_center.x());
Mathieu Tillet's avatar
Mathieu Tillet committed
1030
1031
    if (newAngle < 0)
      newAngle += 2 * M_PI;
1032

1033
    // conditions to prevent the startAngle from going over the endAngle
1034
    // this one is in case of trigonometrical rotation
1035
    if ((m_startAngle < m_endAngle && newAngle >= m_endAngle &&
1036
         std::abs(newAngle - m_startAngle) < M_PI) ||
1037
        (newAngle < m_endAngle && m_startAngle < m_endAngle &&
1038
         std::abs(newAngle - m_startAngle) > M_PI && newAngle < m_startAngle) ||
1039
        (newAngle > m_endAngle && m_startAngle > m_endAngle &&
1040
         std::abs(newAngle - m_startAngle) > M_PI && newAngle < m_startAngle)) {
1041

1042
      newAngle = m_endAngle - epsilon;
1043
1044
1045
1046
1047

      if (newAngle < 0) {
        newAngle += 2 * M_PI;
      }

1048
      // and this one is in case of clockwise rotation
1049
    } else if ((m_startAngle > m_endAngle && newAngle <= m_endAngle &&
1050
                std::abs(newAngle - m_startAngle) < M_PI) ||
1051
               (newAngle > m_endAngle && m_startAngle > m_endAngle &&
1052
                std::abs(newAngle - m_startAngle) > M_PI &&
1053
1054
                newAngle > m_startAngle) ||
               (newAngle < m_endAngle && m_startAngle < m_endAngle &&
1055
                std::abs(newAngle - m_startAngle) > M_PI &&
1056
1057
                newAngle > m_startAngle)) {

1058
      newAngle = m_endAngle + epsilon;
1059
1060
1061
1062

      if (newAngle >= 2 * M_PI) {
        newAngle -= 2 * M_PI;
      }
Mathieu Tillet's avatar
Mathieu Tillet committed
1063
    }
1064

Mathieu Tillet's avatar
Mathieu Tillet committed
1065
    m_startAngle = newAngle;
1066
    break;
Mathieu Tillet's avatar
Mathieu Tillet committed
1067

1068
  case 3:
1069
    newAngle = std::atan2(to_center.y(), to_center.x());
Mathieu Tillet's avatar
Mathieu Tillet committed
1070
1071
    if (newAngle < 0)
      newAngle += 2 * M_PI;
1072

1073
1074
    // and conditions to prevent the endAngle from going over the startAngle
    // trigonometrical rotation
1075
    if ((m_endAngle < m_startAngle && newAngle >= m_startAngle &&
1076
         std::abs(newAngle - m_endAngle) < M_PI) ||
1077
        (newAngle < m_startAngle && m_endAngle < m_startAngle &&
1078
         std::abs(newAngle - m_endAngle) > M_PI && newAngle < m_endAngle) ||
1079
        (newAngle > m_startAngle && m_endAngle > m_startAngle &&
1080
         std::abs(newAngle - m_endAngle) > M_PI && newAngle < m_endAngle)) {
1081

1082
      newAngle = m_startAngle - epsilon;
1083
1084
1085
1086

      if (newAngle < 0) {
        newAngle += 2 * M_PI;
      }
1087
      // clockwise rotation
1088
    } else if ((m_endAngle >= m_startAngle && newAngle <= m_startAngle &&
1089
                std::abs(newAngle - m_endAngle) < M_PI) ||
1090
               (newAngle >= m_startAngle && m_endAngle >= m_startAngle &&
1091
                std::abs(newAngle - m_endAngle) > M_PI &&
1092
1093
                newAngle > m_endAngle) ||
               (newAngle < m_startAngle && m_endAngle < m_startAngle &&
1094
                std::abs(newAngle - m_endAngle) > M_PI &&
1095
1096
                newAngle > m_endAngle)) {

1097
      newAngle = m_startAngle + epsilon;
1098
1099
1100
      if (newAngle >= 2 * M_PI) {
        newAngle -= 2 * M_PI;
      }
Mathieu Tillet's avatar
Mathieu Tillet committed
1101
    }
1102

Mathieu Tillet's avatar
Mathieu Tillet committed
1103
    m_endAngle = newAngle;
1104
    break;
1105
1106
  default:
    return;
1107
  }
Mathieu Tillet's avatar
Mathieu Tillet committed
1108
  resetBoundingRect();
1109
1110
}

1111
1112
1113
1114
1115
/**
 * @brief Shape2DSector::getDoubleNames
 * Getter method to access the names of the double attributes
 * @return
 */
1116
1117
1118
1119
1120
1121
1122
1123
1124
QStringList Shape2DSector::getDoubleNames() const {
  QStringList res;
  res << "outerRadius"
      << "innerRadius"
      << "startAngle"
      << "endAngle";
  return res;
}

1125
1126
1127
1128
1129
1130
/**
 * @brief Shape2DSector::getDouble
 * Getter method to access the value of double attributes
 * @param prop :: the name of the sought attribute
 * @return
 */
1131
double Shape2DSector::getDouble(const QString &prop) const {
1132
  double to_degrees = 180 / M_PI;
1133
  if (prop == "outerRadius")
1134
    return m_outerRadius;
1135
  if (prop == "innerRadius")
1136
    return m_innerRadius;
1137
  if (prop == "startAngle")
Mathieu Tillet's avatar
Mathieu Tillet committed
1138
    return m_startAngle * to_degrees;
1139
  if (prop == "endAngle")
Mathieu Tillet's avatar
Mathieu Tillet committed
1140
    return m_endAngle * to_degrees;
1141

1142
1143
1144
  return 0.0;
}

1145
1146
1147
1148
1149
1150
1151
/**
 * @brief Shape2DSector::setDouble
 * Set the value of a double attribute, after some checks, and update the
 * sector.
 * @param prop :: the name of the attribute to change
 * @param value :: the new value
 */
1152
void Shape2DSector::setDouble(const QString &prop, double value) {
Mathieu Tillet's avatar
Mathieu Tillet committed
1153
  double to_radians = M_PI / 180;
1154
  if (prop == "outerRadius") {
1155
1156
1157
1158
    if (m_innerRadius < value)
      m_outerRadius = value;
    else
      m_outerRadius = m_innerRadius != 0 ? 1.01 * m_innerRadius : 1e-4;
1159
  } else if (prop == "innerRadius") {
1160
1161
    value = std::max(0.0, value);
    m_innerRadius = m_outerRadius >= value ? value : 0.99 * m_outerRadius;
1162
  } else if (prop == "startAngle") {
1163
    m_startAngle = std::fmod(value, 360);
Mathieu Tillet's avatar
Mathieu Tillet committed
1164
    m_startAngle = m_startAngle >= 0 ? m_startAngle : m_startAngle + 360;
1165
    m_startAngle *= to_radians;
1166
  } else if (prop == "endAngle") {
1167
    m_endAngle = std::fmod(value, 360);
Mathieu Tillet's avatar
Mathieu Tillet committed
1168
    m_endAngle = m_endAngle >= 0 ? m_endAngle : m_endAngle + 360;
1169
1170
1171
1172
    m_endAngle *= to_radians;
  } else
    return;
  resetBoundingRect();
1173
1174
}

1175
1176
1177
1178
1179
1180
/**
 * @brief Shape2DSector::getPoint
 * Getter method to access the value of point attributes (ie m_center).
 * @param prop :: the name of the sought attribute
 * @return
 */
1181
1182
1183
1184
1185
1186
1187
QPointF Shape2DSector::getPoint(const QString &prop) const {
  if (prop == "center") {
    return m_center;
  }
  return QPointF();
}

1188
1189
1190
1191
1192
1193
1194
/**
 * @brief Shape2DSector::setPoint
 * Set the value of a point attribute, after some checks, and update the
 * sector.
 * @param prop :: the name of the point to change
 * @param value :: the new value
 */
1195
1196
1197
void Shape2DSector::setPoint(const QString &prop, const QPointF &value) {
  if (prop == "center") {
    m_center = value;
1198
    resetBoundingRect();
1199
1200
  }
}
1201

1202
1203
1204
1205
/** Load shape 2D state from a Mantid project file
 * @param lines :: lines from the project file to load state from
 * @return a new shape2D with old state applied
 */
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
Shape2D *Shape2DSector::loadFromProject(const std::string &lines) {
  API::TSVSerialiser tsv(lines);
  tsv.selectLine("Parameters");
  double innerRadius, outerRadius, startAngle, endAngle, xCenter, yCenter;
  tsv >> innerRadius >> outerRadius >> startAngle >> endAngle >> xCenter >>
      yCenter;

  return new Shape2DSector(innerRadius, outerRadius, startAngle, endAngle,
                           QPointF(xCenter, yCenter));
}

1217
1218
1219
/** Save the state of the sector to a Mantid project file
 * @return a string representing the state of the sector
 */
1220
std::string Shape2DSector::saveToProject() const {
Mathieu Tillet's avatar