//Copyright 2017 Ryan Wick //This file is part of Bandage //Bandage is free software: you can redistribute it and/or modify //it under the terms of the GNU General Public License as published by //the Free Software Foundation, either version 3 of the License, or //(at your option) any later version. //Bandage is distributed in the hope that it will be useful, //but WITHOUT ANY WARRANTY; without even the implied warranty of //MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the //GNU General Public License for more details. //You should have received a copy of the GNU General Public License //along with Bandage. If not, see . #include "mygraphicsview.h" #include #include "../program/globals.h" #include "../program/settings.h" #include #include "graphicsviewzoom.h" #include #include #include #include "../graph/graphicsitemnode.h" MyGraphicsView::MyGraphicsView(QObject * /*parent*/) : QGraphicsView(), m_rotation(0.0) { setDragMode(QGraphicsView::RubberBandDrag); setAntialiasing(g_settings->antialiasing); setViewportUpdateMode(QGraphicsView::FullViewportUpdate); setBackgroundBrush(QBrush(Qt::white)); } void MyGraphicsView::mousePressEvent(QMouseEvent * event) { if (event->modifiers() == Qt::CTRL) setDragMode(QGraphicsView::ScrollHandDrag); else if (event->button() == Qt::RightButton) g_settings->nodeDragging = ONE_PIECE; m_previousPos = event->pos(); QGraphicsView::mousePressEvent(event); } void MyGraphicsView::mouseReleaseEvent(QMouseEvent * event) { QGraphicsView::mouseReleaseEvent(event); setDragMode(QGraphicsView::RubberBandDrag); g_settings->nodeDragging = NEARBY_PIECES; } void MyGraphicsView::mouseMoveEvent(QMouseEvent * event) { //If the user drags the right mouse button while holding control, //the view rotates. bool rightButtonDown = event->buttons() & Qt::RightButton; if (event->modifiers() == Qt::CTRL && rightButtonDown) { QPointF viewCentre(width() / 2.0, height() / 2.0); double angle = angleBetweenTwoLines(viewCentre, m_previousPos, viewCentre, event->pos()); angle *= 57.295779513; //convert to degrees rotate(angle); m_rotation += angle; m_previousPos = event->pos(); g_settings->nodeDragging = NO_DRAGGING; } else { QGraphicsView::mouseMoveEvent(event); } } void MyGraphicsView::mouseDoubleClickEvent(QMouseEvent * event) { //Find the node beneath the cursor. QGraphicsItem * item = itemAt(event->pos()); GraphicsItemNode * graphicsItemNode = dynamic_cast(item); if (graphicsItemNode != 0) emit doubleClickedNode(graphicsItemNode->m_deBruijnNode); } //Adapted from: //http://stackoverflow.com/questions/2663570/how-to-calculate-both-positive-and-negative-angle-between-two-lines double MyGraphicsView::angleBetweenTwoLines(QPointF line1Start, QPointF line1End, QPointF line2Start, QPointF line2End) { double a = line1End.x() - line1Start.x(); double b = line1End.y() - line1Start.y(); double c = line2End.x() - line2Start.x(); double d = line2End.y() - line2Start.y(); double atanA = atan2(a, b); double atanB = atan2(c, d); return atanA - atanB; } double MyGraphicsView::distance(double x1, double y1, double x2, double y2) { double xDiff = x1 - x2; double yDiff = y1 - y2; return sqrt(xDiff * xDiff + yDiff * yDiff); } void MyGraphicsView::keyPressEvent(QKeyEvent * event) { //This function uses angle in the same way that the mouse wheel code //in GraphicsViewZoom does. This keeps the zoom steps consistent //between keyboard and mouse wheel. int angle = 0; bool shiftPressed = event->modifiers().testFlag(Qt::ShiftModifier); //Ctrl+C (Command+C on Mac) copies the selected sequences to the clipboard. if (event->key() == Qt::Key_C && (event->modifiers() & Qt::ControlModifier)) emit copySelectedSequencesToClipboard(); //Ctrl+S (Command+S on Mac) saves the selected sequences to a FASTA file. else if (event->key() == Qt::Key_S && (event->modifiers() & Qt::ControlModifier)) emit saveSelectedSequencesToFile(); //Ctrl+plus (Command+plus on Mac) zooms the view in. If shift is pressed //as well, then it rotates clockwise. else if (event->matches(QKeySequence::ZoomIn) || event->key() == Qt::Key_Equal || event->key() == Qt::Key_Plus) { if (shiftPressed) changeRotation(1.0); else angle = 120; } //Ctrl+minus (Command+mins on Mac) zooms the view out. If shift is pressed //as well, then it rotates anticlockwise. else if (event->matches(QKeySequence::ZoomOut) || event->key() == Qt::Key_Minus || event->key() == Qt::Key_Underscore) { if (shiftPressed) changeRotation(-1.0); else angle = -120; } //Actually change the zoom now, if appropriate. if (angle != 0) { double factor = qPow(m_zoom->m_zoomFactorBase, angle); m_zoom->gentleZoom(factor, KEYBOARD); } //The event press event handling of QGraphicsView will take care of using //the arrow keys to adjust scroll bars. QGraphicsView::keyPressEvent(event); } void MyGraphicsView::setAntialiasing(bool antialiasingOn) { if (antialiasingOn) { setRenderHint(QPainter::Antialiasing, true); setRenderHint(QPainter::TextAntialiasing, true); g_settings->labelFont.setStyleStrategy(QFont::PreferDefault); } else { setRenderHint(QPainter::Antialiasing, false); setRenderHint(QPainter::TextAntialiasing, false); g_settings->labelFont.setStyleStrategy(QFont::NoAntialias); } } bool MyGraphicsView::isPointVisible(QPointF p) { QPointF corner1, corner2, corner3, corner4; getFourViewportCornersInSceneCoordinates(&corner1, &corner2, &corner3, &corner4); QLineF boundary1(corner1, corner2); QLineF boundary2(corner2, corner3); QLineF boundary3(corner3, corner4); QLineF boundary4(corner4, corner1); return !(differentSidesOfLine(p, corner3, boundary1) || differentSidesOfLine(p, corner4, boundary2) || differentSidesOfLine(p, corner1, boundary3) || differentSidesOfLine(p, corner2, boundary4)); } //This function tests to see if two given points, p1 and p2, are on different sides of a line. bool MyGraphicsView::differentSidesOfLine(QPointF p1, QPointF p2, QLineF line) { bool p1Side = sideOfLine(p1, line); bool p2Side = sideOfLine(p2, line); return (p1Side != p2Side); } //This function tests to see if all four points are the same side of a line (returns false) or //some are on different sides (returns true). bool MyGraphicsView::differentSidesOfLine(QPointF p1, QPointF p2, QPointF p3, QPointF p4, QLineF line) { bool p1Side = sideOfLine(p1, line); bool p2Side = sideOfLine(p2, line); if (p1Side != p2Side) return true; bool p3Side = sideOfLine(p3, line); if (p1Side != p3Side) return true; bool p4Side = sideOfLine(p4, line); return (p1Side != p4Side); } bool MyGraphicsView::sideOfLine(QPointF p, QLineF line) { return ((p.y() - line.p1().y() - (p.x() - line.p1().x())*(line.p2().y() - line.p1().y())/(line.p2().x() - line.p1().x())) > 0); } //This function assumes that the line does intersect with the viewport //boundary - i.e. one end of the line is in the viewport and one is not. QPointF MyGraphicsView::findIntersectionWithViewportBoundary(QLineF line) { QPointF c1, c2, c3, c4; getFourViewportCornersInSceneCoordinates(&c1, &c2, &c3, &c4); QLineF boundary1(c1, c2); QLineF boundary2(c2, c3); QLineF boundary3(c3, c4); QLineF boundary4(c4, c1); QPointF intersection; if (line.intersects(boundary1, &intersection) == QLineF::BoundedIntersection) return intersection; if (line.intersects(boundary2, &intersection) == QLineF::BoundedIntersection) return intersection; if (line.intersects(boundary3, &intersection) == QLineF::BoundedIntersection) return intersection; if (line.intersects(boundary4, &intersection) == QLineF::BoundedIntersection) return intersection; //The code should not get here, as the line should intersect with one of //the boundaries. return intersection; } //If a line intersections the scene rectangle but both of its end points are //outside the scene rectangle, then this function will return the part of the //line which is in the scene rectangle. QLineF MyGraphicsView::findVisiblePartOfLine(QLineF line, bool * success) { QPointF c1, c2, c3, c4; getFourViewportCornersInSceneCoordinates(&c1, &c2, &c3, &c4); QLineF boundary1(c1, c2); QLineF boundary2(c2, c3); QLineF boundary3(c3, c4); QLineF boundary4(c4, c1); QPointF intersection1; QPointF intersection2; QPointF intersection3; QPointF intersection4; *success = true; bool b1Intersects = (line.intersects(boundary1, &intersection1) == QLineF::BoundedIntersection); bool b2Intersects = (line.intersects(boundary2, &intersection2) == QLineF::BoundedIntersection); if (b1Intersects && b2Intersects) return QLineF(intersection1, intersection2); bool b3Intersects = (line.intersects(boundary3, &intersection3) == QLineF::BoundedIntersection); if (b1Intersects && b3Intersects) return QLineF(intersection1, intersection3); if (b2Intersects && b3Intersects) return QLineF(intersection2, intersection3); bool b4Intersects = (line.intersects(boundary4, &intersection4) == QLineF::BoundedIntersection); if (b1Intersects && b4Intersects) return QLineF(intersection1, intersection4); if (b2Intersects && b4Intersects) return QLineF(intersection2, intersection4); if (b3Intersects && b4Intersects) return QLineF(intersection3, intersection4); *success = false; return QLineF(); } void MyGraphicsView::getFourViewportCornersInSceneCoordinates(QPointF * c1, QPointF * c2, QPointF * c3, QPointF * c4) { *c1 = mapToScene(QPoint(0, 0)); *c2 = mapToScene(QPoint(viewport()->width(), 0)); *c3 = mapToScene(QPoint(viewport()->width(), viewport()->height())); *c4 = mapToScene(QPoint(0, viewport()->height())); } void MyGraphicsView::setRotation(double newRotation) { undoRotation(); changeRotation(newRotation); } void MyGraphicsView::changeRotation(double rotationChange) { rotate(rotationChange); m_rotation += rotationChange; } void MyGraphicsView::undoRotation() { rotate(-g_graphicsView->m_rotation); m_rotation = 0.0; }