diff --git a/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowMaskTab.cpp b/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowMaskTab.cpp index c90fccd9e4b72a00a4ba8e906e1f781b88bb14b8..3cce4254663201da8da378d915dc8bd0103cf735 100644 --- a/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowMaskTab.cpp +++ b/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowMaskTab.cpp @@ -120,6 +120,13 @@ m_left(NULL), m_top(NULL), m_right(NULL), m_bottom(NULL) m_ring_rectangle->setToolTip("Draw a rectangular ring (Shift+Alt+R)"); m_ring_rectangle->setShortcut(QKeySequence("Shift+Alt+R")); + m_free_draw = new QPushButton(); + m_free_draw->setCheckable(true); + m_free_draw->setAutoExclusive(true); + m_free_draw->setIcon(QIcon(":/MaskTools/brush.png")); + m_free_draw->setToolTip("Draw an arbitrary shape (Shift+Alt+A)"); + m_free_draw->setShortcut(QKeySequence("Shift+Alt+A")); + QHBoxLayout* toolBox = new QHBoxLayout(); toolBox->addWidget(m_move); toolBox->addWidget(m_pointer); @@ -127,6 +134,7 @@ m_left(NULL), m_top(NULL), m_right(NULL), m_bottom(NULL) toolBox->addWidget(m_rectangle); toolBox->addWidget(m_ring_ellipse); toolBox->addWidget(m_ring_rectangle); + toolBox->addWidget(m_free_draw); toolBox->addStretch(); toolBox->setSpacing(2); toolBox->setMargin(0); @@ -137,6 +145,7 @@ m_left(NULL), m_top(NULL), m_right(NULL), m_bottom(NULL) connect(m_rectangle,SIGNAL(clicked()),this,SLOT(setActivity())); connect(m_ring_ellipse,SIGNAL(clicked()),this,SLOT(setActivity())); connect(m_ring_rectangle,SIGNAL(clicked()),this,SLOT(setActivity())); + connect(m_free_draw,SIGNAL(clicked()),this,SLOT(setActivity())); m_move->setChecked(true); QFrame* toolGroup = new QFrame(); toolGroup->setLayout(toolBox); @@ -340,6 +349,8 @@ void InstrumentWindowMaskTab::selectTool(Activity tool) break; case DrawRectangularRing: m_ring_rectangle->setChecked(true); break; + case DrawFree: m_free_draw->setChecked(true); + break; default: throw std::invalid_argument("Invalid tool type."); } setActivity(); @@ -362,37 +373,44 @@ void InstrumentWindowMaskTab::setActivity() else if (m_pointer->isChecked()) { m_activity = Select; - m_instrWindow->getSurface()->setInteractionMode(ProjectionSurface::DrawMode); + m_instrWindow->getSurface()->setInteractionMode(ProjectionSurface::DrawRegularMode); m_activeTool->setText("Tool: Shape editing"); } else if (m_ellipse->isChecked()) { m_activity = DrawEllipse; m_instrWindow->getSurface()->startCreatingShape2D("ellipse",borderColor,fillColor); - m_instrWindow->getSurface()->setInteractionMode(ProjectionSurface::DrawMode); + m_instrWindow->getSurface()->setInteractionMode(ProjectionSurface::DrawRegularMode); m_activeTool->setText("Tool: Ellipse"); } else if (m_rectangle->isChecked()) { m_activity = DrawRectangle; m_instrWindow->getSurface()->startCreatingShape2D("rectangle",borderColor,fillColor); - m_instrWindow->getSurface()->setInteractionMode(ProjectionSurface::DrawMode); + m_instrWindow->getSurface()->setInteractionMode(ProjectionSurface::DrawRegularMode); m_activeTool->setText("Tool: Rectangle"); } else if (m_ring_ellipse->isChecked()) { m_activity = DrawEllipticalRing; m_instrWindow->getSurface()->startCreatingShape2D("ring ellipse",borderColor,fillColor); - m_instrWindow->getSurface()->setInteractionMode(ProjectionSurface::DrawMode); + m_instrWindow->getSurface()->setInteractionMode(ProjectionSurface::DrawRegularMode); m_activeTool->setText("Tool: Elliptical ring"); } else if (m_ring_rectangle->isChecked()) { m_activity = DrawRectangularRing; m_instrWindow->getSurface()->startCreatingShape2D("ring rectangle",borderColor,fillColor); - m_instrWindow->getSurface()->setInteractionMode(ProjectionSurface::DrawMode); + m_instrWindow->getSurface()->setInteractionMode(ProjectionSurface::DrawRegularMode); m_activeTool->setText("Tool: Rectangular ring"); } + else if (m_free_draw->isChecked()) + { + m_activity = DrawFree; + m_instrWindow->getSurface()->startCreatingFreeShape(borderColor,fillColor); + m_instrWindow->getSurface()->setInteractionMode(ProjectionSurface::DrawFreeMode); + m_activeTool->setText("Tool: Free draw"); + } m_instrWindow->updateInfoText(); } @@ -401,7 +419,11 @@ void InstrumentWindowMaskTab::setActivity() */ void InstrumentWindowMaskTab::shapeCreated() { - setSelectActivity(); + if (!isVisible()) return; + if (m_activity != DrawFree) + { + setSelectActivity(); + } enableApplyButtons(); } @@ -463,6 +485,7 @@ void InstrumentWindowMaskTab::shapesCleared() void InstrumentWindowMaskTab::clearShapes() { m_instrWindow->getSurface()->clearMask(); + setSelectActivity(); } void InstrumentWindowMaskTab::showEvent (QShowEvent *) diff --git a/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowMaskTab.h b/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowMaskTab.h index 5ef2db91bee8a2ac2946f64ce750114bdf99d62c..bb5093e1a48791a5eb84523d48743201091823c0 100644 --- a/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowMaskTab.h +++ b/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowMaskTab.h @@ -54,7 +54,7 @@ class InstrumentWindowMaskTab: public InstrumentWindowTab Q_OBJECT public: enum Mode {Mask, Group, ROI}; - enum Activity {Move,Select,DrawEllipse,DrawRectangle,DrawEllipticalRing,DrawRectangularRing}; + enum Activity {Move,Select,DrawEllipse,DrawRectangle,DrawEllipticalRing,DrawRectangularRing,DrawFree}; InstrumentWindowMaskTab(InstrumentWindow* instrWindow); void initSurface(); @@ -129,6 +129,7 @@ protected: QPushButton* m_rectangle; QPushButton* m_ring_ellipse; QPushButton* m_ring_rectangle; + QPushButton* m_free_draw; QPushButton* m_apply; QPushButton* m_apply_to_view; diff --git a/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowPickTab.cpp b/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowPickTab.cpp index 05247a3929590148097e37ed1dabe406f0a4a91c..f49c0a553dada231bfbf167e2413e54d2c64d922 100644 --- a/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowPickTab.cpp +++ b/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowPickTab.cpp @@ -197,6 +197,12 @@ m_plotTypeCache(0) m_ring_rectangle->setIcon(QIcon(":/PickTools/selection-box-ring.png")); m_ring_rectangle->setToolTip("Draw a rectangular ring"); + m_free_draw = new QPushButton(); + m_free_draw->setCheckable(true); + m_free_draw->setAutoExclusive(true); + m_free_draw->setIcon(QIcon(":/PickTools/brush.png")); + m_free_draw->setToolTip("Draw an arbitrary shape"); + m_edit = new QPushButton(); m_edit->setCheckable(true); m_edit->setAutoExclusive(true); @@ -222,6 +228,7 @@ m_plotTypeCache(0) toolBox->addWidget(m_rectangle,0,3); toolBox->addWidget(m_ring_ellipse,0,4); toolBox->addWidget(m_ring_rectangle,0,5); + toolBox->addWidget(m_free_draw,0,6); toolBox->addWidget(m_one,1,0); toolBox->addWidget(m_tube,1,1); toolBox->addWidget(m_peak,1,2); @@ -237,6 +244,7 @@ m_plotTypeCache(0) connect(m_ellipse,SIGNAL(clicked()),this,SLOT(setSelectionType())); connect(m_ring_ellipse,SIGNAL(clicked()),this,SLOT(setSelectionType())); connect(m_ring_rectangle,SIGNAL(clicked()),this,SLOT(setSelectionType())); + connect(m_free_draw,SIGNAL(clicked()),this,SLOT(setSelectionType())); connect(m_edit,SIGNAL(clicked()),this,SLOT(setSelectionType())); // lay out the widgets @@ -404,13 +412,13 @@ void InstrumentWindowPickTab::setSelectionType() { m_selectionType = ErasePeak; m_activeTool->setText("Tool: Erase crystal peak(s)"); - surfaceMode = ProjectionSurface::EraseMode; + surfaceMode = ProjectionSurface::ErasePeakMode; } else if (m_rectangle->isChecked()) { m_selectionType = Draw; m_activeTool->setText("Tool: Rectangle"); - surfaceMode = ProjectionSurface::DrawMode; + surfaceMode = ProjectionSurface::DrawRegularMode; plotType = DetectorPlotController::Single; m_instrWindow->getSurface()->startCreatingShape2D("rectangle",Qt::green,QColor(255,255,255,80)); } @@ -418,7 +426,7 @@ void InstrumentWindowPickTab::setSelectionType() { m_selectionType = Draw; m_activeTool->setText("Tool: Ellipse"); - surfaceMode = ProjectionSurface::DrawMode; + surfaceMode = ProjectionSurface::DrawRegularMode; plotType = DetectorPlotController::Single; m_instrWindow->getSurface()->startCreatingShape2D("ellipse",Qt::green,QColor(255,255,255,80)); } @@ -426,7 +434,7 @@ void InstrumentWindowPickTab::setSelectionType() { m_selectionType = Draw; m_activeTool->setText("Tool: Elliptical ring"); - surfaceMode = ProjectionSurface::DrawMode; + surfaceMode = ProjectionSurface::DrawRegularMode; plotType = DetectorPlotController::Single; m_instrWindow->getSurface()->startCreatingShape2D("ring ellipse",Qt::green,QColor(255,255,255,80)); } @@ -434,15 +442,23 @@ void InstrumentWindowPickTab::setSelectionType() { m_selectionType = Draw; m_activeTool->setText("Tool: Rectangular ring"); - surfaceMode = ProjectionSurface::DrawMode; + surfaceMode = ProjectionSurface::DrawRegularMode; plotType = DetectorPlotController::Single; m_instrWindow->getSurface()->startCreatingShape2D("ring rectangle",Qt::green,QColor(255,255,255,80)); } + else if (m_free_draw->isChecked()) + { + m_selectionType = Draw; + m_activeTool->setText("Tool: Arbitrary shape"); + surfaceMode = ProjectionSurface::DrawFreeMode; + plotType = DetectorPlotController::Single; + m_instrWindow->getSurface()->startCreatingFreeShape(Qt::green,QColor(255,255,255,80)); + } else if (m_edit->isChecked()) { m_selectionType = Draw; m_activeTool->setText("Tool: Shape editing"); - surfaceMode = ProjectionSurface::DrawMode; + surfaceMode = ProjectionSurface::DrawRegularMode; plotType = DetectorPlotController::Single; } m_plotController->setPlotType( plotType ); @@ -451,7 +467,7 @@ void InstrumentWindowPickTab::setSelectionType() { surface->setInteractionMode( surfaceMode ); auto interactionMode = surface->getInteractionMode(); - if ( interactionMode == ProjectionSurface::DrawMode || interactionMode == ProjectionSurface::MoveMode ) + if ( interactionMode == ProjectionSurface::DrawRegularMode || interactionMode == ProjectionSurface::MoveMode ) { updatePlotMultipleDetectors(); } @@ -529,7 +545,7 @@ void InstrumentWindowPickTab::changedIntegrationRange(double,double) if ( surface ) { auto interactionMode = surface->getInteractionMode(); - if ( interactionMode == ProjectionSurface::DrawMode || interactionMode == ProjectionSurface::MoveMode ) + if ( interactionMode == ProjectionSurface::DrawRegularMode || interactionMode == ProjectionSurface::MoveMode ) { updatePlotMultipleDetectors(); } @@ -629,6 +645,8 @@ void InstrumentWindowPickTab::selectTool(const ToolType tool) break; case DrawEllipse: m_ellipse->setChecked(true); break; + case DrawFree: m_free_draw->setChecked(true); + break; case EditShape: m_edit->setChecked(true); break; default: throw std::invalid_argument("Invalid tool type."); @@ -668,7 +686,11 @@ void InstrumentWindowPickTab::updateSelectionInfoDisplay() */ void InstrumentWindowPickTab::shapeCreated() { + if ( !isVisible() ) return; + if (!m_free_draw->isChecked()) + { selectTool( EditShape ); + } } /** diff --git a/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowPickTab.h b/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowPickTab.h index ec169a6b904f917b7301cad5e29acf69816e44a8..5bcdf5c420fcbe6b22a105b1d72bd1e90bf8a399 100644 --- a/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowPickTab.h +++ b/MantidPlot/src/Mantid/InstrumentWidget/InstrumentWindowPickTab.h @@ -47,7 +47,7 @@ public: /// SelectPeak: click on a peak marker or draw a rubber-band selector to select peak /// markers. Selected peaks can be deleted by pressing the Delete key. enum SelectionType {Single=0,AddPeak,ErasePeak,SingleDetectorSelection,Tube, Draw}; - enum ToolType {Zoom,PixelSelect,TubeSelect,PeakSelect,PeakErase, DrawEllipse, DrawRectangle, EditShape}; + enum ToolType {Zoom,PixelSelect,TubeSelect,PeakSelect,PeakErase, DrawEllipse, DrawRectangle, DrawFree, EditShape}; InstrumentWindowPickTab(InstrumentWindow* instrWindow); bool canUpdateTouchedDetector()const; @@ -91,6 +91,7 @@ private: QPushButton *m_ellipse; ///< Button switching on drawing a elliptical selection region QPushButton *m_ring_ellipse; ///< Button switching on drawing a elliptical ring selection region QPushButton *m_ring_rectangle; ///< Button switching on drawing a rectangular ring selection region + QPushButton *m_free_draw; ///< Button switching on drawing a region of arbitrary shape QPushButton *m_edit; ///< Button switching on edditing the selection region bool m_plotSum; diff --git a/MantidPlot/src/Mantid/InstrumentWidget/ProjectionSurface.cpp b/MantidPlot/src/Mantid/InstrumentWidget/ProjectionSurface.cpp index cd8e0151e575838b1d05707d676ee92dc73ba563..1c6a0052da5fb529510990d2b153790c8ae3dee3 100644 --- a/MantidPlot/src/Mantid/InstrumentWidget/ProjectionSurface.cpp +++ b/MantidPlot/src/Mantid/InstrumentWidget/ProjectionSurface.cpp @@ -62,7 +62,7 @@ ProjectionSurface::ProjectionSurface(const InstrumentActor* rootActor): // create and connect the mask drawing input controller InputControllerDrawShape* drawController = new InputControllerDrawShape(this); - setInputController(DrawMode, drawController); + setInputController(DrawRegularMode, drawController); connect(drawController,SIGNAL(addShape(QString,int,int,QColor,QColor)),&m_maskShapes,SLOT(addShape(QString,int,int,QColor,QColor))); connect(this,SIGNAL(signalToStartCreatingShape2D(QString,QColor,QColor)),drawController,SLOT(startCreatingShape2D(QString,QColor,QColor))); connect(drawController,SIGNAL(moveRightBottomTo(int,int)),&m_maskShapes,SLOT(moveRightBottomTo(int,int))); @@ -70,7 +70,6 @@ ProjectionSurface::ProjectionSurface(const InstrumentActor* rootActor): connect(drawController,SIGNAL(selectCtrlAt(int,int)),&m_maskShapes,SLOT(addToSelectionShapeAt(int,int))); connect(drawController,SIGNAL(moveBy(int,int)),&m_maskShapes,SLOT(moveShapeOrControlPointBy(int,int))); connect(drawController,SIGNAL(touchPointAt(int,int)),&m_maskShapes,SLOT(touchShapeOrControlPointAt(int,int))); - connect(drawController,SIGNAL(disabled()),&m_maskShapes,SLOT(deselectAll())); connect(drawController,SIGNAL(removeSelectedShapes()),&m_maskShapes,SLOT(removeSelectedShapes())); connect(drawController,SIGNAL(deselectAll()),&m_maskShapes,SLOT(deselectAll())); connect(drawController,SIGNAL(restoreOverrideCursor()),&m_maskShapes,SLOT(restoreOverrideCursor())); @@ -78,9 +77,16 @@ ProjectionSurface::ProjectionSurface(const InstrumentActor* rootActor): connect(drawController,SIGNAL(finishSelection(QRect)),this,SLOT(selectMultipleMasks(QRect))); connect(drawController,SIGNAL(finishSelection(QRect)),this,SIGNAL(shapeChangeFinished())); + InputControllerDrawAndErase* freeDrawController = new InputControllerDrawAndErase(this); + setInputController(DrawFreeMode, freeDrawController); + connect(this,SIGNAL(signalToStartCreatingFreeShape(QColor,QColor)),freeDrawController,SLOT(startCreatingShape2D(QColor,QColor))); + connect(freeDrawController,SIGNAL(addShape(const QPolygonF&,QColor,QColor)),&m_maskShapes,SLOT(addFreeShape(const QPolygonF&,QColor,QColor))); + connect(freeDrawController,SIGNAL(draw(const QPolygonF&)),&m_maskShapes,SLOT(drawFree(const QPolygonF&))); + connect(freeDrawController,SIGNAL(erase(const QPolygonF&)),&m_maskShapes,SLOT(eraseFree(const QPolygonF&))); + // create and connect the peak eraser controller InputControllerErase* eraseController = new InputControllerErase(this); - setInputController(EraseMode, eraseController); + setInputController(ErasePeakMode, eraseController); connect(eraseController,SIGNAL(erase(QRect)),this,SLOT(erasePeaks(QRect))); } @@ -138,7 +144,7 @@ void ProjectionSurface::draw(MantidGLWidget *widget)const if ( m_viewChanged && ( m_redrawPicking || m_interactionMode == PickSingleMode || m_interactionMode == PickTubeMode - || m_interactionMode == DrawMode ) ) + || m_interactionMode == DrawRegularMode ) ) { draw(widget,true); m_redrawPicking = false; @@ -435,7 +441,7 @@ void ProjectionSurface::setInteractionMode(int mode) controller = m_inputControllers[m_interactionMode]; if ( !controller ) throw std::logic_error("Input controller doesn't exist."); controller->onEnabled(); - if ( mode != DrawMode ) + if ( mode != DrawRegularMode && mode != DrawFreeMode ) { m_maskShapes.deselectAll(); foreach(PeakOverlay* po, m_peakShapes) @@ -473,10 +479,13 @@ QString ProjectionSurface::getInfoText() const return "Move cursor over instrument to see detector information. "; case AddPeakMode: return "Click on a detector then click on the mini-plot to add a peak."; - case DrawMode: + case DrawRegularMode: return "Select a tool button to draw a new shape. " "Click on shapes to select. Click and move to edit."; - case EraseMode: + case DrawFreeMode: + return "Draw by holding the left button down. " + "Erase with the right button."; + case ErasePeakMode: return "Click and move the mouse to erase peaks. " "Rotate the wheel to resize the cursor."; } @@ -566,6 +575,11 @@ void ProjectionSurface::startCreatingShape2D(const QString& type,const QColor& b emit signalToStartCreatingShape2D(type,borderColor,fillColor); } +void ProjectionSurface::startCreatingFreeShape(const QColor& borderColor,const QColor& fillColor) +{ + emit signalToStartCreatingFreeShape(borderColor,fillColor); +} + /** * Return a combined list of peak parkers from all overlays * @param detID :: The detector ID of interest @@ -679,7 +693,7 @@ void ProjectionSurface::setShowPeakLabelsFlag(bool on) */ void ProjectionSurface::setSelectionRect(const QRect &rect) { - if ( m_interactionMode != DrawMode || !m_maskShapes.hasSelection() ) + if ( m_interactionMode != DrawRegularMode || !m_maskShapes.hasSelection() ) { m_selectRect = rect; } diff --git a/MantidPlot/src/Mantid/InstrumentWidget/ProjectionSurface.h b/MantidPlot/src/Mantid/InstrumentWidget/ProjectionSurface.h index 33b4a96a7badc9e2716a931a536476cbb9271d87..161484368e8dbf3a447f194382b48c04c4bf59c4 100644 --- a/MantidPlot/src/Mantid/InstrumentWidget/ProjectionSurface.h +++ b/MantidPlot/src/Mantid/InstrumentWidget/ProjectionSurface.h @@ -56,7 +56,16 @@ class ProjectionSurface: public QObject { Q_OBJECT public: - enum InteractionMode {MoveMode = 0, PickSingleMode, PickTubeMode, AddPeakMode, DrawMode, EraseMode, InteractionModeSize }; + enum InteractionMode { + MoveMode = 0, + PickSingleMode, + PickTubeMode, + AddPeakMode, + DrawRegularMode, + DrawFreeMode, + ErasePeakMode, + InteractionModeSize + }; /// Constructor ProjectionSurface(const InstrumentActor* rootActor); /// Destructor @@ -140,6 +149,11 @@ public: /// @param fillColor :: The fill color. void startCreatingShape2D(const QString& type,const QColor& borderColor,const QColor& fillColor = QColor()); + /// Initialize interactive creation of a free draw shape. + /// @param borderColor :: The color of the shape outline. + /// @param fillColor :: The fill color. + void startCreatingFreeShape(const QColor& borderColor,const QColor& fillColor = QColor()); + // Properties methods which allow the mask shapes to be modified with a property browser. /// Return a list of all properties of type double of the currently selected shape. @@ -200,6 +214,7 @@ signals: // shape manipulation void signalToStartCreatingShape2D(const QString& type,const QColor& borderColor,const QColor& fillColor); + void signalToStartCreatingFreeShape(const QColor& borderColor,const QColor& fillColor); void shapeCreated(); void shapeSelected(); void shapesDeselected(); diff --git a/MantidPlot/src/Mantid/InstrumentWidget/RectF.h b/MantidPlot/src/Mantid/InstrumentWidget/RectF.h index 264fa5172962f55ee9dca3f5bad3c2683710a96a..570edc68c82538e2d404db9e8c051ce5bc9fb47a 100644 --- a/MantidPlot/src/Mantid/InstrumentWidget/RectF.h +++ b/MantidPlot/src/Mantid/InstrumentWidget/RectF.h @@ -197,4 +197,16 @@ inline std::ostream& operator << (std::ostream& ostr, const RectF& rect) return ostr; } +inline std::ostream& operator << (std::ostream& ostr, const QRectF& rect) +{ + ostr << '[' << rect.left() << ',' << rect.right() << ';' << rect.top() << ',' << rect.bottom() << ']'; + return ostr; +} + +inline std::ostream& operator << (std::ostream& ostr, const QPointF& p) +{ + ostr << '(' << p.x() << ',' << p.y() << ')'; + return ostr; +} + #endif // RECTF_H diff --git a/MantidPlot/src/Mantid/InstrumentWidget/Shape2D.cpp b/MantidPlot/src/Mantid/InstrumentWidget/Shape2D.cpp index 9543dcdaf288d31f17ef511e964d9e0d3d0def7a..0a40a6ff188c653ab6e139072d4222438edba486 100644 --- a/MantidPlot/src/Mantid/InstrumentWidget/Shape2D.cpp +++ b/MantidPlot/src/Mantid/InstrumentWidget/Shape2D.cpp @@ -5,6 +5,9 @@ #include <QMouseEvent> #include <QWheelEvent> +#include <QLine> +#include <QMap> + #include <iostream> #include <algorithm> #include <stdexcept> @@ -487,3 +490,154 @@ void Shape2DRing::setColor(const QColor &color) m_outer_shape->setColor(color); } +//------------------------------------------------------------------------------ + +/// Construct a zero-sized shape. +Shape2DFree::Shape2DFree(const QPointF& p): + m_polygon(QRectF(p,p)) +{ + resetBoundingRect(); +} + +/// The shape can be selected if it contains the point. +bool Shape2DFree::selectAt(const QPointF& p)const +{ + return contains(p); +} + +/// Check if a point is inside the shape. +bool Shape2DFree::contains(const QPointF& p)const +{ + return m_polygon.containsPoint(p,Qt::OddEvenFill); +} + +/// Add to a larger shape. +void Shape2DFree::addToPath(QPainterPath& path) const +{ + path.addPolygon(m_polygon); +} + +/// Draw. +void Shape2DFree::drawShape(QPainter& painter) const +{ + QPainterPath path; + path.addPolygon(m_polygon); + painter.fillPath(path, m_fill_color); + painter.drawPath(m_outline); +} + +/// Rescale polygon's verices to fit to the new bounding rect. +void Shape2DFree::refit() +{ + auto brOld = getPolygonBoundingRect(); + auto &brNew = m_boundingRect; + if (brNew.xSpan() < 0.0) brNew.xFlip(); + if (brNew.ySpan() < 0.0) brNew.yFlip(); + + auto xs0 = brNew.x0(); + auto x0 = brOld.x0(); + auto xScale = brNew.width() / brOld.width(); + + auto ys0 = brNew.y0(); + auto y0 = brOld.y0(); + auto yScale = brNew.height() / brOld.height(); + for(int i = 0; i < m_polygon.size(); ++i) + { + auto &p = m_polygon[i]; + p.rx() = xs0 + xScale * (p.x() - x0); + p.ry() = ys0 + yScale * (p.y() - y0); + } + resetBoundingRect(); +} + +/// Recalculate the bounding rect. +/// Also make the new border outline. +/// QPolygonF cannot have holes or disjointed parts, +/// it's a single closed line. The outline (implemented as a QPainterPath) +/// makes it look like it have holes. +void Shape2DFree::resetBoundingRect() +{ + m_boundingRect = getPolygonBoundingRect(); + // Clear the outline path. + m_outline = QPainterPath(); + if (m_polygon.isEmpty()) return; + + // If the polygon has apparent holes/discontinuities + // it will have extra pairs of edges which we don't want + // to draw. + auto n = m_polygon.size() - 1; + // Find those vertices at which we must break the polygon + // to get rid of these extra edges. + QList<int> breaks; + breaks.push_back(0); + for(int i = 1; i < m_polygon.size() - 1; ++i) + { + auto p = m_polygon[i]; + auto j = m_polygon.indexOf(p, i + 1); + if (j != -1) + { + auto i1 = i + 1; + auto j1 = j - 1; + if (m_polygon[i1] == m_polygon[j1]) + { + breaks.push_back(i); + breaks.push_back(i1); + breaks.push_back(j1); + breaks.push_back(j); + } + } + } + if (breaks.back() != n) + { + breaks.push_back(n); + } + qSort(breaks); + + m_outline.moveTo(m_polygon[0]); + int j1 = 0; + // Add contiguous portions of the polygon to the outline + // and break at points from breaks list. + for(int i = 0; i < breaks.size(); ++i) + { + auto j = breaks[i]; + if (j == j1 + 1) + { + m_outline.moveTo(m_polygon[j]); + } + else + { + for(auto k = j1; k <= j; ++k) + { + m_outline.lineTo(m_polygon[k]); + } + } + j1 = j; + } +} + +/// Convert the bounding rect computed by QPolygonF to RectF +RectF Shape2DFree::getPolygonBoundingRect() const +{ + auto br = m_polygon.boundingRect(); + auto x0 = br.left(); + auto x1 = br.right(); + if (x0 > x1) std::swap(x0, x1); + auto y0 = br.bottom(); + auto y1 = br.top(); + if (y0 > y1) std::swap(y0, y1); + return RectF(QPointF(x0,y0), QPointF(x1,y1)); +} + +/// Add a polygon to this shape. +void Shape2DFree::addPolygon(const QPolygonF& polygon) +{ + m_polygon = m_polygon.united(polygon); + resetBoundingRect(); +} + +/// Subtract a polygon from this shape. +void Shape2DFree::subtractPolygon(const QPolygonF& polygon) +{ + m_polygon = m_polygon.subtracted(polygon); + resetBoundingRect(); +} diff --git a/MantidPlot/src/Mantid/InstrumentWidget/Shape2D.h b/MantidPlot/src/Mantid/InstrumentWidget/Shape2D.h index 9ab6d058a2001d0381f6127df001f56de3594d72..d1db694c15db59ffc266130b33aa33db730608c8 100644 --- a/MantidPlot/src/Mantid/InstrumentWidget/Shape2D.h +++ b/MantidPlot/src/Mantid/InstrumentWidget/Shape2D.h @@ -147,6 +147,13 @@ protected: bool m_visible; ///< flag to show or hide the shape }; +/** + * An ellipse with the axes parallel to the x and y axes on the screen. + * + * It has a QPointF property "center" defining the centre of the ellipse + * and double properties "radius1" and "radius2" equal to distances from + * the centre to the curve along the x and y axes. + */ class Shape2DEllipse: public Shape2D { public: @@ -169,6 +176,11 @@ protected: virtual void refit(){} }; +/** + * A axis aligned rectangle. + * + * No specific properties. + */ class Shape2DRectangle: public Shape2D { public: @@ -184,6 +196,13 @@ protected: virtual void refit(){} }; +/** + * A ring: area bounded by two curves of the same shape but different size. + * + * The constructor takes a curve shape and the ring widths in the x and y + * directions. + * It has QPointF "centre" property and "xwidth" and "ywidth" double properties. + */ class Shape2DRing: public Shape2D { public: @@ -216,5 +235,31 @@ protected: double m_yWidth; }; +/** + * An arbitrary shape. Implemented as a polygon. + * It can have disjointed parts and holes. + * + * No shape specific properties. + */ +class Shape2DFree: public Shape2D +{ +public: + Shape2DFree(const QPointF& p); + virtual Shape2D* clone()const{return new Shape2DFree(*this);} + virtual bool selectAt(const QPointF& p)const; + virtual bool contains(const QPointF& p)const; + virtual void addToPath(QPainterPath& path) const; + void addPolygon(const QPolygonF& polygon); + void subtractPolygon(const QPolygonF& polygon); +protected: + virtual void drawShape(QPainter& painter) const; + virtual void refit(); + virtual void resetBoundingRect(); +private: + RectF getPolygonBoundingRect() const; + QPolygonF m_polygon; ///< Implements the shape. + QPainterPath m_outline; ///< Object to draw the shape's border. +}; + #endif /*MANTIDPLOT_SHAPE2D_H_*/ diff --git a/MantidPlot/src/Mantid/InstrumentWidget/Shape2DCollection.cpp b/MantidPlot/src/Mantid/InstrumentWidget/Shape2DCollection.cpp index 7ccdfae2720dc618b08d36661f890ae3138ae61a..dcbb04b15d4e764292ac601c3cffcda4c42b11ae 100644 --- a/MantidPlot/src/Mantid/InstrumentWidget/Shape2DCollection.cpp +++ b/MantidPlot/src/Mantid/InstrumentWidget/Shape2DCollection.cpp @@ -204,6 +204,10 @@ Shape2D* Shape2DCollection::createShape(const QString& type,int x,int y) const { return new Shape2DRectangle(p,QSizeF(0,0)); } + else if (type.toLower() == "free") + { + return new Shape2DFree(p); + } QStringList complexType = type.split(' ',QString::SkipEmptyParts); @@ -735,3 +739,48 @@ void Shape2DCollection::changeBorderColor(const QColor &color) } } +/** + * Add a Shape2D object allowing free drawing. + * @param poly :: Initial shape. + * @param borderColor :: The border colour. + * @param fillColor :: The fill colour. + */ +void Shape2DCollection::addFreeShape(const QPolygonF& poly,const QColor& borderColor,const QColor& fillColor) +{ + auto freeShape = dynamic_cast<Shape2DFree*>(m_currentShape); + if (!freeShape) + { + if (poly.isEmpty()) throw std::logic_error("Cannot create a shape from empty polygon."); + auto p = m_transform.inverted().map(poly[0]); + addShape("free", static_cast<int>(p.x()), static_cast<int>(p.y()), borderColor, fillColor); + } + drawFree(poly); +} + +/** + * Draw the shape by adding a polygon to it. + */ +void Shape2DCollection::drawFree(const QPolygonF& polygon) +{ + auto freeShape = dynamic_cast<Shape2DFree*>(m_currentShape); + if (freeShape) + { + auto transform = m_transform.inverted(); + freeShape->addPolygon(transform.map(polygon)); + emit shapeChanged(); + } +} + +/** + * Erase part of the shape by subtracting a polygon from it. + */ +void Shape2DCollection::eraseFree(const QPolygonF& polygon) +{ + auto freeShape = dynamic_cast<Shape2DFree*>(m_currentShape); + if (freeShape) + { + auto transform = m_transform.inverted(); + freeShape->subtractPolygon(transform.map(polygon)); + emit shapeChanged(); + } +} diff --git a/MantidPlot/src/Mantid/InstrumentWidget/Shape2DCollection.h b/MantidPlot/src/Mantid/InstrumentWidget/Shape2DCollection.h index ed62362b346e512483106b443c0753e103618b01..53ca3567f83948faeadff5a8d4c1543f02bca995 100644 --- a/MantidPlot/src/Mantid/InstrumentWidget/Shape2DCollection.h +++ b/MantidPlot/src/Mantid/InstrumentWidget/Shape2DCollection.h @@ -88,6 +88,7 @@ signals: public slots: void addShape(const QString& type,int x,int y,const QColor& borderColor,const QColor& fillColor); + void addFreeShape(const QPolygonF&,const QColor& borderColor,const QColor& fillColor); void deselectAll(); void moveRightBottomTo(int,int); void selectShapeOrControlPointAt(int x,int y); @@ -96,6 +97,8 @@ public slots: void touchShapeOrControlPointAt(int x,int y); void removeSelectedShapes(); void restoreOverrideCursor(); + void drawFree(const QPolygonF& polygon); + void eraseFree(const QPolygonF& polygon); protected: virtual void drawShape(QPainter& ) const{} // never called diff --git a/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/InputController.h b/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/InputController.h index 6c2b3ab4330b8d0449a41efd133838c1cdd18e17..7d9e9547ac954f6f44286ed5227efabd958fe6d9 100644 --- a/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/InputController.h +++ b/MantidQt/MantidWidgets/inc/MantidQtMantidWidgets/InputController.h @@ -5,6 +5,7 @@ #include <QObject> #include <QRect> #include <QColor> +#include <QPolygonF> class QMouseEvent; class QWheelEvent; @@ -31,6 +32,7 @@ class EXPORT_OPT_MANTIDQT_MANTIDWIDGETS InputController : public QObject { Q_OBJECT public: explicit InputController(QObject *parent, bool contextAllowed = true); + virtual ~InputController(){} virtual void mousePressEvent(QMouseEvent *) {} virtual void mouseMoveEvent(QMouseEvent *) {} @@ -197,37 +199,100 @@ private: }; /** - Controller for moving the instrument on an unwrapped surface. + Controller for free drawing on an unwrapped surface. */ -class EXPORT_OPT_MANTIDQT_MANTIDWIDGETS InputControllerErase : public InputController { +class EXPORT_OPT_MANTIDQT_MANTIDWIDGETS InputControllerDraw : public InputController { Q_OBJECT public: - InputControllerErase(QObject *parent); - ~InputControllerErase(); + InputControllerDraw(QObject *parent); + ~InputControllerDraw(); virtual void mousePressEvent(QMouseEvent *); virtual void mouseMoveEvent(QMouseEvent *); virtual void mouseReleaseEvent(QMouseEvent *); virtual void wheelEvent(QWheelEvent *); - virtual void onPaint(QPainter &); virtual void enterEvent(QEvent *); virtual void leaveEvent(QEvent *); -signals: - void erase(const QRect &); +protected: + int cursorSize() const {return m_size;} + bool isLeftButtonPressed() const {return m_isLeftButtonPressed;} + bool isRightButtonPressed() const {return m_isRightButtonPressed;} + bool isActive() const {return m_isActive;} private: - void drawCursor(); + void redrawCursor(); + virtual void signalLeftClick() = 0; + virtual void signalRightClick(); + virtual void drawCursor(QPixmap *cursor) = 0; + virtual void setPosition(const QPoint &pos) = 0; + virtual void resize() = 0; const int m_max_size; - int m_size; ///< Size of the eraser - bool m_isButtonPressed; + int m_size; ///< Size of the cursor + bool m_isLeftButtonPressed; + bool m_isRightButtonPressed; bool m_isActive; - QRect m_rect; QPixmap *m_cursor; +}; + +/** + Controller for erasing peaks on an unwrapped surface. + */ +class EXPORT_OPT_MANTIDQT_MANTIDWIDGETS InputControllerErase : public InputControllerDraw { + Q_OBJECT + +public: + InputControllerErase(QObject *parent); + ~InputControllerErase(); + void onPaint(QPainter &); + +signals: + void erase(const QRect &); + +private: + void drawCursor(QPixmap *cursor); + void signalLeftClick(); + void setPosition(const QPoint &pos); + void resize(); + + QRect m_rect; QPixmap *m_image; }; + +/** + Controller for drawing and erasing arbitrary shapes on an unwrapped surface. + */ +class EXPORT_OPT_MANTIDQT_MANTIDWIDGETS InputControllerDrawAndErase : public InputControllerDraw { + Q_OBJECT + +public: + InputControllerDrawAndErase(QObject *parent); + +signals: + void draw(const QPolygonF &); + void erase(const QPolygonF &); + void addShape(const QPolygonF &poly, const QColor &borderColor, + const QColor &fillColor); + +public slots: + void startCreatingShape2D(const QColor &borderColor, const QColor &fillColor); + +private: + void drawCursor(QPixmap *cursor); + void signalLeftClick(); + void signalRightClick(); + void setPosition(const QPoint &pos); + void resize(); + void makePolygon(); + + QPoint m_pos; + QPolygonF m_rect; + QColor m_borderColor, m_fillColor; + bool m_creating; +}; + } } diff --git a/MantidQt/MantidWidgets/src/InputController.cpp b/MantidQt/MantidWidgets/src/InputController.cpp index b707dea7b2dd8c6e402b5d0c3b5745f80c846bec..afc5d3d445b33e9739ee4fc1cb6b667c42e17455 100644 --- a/MantidQt/MantidWidgets/src/InputController.cpp +++ b/MantidQt/MantidWidgets/src/InputController.cpp @@ -6,7 +6,7 @@ #include <QCursor> #include <QApplication> -#include <iostream> +#include <cmath> namespace MantidQt { namespace MantidWidgets { @@ -315,112 +315,250 @@ void InputControllerMoveUnwrapped::mouseReleaseEvent(QMouseEvent *) /** * Constructor. */ -InputControllerErase::InputControllerErase(QObject *parent): +InputControllerDraw::InputControllerDraw(QObject *parent): InputController(parent), m_max_size(32), m_size(30), -m_isButtonPressed(false), +m_isLeftButtonPressed(false), +m_isRightButtonPressed(false), m_isActive(false), -m_rect( 0, 0, m_size, m_size ) +m_cursor(NULL) { - m_cursor = new QPixmap(m_max_size,m_max_size); - drawCursor(); - m_image = new QPixmap(":/PickTools/eraser.png"); } -InputControllerErase::~InputControllerErase() +InputControllerDraw::~InputControllerDraw() { delete m_cursor; - delete m_image; } /** * Process the mouse press event. */ -void InputControllerErase::mousePressEvent(QMouseEvent *event) +void InputControllerDraw::mousePressEvent(QMouseEvent *event) { m_isActive = true; - m_rect.moveTopLeft(QPoint(event->x(),event->y())); + setPosition(QPoint(event->x(),event->y())); if (event->button() == Qt::LeftButton) { - m_isButtonPressed = true; - emit erase( m_rect ); + m_isLeftButtonPressed = true; + signalLeftClick(); + } + else if (event->button() == Qt::RightButton) + { + m_isRightButtonPressed = true; + signalRightClick(); } } /** * Process the mouse move event. */ -void InputControllerErase::mouseMoveEvent(QMouseEvent *event) +void InputControllerDraw::mouseMoveEvent(QMouseEvent *event) { m_isActive = true; - m_rect.moveTopLeft(QPoint(event->x(),event->y())); - if ( m_isButtonPressed ) + setPosition(QPoint(event->x(),event->y())); + if ( m_isLeftButtonPressed ) + { + signalLeftClick(); + } + else if ( m_isRightButtonPressed ) { - emit erase( m_rect ); + signalRightClick(); } } /** * Process the mouse button release event. */ -void InputControllerErase::mouseReleaseEvent(QMouseEvent *) +void InputControllerDraw::mouseReleaseEvent(QMouseEvent *event) { - m_isButtonPressed = false; + if (event->button() == Qt::LeftButton) + { + m_isLeftButtonPressed = false; + } + else if (event->button() == Qt::RightButton) + { + m_isRightButtonPressed = false; + } } -void InputControllerErase::wheelEvent(QWheelEvent *event) +void InputControllerDraw::wheelEvent(QWheelEvent *event) { int d = m_size + ( event->delta() > 0 ? 4 : -4 ); if ( d > 2 && d < m_max_size ) { m_size = d; - drawCursor(); + resize(); + redrawCursor(); QApplication::restoreOverrideCursor(); QApplication::setOverrideCursor(QCursor( *m_cursor, 0, 0 )); } } +void InputControllerDraw::enterEvent(QEvent *) +{ + redrawCursor(); + QApplication::setOverrideCursor(QCursor( *m_cursor, 0, 0 )); + m_isActive = true; +} + +void InputControllerDraw::leaveEvent(QEvent *) +{ + QApplication::restoreOverrideCursor(); + m_isActive = false; +} + +void InputControllerDraw::redrawCursor() +{ + if (!m_cursor) + { + m_cursor = new QPixmap(m_max_size,m_max_size); + } + drawCursor(m_cursor); +} + +void InputControllerDraw::signalRightClick() +{ +} + +//-------------------------------------------------------------------------------- + +InputControllerErase::InputControllerErase(QObject *parent): InputControllerDraw(parent), + m_rect( 0, 0, cursorSize(), cursorSize() ) +{ + m_image = new QPixmap(":/PickTools/eraser.png"); +} + +InputControllerErase::~InputControllerErase() +{ + delete m_image; +} + +void InputControllerErase::signalLeftClick() +{ + emit erase(m_rect); +} + void InputControllerErase::onPaint(QPainter& painter) { - if ( m_isActive && !m_isButtonPressed ) + if ( isActive() && !isLeftButtonPressed() ) { painter.drawPixmap(m_rect.bottomRight(),*m_image); } } -void InputControllerErase::enterEvent(QEvent *) +void InputControllerErase::drawCursor(QPixmap *cursor) { - QApplication::setOverrideCursor(QCursor( *m_cursor, 0, 0 )); - m_isActive = true; + cursor->fill(QColor(255,255,255,0)); + QPainter painter( cursor ); + auto size = cursorSize(); + + auto pen = QPen(Qt::DashLine); + QVector<qreal> dashPattern; + dashPattern << 4 << 4; + pen.setDashPattern(dashPattern); + pen.setColor(QColor(0,0,0)); + painter.setPen(pen); + painter.drawRect( QRect( 0, 0, size, size ) ); + + pen.setColor(QColor(255,255,255)); + pen.setDashOffset(4); + painter.setPen(pen); + painter.drawRect( QRect( 0, 0, size, size ) ); } -void InputControllerErase::leaveEvent(QEvent *) +void InputControllerErase::setPosition(const QPoint &pos) { - QApplication::restoreOverrideCursor(); - m_isActive = false; + m_rect.moveTopLeft(pos); +} + +void InputControllerErase::resize() +{ + auto size = cursorSize(); + m_rect.setSize( QSize(size, size) ); +} + +//-------------------------------------------------------------------------------- + +InputControllerDrawAndErase::InputControllerDrawAndErase(QObject *parent): InputControllerDraw(parent), + m_pos(0,0), m_rect(8), m_creating(false) +{ + makePolygon(); +} + +void InputControllerDrawAndErase::makePolygon() +{ + auto r = double(cursorSize()) / 2.0; + double a = 2.0 * M_PI / double(m_rect.size()); + for(int i = 0; i < m_rect.size(); ++i) + { + double ia = double(i) * a; + auto x = r + static_cast<int>(r * cos(ia)); + auto y = r + static_cast<int>(r * sin(ia)); + m_rect[i] = QPointF(x, y); + } +} + +void InputControllerDrawAndErase::signalLeftClick() +{ + auto poly = m_rect.translated(m_pos); + if (m_creating) + { + m_creating = false; + emit addShape(poly, m_borderColor, m_fillColor); + } + else + { + emit draw(poly); + } +} + +void InputControllerDrawAndErase::signalRightClick() +{ + auto poly = m_rect.translated(m_pos); + emit erase(poly); } -void InputControllerErase::drawCursor() +void InputControllerDrawAndErase::drawCursor(QPixmap *cursor) { - m_cursor->fill(QColor(255,255,255,0)); - QPainter painter( m_cursor ); + cursor->fill(QColor(255,255,255,0)); + QPainter painter( cursor ); + + auto bRect = m_rect.boundingRect(); + auto poly = m_rect.translated(-bRect.topLeft()); auto pen = QPen(Qt::DashLine); QVector<qreal> dashPattern; - dashPattern << 4 << 4; + qreal dashLength = cursorSize() < 10 ? 1 : 2; + dashPattern << dashLength << dashLength; pen.setDashPattern(dashPattern); pen.setColor(QColor(0,0,0)); painter.setPen(pen); - painter.drawRect( QRect( 0, 0, m_size, m_size ) ); + painter.drawPolygon(poly); pen.setColor(QColor(255,255,255)); - pen.setDashOffset(4); + pen.setDashOffset(dashLength); painter.setPen(pen); - painter.drawRect( QRect( 0, 0, m_size, m_size ) ); + painter.drawPolygon(poly); +} - m_rect.setSize( QSize(m_size,m_size) ); +void InputControllerDrawAndErase::setPosition(const QPoint &pos) +{ + m_pos = pos; +} + +void InputControllerDrawAndErase::resize() +{ + makePolygon(); } + +void InputControllerDrawAndErase::startCreatingShape2D(const QColor &borderColor, const QColor &fillColor) +{ + m_borderColor = borderColor; + m_fillColor = fillColor; + m_creating = true; +} + } } diff --git a/images/brush.png b/images/brush.png new file mode 100644 index 0000000000000000000000000000000000000000..328d01196160c1bc534ac5bada51431445c612e6 Binary files /dev/null and b/images/brush.png differ diff --git a/images/images.qrc b/images/images.qrc index d68c46cd1be169a22d4f1458ecf80e0c6898c893..f574b14d849722bdb4ae85bdc201873be4d0d521 100644 --- a/images/images.qrc +++ b/images/images.qrc @@ -67,6 +67,7 @@ <file>eraser.png</file> <file>selection-box-ring.png</file> <file>selection-circle-ring.png</file> + <file>brush.png</file> </qresource> <qresource prefix="/MaskTools"> <file>selection-circle.png</file> @@ -74,5 +75,6 @@ <file>selection-box-ring.png</file> <file>selection-circle-ring.png</file> <file>selection-edit.png</file> + <file>brush.png</file> </qresource> </RCC>