-
Nick Draper authoredNick Draper authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
LineOverlay.cpp 18.83 KiB
#include "MantidQtSliceViewer/LineOverlay.h"
#include <qwt_plot.h>
#include <qwt_plot_canvas.h>
#include <iostream>
#include <qpainter.h>
#include <QRect>
#include <QShowEvent>
#include "MantidKernel/Utils.h"
using namespace Mantid::Kernel;
namespace MantidQt
{
namespace SliceViewer
{
//----------------------------------------------------------------------------------------------
/** Constructor
*/
LineOverlay::LineOverlay(QwtPlot * plot, QWidget * parent)
: QWidget( parent ),
m_plot(plot),
m_snapEnabled(false),
m_snapX(0.1), m_snapY(0.1), m_snapLength(0),
m_shown(true), m_showHandles(true), m_showLine(true),
m_angleSnapMode(false), m_angleSnap(45)
{
m_creation = true; // Will create with the mouse
m_rightButton = false;
m_dragHandle = HandleNone;
m_pointA = QPointF(0.0, 0.0);
m_pointB = QPointF(1.0, 1.0);
m_width = 0.1;
//setAttribute(Qt::WA_TransparentForMouseEvents);
// We need mouse events all the time
setMouseTracking(true);
//setAttribute(Qt::WA_TransparentForMouseEvents);
// Make sure mouse propagates
setAttribute(Qt::WA_NoMousePropagation, false);
}
//----------------------------------------------------------------------------------------------
/** Destructor
*/
LineOverlay::~LineOverlay()
{
}
//----------------------------------------------------------------------------------------------
/** Reset the line. User will have to click to create it */
void LineOverlay::reset()
{
m_creation = true; // Will create with the mouse
m_rightButton = false;
m_dragHandle = HandleNone;
this->update();
}
//----------------------------------------------------------------------------------------------
/** Set point A's position
* @param pointA :: plot coordinates */
void LineOverlay::setPointA(QPointF pointA)
{
m_pointA = pointA;
this->update(); //repaint
emit lineChanging(m_pointA, m_pointB, m_width);
}
/** Set point B's position
* @param pointB :: plot coordinates */
void LineOverlay::setPointB(QPointF pointB)
{
m_pointB = pointB;
this->update(); //repaint
emit lineChanging(m_pointA, m_pointB, m_width);
}
/** Set the width of integration
* @param width :: in plot coordinates */
void LineOverlay::setWidth(double width)
{
m_width = width;
this->update(); //repaint
emit lineChanging(m_pointA, m_pointB, m_width);
}
//----------------------------------------------------------------------------------------------
/** Set the snap-to-grid spacing in the X direction.
* @param spacing :: spacing */
void LineOverlay::setSnapX(double spacing)
{
m_snapX = spacing;
}
/** Set the snap-to-grid spacing in the Y direction.
* @param spacing :: spacing */
void LineOverlay::setSnapY(double spacing)
{
m_snapY = spacing;
}
/** Set the snap-to-grid spacing in both directions.
* @param spacing :: spacing */
void LineOverlay::setSnap(double spacing)
{
m_snapX = spacing;
m_snapY = spacing;
}
/** Set the snap-to-grid spacing in the Y direction.
* @param enabled :: enable spacing */
void LineOverlay::setSnapEnabled(bool enabled)
{
m_snapEnabled = enabled;
}
/** Set the snap-to-line-length spacing
* @param spacing :: spacing */
void LineOverlay::setSnapLength(double spacing)
{
m_snapLength = spacing;
}
/** Sets whether any of the control is visible
* @param shown :: if false, the the control is not drawng */
void LineOverlay::setShown(bool shown)
{
m_shown = shown;
}
/** Sets whether to show the mouse handles
* @param shown :: if false, the mouse handles are invisible */
void LineOverlay::setShowHandles(bool shown)
{
m_showHandles = shown;
}
/** Sets whether to show the central line
* @param shown :: if false, the central line is invisible */
void LineOverlay::setShowLine(bool shown)
{
m_showLine = shown;
}
/** Sets whether we are in "creation" mode
* For use by python directly specifying the position
*
* @param creation :: True for creation mode.
*/
void LineOverlay::setCreationMode(bool creation)
{
m_creation = creation;
this->update();
}
//----------------------------------------------------------------------------------------------
/** Turn angle snap on/off
* @param angleSnap :: true for always angle snap. */
void LineOverlay::setAngleSnapMode(bool angleSnap)
{
m_angleSnapMode = angleSnap;
}
/** Sets the angle increments to snap to.
* @param snapDegrees :: snap amount, in degrees */
void LineOverlay::setAngleSnap(double snapDegrees)
{
m_angleSnap = snapDegrees;
}
//----------------------------------------------------------------------------------------------
/// @return point A's position in plot coordinates
const QPointF & LineOverlay::getPointA() const
{ return m_pointA; }
/// @return point B's position in plot coordinates
const QPointF & LineOverlay::getPointB() const
{ return m_pointB; }
/// @return width of the line in plot coordinates
double LineOverlay::getWidth() const
{ return m_width; }
//----------------------------------------------------------------------------------------------
/// Return the recommended size of the widget
QSize LineOverlay::sizeHint() const
{
//TODO: Is there a smarter way to find the right size?
return QSize(20000, 20000);
// Always as big as the canvas
//return m_plot->canvas()->size();
}
QSize LineOverlay::size() const
{ return m_plot->canvas()->size(); }
int LineOverlay::height() const
{ return m_plot->canvas()->height(); }
int LineOverlay::width() const
{ return m_plot->canvas()->width(); }
//----------------------------------------------------------------------------------------------
/** Tranform from plot coordinates to pixel coordinates
* @param coords :: coordinate point in plot coordinates
* @return pixel coordinates */
QPoint LineOverlay::transform(QPointF coords) const
{
int xA = m_plot->transform( QwtPlot::xBottom, coords.x() );
int yA = m_plot->transform( QwtPlot::yLeft, coords.y() );
return QPoint(xA, yA);
}
//----------------------------------------------------------------------------------------------
/** Inverse transform: from pixels to plot coords
* @param pixels :: location in pixels
* @return plot coordinates (float) */
QPointF LineOverlay::invTransform(QPoint pixels) const
{
double xA = m_plot->invTransform( QwtPlot::xBottom, pixels.x() );
double yA = m_plot->invTransform( QwtPlot::yLeft, pixels.y() );
return QPointF(xA, yA);
}
//----------------------------------------------------------------------------------------------
/** Snap a point to the grid (if enabled)
* @param original :: original point
* @return snapped to grid in either or both dimensions */
QPointF LineOverlay::snap(QPointF original) const
{
if (!m_snapEnabled)
return original;
else
{
QPointF out = original;
// Snap to grid
if (m_snapX > 0)
out.setX( Utils::rounddbl(out.x()/m_snapX) * m_snapX);
if (m_snapY > 0)
out.setY( Utils::rounddbl(out.y()/m_snapY) * m_snapY);
return out;
}
}
//----------------------------------------------------------------------------------------------
/** Draw a handle (for dragging) at the given plot coordinates */
QRect LineOverlay::drawHandle(QPainter & painter, QPointF coords, QColor brush)
{
int size = 8;
QPoint center = transform(coords);
QRect marker(center.x()-size/2, center.y()-size/2, size, size);
if (this->m_showHandles)
{
painter.setPen(QColor(255,0,0));
painter.setBrush(brush);
painter.drawRect(marker);
}
return marker;
}
//----------------------------------------------------------------------------------------------
/// Paint the overlay
void LineOverlay::paintEvent(QPaintEvent * /*event*/)
{
// Don't paint until created
// Also, don't paint while right-click dragging (panning) the underlying pic
if (m_creation || m_rightButton || !m_shown)
return;
QPainter painter(this);
// int r = rand() % 255;
// int g = rand() % 255;
// int b = rand() % 255;
// painter.setBrush(QBrush(QColor(r,g,b)));
QPointF diff = m_pointB - m_pointA;
// Angle of the "width" perpendicular to the line
double angle = atan2(diff.y(), diff.x()) + M_PI / 2.0;
QPointF widthOffset( m_width * cos(angle), m_width * sin(angle) );
// Rectangle with a rotation
QPointF pA1 = m_pointA + widthOffset;
QPointF pA2 = m_pointA - widthOffset;
QPointF pB1 = m_pointB + widthOffset;
QPointF pB2 = m_pointB - widthOffset;
QPen boxPenLight(QColor(255,255,255, 200));
QPen boxPenDark(QColor(0,0,0, 200));
QPen centerPen(QColor(192,192,192, 128));
// Special XOR pixel drawing
//painter.setCompositionMode( QPainter::RasterOp_SourceXorDestination ); // RHEL5 has an old version of QT?
boxPenLight.setDashPattern( QVector<qreal>() << 5 << 5 );
boxPenDark.setDashPattern( QVector<qreal>() << 0 << 5 << 5 << 0 );
// --- Draw the box ---
boxPenLight.setWidthF(1.0);
boxPenDark.setWidthF(1.0);
// QPoint points[4] = {transform(pA1), transform(pB1), transform(pB2), transform(pA2)};
// painter.drawPolygon(points, 4);
painter.setPen(boxPenLight);
painter.drawLine(transform(pA1), transform(pB1));
painter.drawLine(transform(pB1), transform(pB2));
painter.drawLine(transform(pA2), transform(pB2));
painter.drawLine(transform(pA2), transform(pA1));
painter.setPen(boxPenDark);
painter.drawLine(transform(pA1), transform(pB1));
painter.drawLine(transform(pB1), transform(pB2));
painter.drawLine(transform(pA2), transform(pB2));
painter.drawLine(transform(pA2), transform(pA1));
// Go back to normal drawing mode
painter.setCompositionMode( QPainter::CompositionMode_SourceOver );
// --- Draw the central line ---
if (m_showLine)
{
centerPen.setWidth(2);
centerPen.setCapStyle(Qt::FlatCap);
painter.setPen(centerPen);
painter.drawLine(transform(m_pointA), transform(m_pointB));
}
// --- Draw and store the rects of the 4 handles ---
m_handles.clear();
m_handles.push_back(drawHandle(painter, m_pointA, QColor(0,0,0) ));
m_handles.push_back(drawHandle(painter, m_pointB, QColor(255,255,255) ));
m_handles.push_back(drawHandle(painter, (m_pointA + m_pointB)/2 + widthOffset, QColor(0,255,255)) );
m_handles.push_back(drawHandle(painter, (m_pointA + m_pointB)/2 - widthOffset, QColor(0,255,255)) );
}
//==============================================================================================
//================================= MOUSE HANDLING =============================================
//==============================================================================================
//-----------------------------------------------------------------------------------------------
/** Return the handle ID over which the mouse is
* @param pos :: position in pixels of mouse */
LineOverlay::eHandleID LineOverlay::mouseOverHandle(QPoint pos)
{
for (int i=0; i < m_handles.size(); i++)
{
if (m_handles[i].contains(pos))
{
return eHandleID(i);
}
}
if (this->mouseOverCenter(pos))
return HandleCenter;
else
return HandleNone;
}
///@return true if value is between A and B. A can be < or > then B.
bool isBetween(double value, double A, double B)
{
if (B > A)
return (value >= A && value <= B);
else if (B < A)
return (value >= B && value <= A);
else
return (value == A);
}
/** Return true if the mouse is over the central line
* @param pos :: position in pixels of mouse */
bool LineOverlay::mouseOverCenter(QPoint pos)
{
// Mouse position in pixels
QPointF current = pos;
// Find the distance (in pixels) between the mouse and the center of the line
QPointF pixA = transform(m_pointA);
QPointF pixB = transform(m_pointB);
QPointF diff = pixB - pixA;
double distance = fabs( diff.x()*(current.y()-pixA.y()) - (current.x() - pixA.x())*diff.y() )
/ sqrt(diff.x()*diff.x() + diff.y()*diff.y());
// Margin of 6 pixels, and must be between the 2 limits (if the limits are not the same)
bool retval = false;
if (distance < 7)
{
retval = true;
if ((pixA.x() != pixB.x()) && (!isBetween( current.x(), pixA.x(), pixB.x())))
{
retval = false;
}
if ((pixA.y() != pixB.y()) && (!isBetween( current.y(), pixA.y(), pixB.y())))
{
retval = false;
}
}
return retval;
}
//-----------------------------------------------------------------------------------------------
/** Handle the mouse move event when the line is being dragged
* @param event mouse event info */
void LineOverlay::handleDrag(QMouseEvent * event)
{
// Is the shift key pressed?
bool shiftPressed = (event->modifiers() & Qt::ShiftModifier);
// Currently dragging!
QPointF current = this->invTransform( event->pos() );
QPointF currentSnap = this->snap(current);
QPointF diff = m_pointB - m_pointA;
QPointF dragAmount = current - this->m_dragStart;
dragAmount = snap(dragAmount);
double width = 0;
// Adjust the current mouse position if needed.
if ((m_snapLength > 0) || shiftPressed || m_angleSnapMode)
{
// This is the distance between the fixed and dragged point
QPointF currentDiff;
if (m_dragHandle == HandleA)
currentDiff = current - m_pointB;
else if (m_dragHandle == HandleB)
currentDiff = current - m_pointA;
// Limit angles to 45 degree increments with shift pressed
double angle = atan2(currentDiff.y(), currentDiff.x());
// Round angle to closest 45 degrees, if in angle snap mode
if (shiftPressed || m_angleSnapMode)
{
// Convert the snap angle from degrees to radians
double angleSnapRad = m_angleSnap / (180.0 / M_PI);
angle = Utils::rounddbl(angle / angleSnapRad) * angleSnapRad;
}
// Round length to m_snapLength, if specified
double length = sqrt(currentDiff.x()*currentDiff.x() + currentDiff.y()*currentDiff.y());
if (m_snapLength > 0)
length = Utils::rounddbl(length / m_snapLength) * m_snapLength;
// Rebuild the mouse position
currentDiff = QPointF( cos(angle) * length, sin(angle) * length);
if (m_dragHandle == HandleA)
currentSnap = snap(m_pointB + currentDiff);
else if (m_dragHandle == HandleB)
currentSnap = snap(m_pointA + currentDiff);
}
switch (m_dragHandle)
{
case HandleA:
setPointA(currentSnap);
break;
case HandleB:
setPointB(currentSnap);
break;
case HandleWidthBottom:
case HandleWidthTop:
// Find the distance between the mouse and the line (see http://mathworld.wolfram.com/Point-LineDistance2-Dimensional.html )
width = fabs( diff.x()*(current.y()-m_pointA.y()) - (current.x() - m_pointA.x())*diff.y() )
/ sqrt(diff.x()*diff.x() + diff.y()*diff.y());
setWidth(width);
break;
case HandleCenter:
// Move the whole line around
m_pointA = m_dragStart_PointA + dragAmount;
m_pointB = m_dragStart_PointB + dragAmount;
this->update();
emit lineChanging(m_pointA, m_pointB, m_width);
break;
default:
break;
}
}
//-----------------------------------------------------------------------------------------------
/** Event when the mouse moves
* @param event mouse event info */
void LineOverlay::mouseMoveEvent(QMouseEvent * event)
{
if (event->buttons() & Qt::RightButton)
m_rightButton = true;
// Do not respond to mouse when hidden
if (!m_showHandles || !m_shown)
{
event->ignore();
return;
}
// --- Initial creation mode - wait for first click ----
if (m_creation)
{
// TODO: Custom mouse cursor?
this->setCursor(Qt::PointingHandCursor);
// Pass-through event to underlying widget, so that it shows the mouse position
event->ignore();
return;
}
// --- Initial creation mode ----
if (m_dragHandle != HandleNone)
{
this->handleDrag(event);
}
else
{
// ---- Just moving the mouse -------------
if (event->buttons() == Qt::NoButton)
{
LineOverlay::eHandleID hdl = mouseOverHandle(event->pos());
switch (hdl)
{
case HandleA:
case HandleB:
this->setCursor(Qt::SizeHorCursor);
break;
case HandleWidthBottom:
case HandleWidthTop:
this->setCursor(Qt::SizeVerCursor);
break;
case HandleCenter:
this->setCursor(Qt::PointingHandCursor);
break;
default:
this->setCursor(Qt::CrossCursor);
break;
}
}
}
// In all cases, we pass-through the event to underlying widget,
// so that it shows the mouse position.
event->ignore();
}
//-----------------------------------------------------------------------------------------------
/** Event when the mouse button is pressed down
* @param event mouse event info */
void LineOverlay::mousePressEvent(QMouseEvent * event)
{
// Do not respond to mouse when hidden
if (!m_showHandles || !m_shown)
{
event->ignore();
return;
}
// First left-click = create!
if (m_creation && (event->buttons() & Qt::LeftButton))
{
QPointF pt = snap(this->invTransform( event->pos() ));
m_pointB = pt;
setPointA(pt);
// And now we are in drag B mode
m_creation = false;
m_dragStart = pt;
m_dragHandle = HandleB;
return;
}
LineOverlay::eHandleID hdl = mouseOverHandle(event->pos());
// Drag with the left mouse button
if (hdl != HandleNone && (event->buttons() & Qt::LeftButton))
{
// Start dragging
m_dragHandle = hdl;
m_dragStart = this->invTransform( event->pos() );
m_dragStart_PointA = m_pointA;
m_dragStart_PointB = m_pointB;
}
else
// Pass-through event to underlying widget if not over a marker
event->ignore();
}
//-----------------------------------------------------------------------------------------------
/** Event when the mouse moves
* @param event mouse event info */
void LineOverlay::mouseReleaseEvent(QMouseEvent * event)
{
if (!(event->buttons() & Qt::RightButton))
m_rightButton = false;
// Do not respond to mouse when hidden
if (!m_showHandles || !m_shown)
{
event->ignore();
return;
}
if (m_dragHandle != HandleNone)
{
// Stop draggin
m_dragHandle = HandleNone;
// Drag is over - signal that
emit lineChanged(m_pointA, m_pointB, m_width);
}
else
// Pass-through event to underlying widget if not dragging
event->ignore();
}
} // namespace Mantid
} // namespace SliceViewer