1 /**
2  * UGENE - Integrated Bioinformatics Tools.
3  * Copyright (C) 2008-2021 UniPro <ugene@unipro.ru>
4  * http://ugene.net
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2
9  * of the License, or (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19  * MA 02110-1301, USA.
20  */
21 
22 #include "ScrollController.h"
23 
24 #include "U2Core/U2SafePoints.h"
25 #include <U2Core/MultipleAlignmentObject.h>
26 #include <U2Core/SignalBlocker.h>
27 
28 #include "BaseWidthController.h"
29 #include "DrawHelper.h"
30 #include "RowHeightController.h"
31 #include "ov_msa/MaCollapseModel.h"
32 #include "ov_msa/MaEditor.h"
33 #include "ov_msa/view_rendering/MaEditorSelection.h"
34 #include "ov_msa/view_rendering/MaEditorSequenceArea.h"
35 #include "ov_msa/view_rendering/MaEditorWgt.h"
36 
37 namespace U2 {
38 
ScrollController(MaEditor * maEditor,MaEditorWgt * maEditorUi)39 ScrollController::ScrollController(MaEditor *maEditor, MaEditorWgt *maEditorUi)
40     : QObject(maEditorUi),
41       maEditor(maEditor),
42       ui(maEditorUi),
43       savedFirstVisibleMaRow(0),
44       savedFirstVisibleMaRowOffset(0) {
45     connect(this, SIGNAL(si_visibleAreaChanged()), maEditorUi, SIGNAL(si_completeRedraw()));
46     connect(maEditor->getCollapseModel(), SIGNAL(si_aboutToBeToggled()), SLOT(sl_collapsibleModelIsAboutToBeChanged()));
47     connect(maEditor->getCollapseModel(), SIGNAL(si_toggled()), SLOT(sl_collapsibleModelChanged()));
48 }
49 
init(GScrollBar * hScrollBar,GScrollBar * vScrollBar)50 void ScrollController::init(GScrollBar *hScrollBar, GScrollBar *vScrollBar) {
51     this->hScrollBar = hScrollBar;
52     hScrollBar->setValue(0);
53     connect(hScrollBar, SIGNAL(valueChanged(int)), SIGNAL(si_visibleAreaChanged()));
54 
55     this->vScrollBar = vScrollBar;
56     vScrollBar->setValue(0);
57     connect(vScrollBar, SIGNAL(valueChanged(int)), SIGNAL(si_visibleAreaChanged()));
58 
59     sl_updateScrollBars();
60 }
61 
getScreenPosition() const62 QPoint ScrollController::getScreenPosition() const {
63     return QPoint(hScrollBar->value(), vScrollBar->value());
64 }
65 
getGlobalMousePosition(const QPoint & mousePos) const66 QPoint ScrollController::getGlobalMousePosition(const QPoint &mousePos) const {
67     return mousePos + getScreenPosition();
68 }
69 
updateVerticalScrollBar()70 void ScrollController::updateVerticalScrollBar() {
71     updateVerticalScrollBarPrivate();
72     emit si_visibleAreaChanged();
73 }
74 
scrollToViewRow(int viewRowIndex,int widgetHeight)75 void ScrollController::scrollToViewRow(int viewRowIndex, int widgetHeight) {
76     const U2Region rowRegion = ui->getRowHeightController()->getGlobalYRegionByViewRowIndex(viewRowIndex);
77     const U2Region visibleRegion = getVerticalRangeToDrawIn(widgetHeight);
78     if (rowRegion.startPos < visibleRegion.startPos) {
79         vScrollBar->setValue(static_cast<int>(rowRegion.startPos));
80     } else if (rowRegion.endPos() >= visibleRegion.endPos()) {
81         if (rowRegion.length > visibleRegion.length) {
82             vScrollBar->setValue(static_cast<int>(rowRegion.startPos));
83         } else if (rowRegion.startPos > visibleRegion.startPos) {
84             vScrollBar->setValue(static_cast<int>(rowRegion.endPos() - widgetHeight));
85         }
86     }
87 }
88 
scrollToBase(int baseNumber,int widgetWidth)89 void ScrollController::scrollToBase(int baseNumber, int widgetWidth) {
90     const U2Region baseRange = U2Region(ui->getBaseWidthController()->getBaseGlobalOffset(baseNumber), maEditor->getColumnWidth());
91     const U2Region visibleRange = getHorizontalRangeToDrawIn(widgetWidth);
92     if (baseRange.startPos < visibleRange.startPos) {
93         hScrollBar->setValue(static_cast<int>(baseRange.startPos));
94     } else if (baseRange.endPos() >= visibleRange.endPos()) {
95         hScrollBar->setValue(static_cast<int>(baseRange.endPos() - widgetWidth));
96     }
97 }
98 
scrollToPoint(const QPoint & maPoint,const QSize & screenSize)99 void ScrollController::scrollToPoint(const QPoint &maPoint, const QSize &screenSize) {
100     scrollToBase(maPoint.x(), screenSize.width());
101     scrollToViewRow(maPoint.y(), screenSize.height());
102 }
103 
centerBase(int baseNumber,int widgetWidth)104 void ScrollController::centerBase(int baseNumber, int widgetWidth) {
105     const U2Region baseGlobalRange = ui->getBaseWidthController()->getBaseGlobalRange(baseNumber);
106     const U2Region visibleRange = getHorizontalRangeToDrawIn(widgetWidth);
107     const int newScreenXOffset = baseGlobalRange.startPos - visibleRange.length / 2;
108     hScrollBar->setValue(newScreenXOffset);
109 }
110 
centerViewRow(int viewRowIndex,int widgetHeight)111 void ScrollController::centerViewRow(int viewRowIndex, int widgetHeight) {
112     const U2Region rowGlobalRange = ui->getRowHeightController()->getGlobalYRegionByViewRowIndex(viewRowIndex);
113     const U2Region visibleRange = getVerticalRangeToDrawIn(widgetHeight);
114     const int newScreenYOffset = rowGlobalRange.startPos - visibleRange.length / 2;
115     vScrollBar->setValue(newScreenYOffset);
116 }
117 
centerPoint(const QPoint & maPoint,const QSize & widgetSize)118 void ScrollController::centerPoint(const QPoint &maPoint, const QSize &widgetSize) {
119     centerBase(maPoint.x(), widgetSize.width());
120     centerViewRow(maPoint.y(), widgetSize.height());
121 }
122 
setHScrollbarValue(int value)123 void ScrollController::setHScrollbarValue(int value) {
124     hScrollBar->setValue(value);
125 }
126 
setVScrollbarValue(int value)127 void ScrollController::setVScrollbarValue(int value) {
128     vScrollBar->setValue(value);
129 }
130 
setFirstVisibleBase(int firstVisibleBase)131 void ScrollController::setFirstVisibleBase(int firstVisibleBase) {
132     hScrollBar->setValue(ui->getBaseWidthController()->getBaseGlobalOffset(firstVisibleBase));
133 }
134 
setFirstVisibleViewRow(int viewRowIndex)135 void ScrollController::setFirstVisibleViewRow(int viewRowIndex) {
136     int y = ui->getRowHeightController()->getGlobalYRegionByViewRowIndex(viewRowIndex).startPos;
137     vScrollBar->setValue(y);
138 }
139 
setFirstVisibleMaRow(int maRowIndex)140 void ScrollController::setFirstVisibleMaRow(int maRowIndex) {
141     int y = ui->getRowHeightController()->getGlobalYPositionByMaRowIndex(maRowIndex);
142     vScrollBar->setValue(y);
143 }
144 
scrollSmoothly(const Directions & directions)145 void ScrollController::scrollSmoothly(const Directions &directions) {
146     QAbstractSlider::SliderAction horizontalAction = QAbstractSlider::SliderNoAction;
147     QAbstractSlider::SliderAction verticalAction = QAbstractSlider::SliderNoAction;
148 
149     if (directions.testFlag(Up)) {
150         verticalAction = QAbstractSlider::SliderSingleStepSub;
151     }
152     if (directions.testFlag(Down)) {
153         verticalAction = QAbstractSlider::SliderSingleStepAdd;
154     }
155 
156     if (directions.testFlag(Left)) {
157         horizontalAction = QAbstractSlider::SliderSingleStepSub;
158     }
159     if (directions.testFlag(Right)) {
160         horizontalAction = QAbstractSlider::SliderSingleStepAdd;
161     }
162 
163     if (verticalAction != vScrollBar->getRepeatAction()) {
164         vScrollBar->setupRepeatAction(verticalAction, 500, 50);
165     }
166 
167     if (horizontalAction != hScrollBar->getRepeatAction()) {
168         hScrollBar->setupRepeatAction(horizontalAction, 500, 50);
169     }
170 }
171 
stopSmoothScrolling()172 void ScrollController::stopSmoothScrolling() {
173     hScrollBar->setupRepeatAction(QAbstractSlider::SliderNoAction);
174     vScrollBar->setupRepeatAction(QAbstractSlider::SliderNoAction);
175 }
176 
scrollStep(ScrollController::Direction direction)177 void ScrollController::scrollStep(ScrollController::Direction direction) {
178     switch (direction) {
179         case Up:
180             vScrollBar->triggerAction(QAbstractSlider::SliderSingleStepSub);
181             break;
182         case Down:
183             vScrollBar->triggerAction(QAbstractSlider::SliderSingleStepAdd);
184             break;
185         case Left:
186             hScrollBar->triggerAction(QAbstractSlider::SliderSingleStepSub);
187             break;
188         case Right:
189             hScrollBar->triggerAction(QAbstractSlider::SliderSingleStepAdd);
190             break;
191         default:
192             FAIL("An unknown direction", );
193             break;
194     }
195 }
196 
scrollPage(ScrollController::Direction direction)197 void ScrollController::scrollPage(ScrollController::Direction direction) {
198     switch (direction) {
199         case Up:
200             vScrollBar->triggerAction(QAbstractSlider::SliderPageStepSub);
201             break;
202         case Down:
203             vScrollBar->triggerAction(QAbstractSlider::SliderPageStepAdd);
204             break;
205         case Left:
206             hScrollBar->triggerAction(QAbstractSlider::SliderPageStepSub);
207             break;
208         case Right:
209             hScrollBar->triggerAction(QAbstractSlider::SliderPageStepAdd);
210             break;
211         default:
212             FAIL("An unknown direction", );
213             break;
214     }
215 }
216 
scrollToEnd(ScrollController::Direction direction)217 void ScrollController::scrollToEnd(ScrollController::Direction direction) {
218     switch (direction) {
219         case Up:
220             vScrollBar->triggerAction(QAbstractSlider::SliderToMinimum);
221             break;
222         case Down:
223             vScrollBar->triggerAction(QAbstractSlider::SliderToMaximum);
224             break;
225         case Left:
226             hScrollBar->triggerAction(QAbstractSlider::SliderToMinimum);
227             break;
228         case Right:
229             hScrollBar->triggerAction(QAbstractSlider::SliderToMaximum);
230             break;
231         default:
232             FAIL("An unknown direction", );
233             break;
234     }
235 }
236 
scrollToMovedSelection(int deltaX,int deltaY)237 void ScrollController::scrollToMovedSelection(int deltaX, int deltaY) {
238     const Direction direction = (deltaX != 0 ? (deltaX < 0 ? ScrollController::Left : ScrollController::Right) : (deltaY != 0 ? (deltaY < 0 ? ScrollController::Up : ScrollController::Down) : ScrollController::None));
239     scrollToMovedSelection(direction);
240 }
241 
scrollToMovedSelection(ScrollController::Direction direction)242 void ScrollController::scrollToMovedSelection(ScrollController::Direction direction) {
243     U2Region fullyVisibleRegion;
244     U2Region selectionRegion;
245     int selectionEdgePosition;
246     QSize widgetSize = ui->getSequenceArea()->size();
247     QRect selectionRect = ui->getEditor()->getSelection().toRect();
248     switch (direction) {
249         case Up:
250             fullyVisibleRegion = ui->getDrawHelper()->getVisibleViewRowsRegion(widgetSize.height(), false, false);
251             selectionRegion = U2Region(selectionRect.y(), selectionRect.height());
252             selectionEdgePosition = static_cast<int>(selectionRegion.startPos);
253             break;
254         case Down:
255             fullyVisibleRegion = ui->getDrawHelper()->getVisibleViewRowsRegion(widgetSize.height(), false, false);
256             selectionRegion = U2Region(selectionRect.y(), selectionRect.height());
257             selectionEdgePosition = static_cast<int>(selectionRegion.endPos() - 1);
258             break;
259         case Left:
260             fullyVisibleRegion = ui->getDrawHelper()->getVisibleBases(widgetSize.width(), false, false);
261             selectionRegion = U2Region(selectionRect.x(), selectionRect.width());
262             selectionEdgePosition = static_cast<int>(selectionRegion.startPos);
263             break;
264         case Right:
265             fullyVisibleRegion = ui->getDrawHelper()->getVisibleBases(widgetSize.width(), false, false);
266             selectionRegion = U2Region(selectionRect.x(), selectionRect.width());
267             selectionEdgePosition = static_cast<int>(selectionRegion.endPos() - 1);
268             break;
269         case None:
270             return;
271         default:
272             FAIL("An unknown direction", );
273             break;
274     }
275 
276     const bool selectionEdgeIsFullyVisible = fullyVisibleRegion.contains(selectionEdgePosition);
277     if (!selectionEdgeIsFullyVisible) {
278         switch (direction) {
279             case Up:
280             case Down:
281                 scrollToViewRow(static_cast<int>(selectionEdgePosition), widgetSize.height());
282                 break;
283             case Left:
284             case Right:
285                 scrollToBase(static_cast<int>(selectionEdgePosition), widgetSize.width());
286                 break;
287             case None:
288                 return;
289             default:
290                 FAIL("An unknown direction", );
291                 break;
292         }
293     }
294 }
295 
getFirstVisibleBase(bool countClipped) const296 int ScrollController::getFirstVisibleBase(bool countClipped) const {
297     if (maEditor->getAlignmentLen() == 0) {
298         return 0;
299     }
300     const bool removeClippedBase = !countClipped && (getAdditionalXOffset() != 0);
301     const int firstVisibleBase = ui->getBaseWidthController()->globalXPositionToColumn(hScrollBar->value()) + (removeClippedBase ? 1 : 0);
302     assert(firstVisibleBase < maEditor->getAlignmentLen());
303     return qMin(firstVisibleBase, maEditor->getAlignmentLen() - 1);
304 }
305 
getLastVisibleBase(int widgetWidth,bool countClipped) const306 int ScrollController::getLastVisibleBase(int widgetWidth, bool countClipped) const {
307     const bool removeClippedBase = !countClipped && ((hScrollBar->value() + widgetWidth) % maEditor->getColumnWidth() != 0);
308     const int lastVisibleBase = ui->getBaseWidthController()->globalXPositionToColumn(hScrollBar->value() + widgetWidth - 1) - (removeClippedBase ? 1 : 0);
309     return qMin(lastVisibleBase, maEditor->getAlignmentLen() - 1);
310 }
311 
getFirstVisibleMaRowIndex(bool countClipped) const312 int ScrollController::getFirstVisibleMaRowIndex(bool countClipped) const {
313     const bool removeClippedRow = !(countClipped || getAdditionalYOffset() == 0);
314     return ui->getRowHeightController()->getMaRowIndexByGlobalYPosition(vScrollBar->value()) + (removeClippedRow ? 1 : 0);
315 }
316 
getFirstVisibleViewRowIndex(bool countClipped) const317 int ScrollController::getFirstVisibleViewRowIndex(bool countClipped) const {
318     int maRowIndex = getFirstVisibleMaRowIndex(countClipped);
319     return maEditor->getCollapseModel()->getViewRowIndexByMaRowIndex(maRowIndex);
320 }
321 
getLastVisibleViewRowIndex(int widgetHeight,bool countClipped) const322 int ScrollController::getLastVisibleViewRowIndex(int widgetHeight, bool countClipped) const {
323     int lastVisibleViewRow = ui->getRowHeightController()->getViewRowIndexByGlobalYPosition(vScrollBar->value() + widgetHeight);
324     if (lastVisibleViewRow < 0) {
325         lastVisibleViewRow = maEditor->getCollapseModel()->getViewRowCount() - 1;
326     }
327     U2Region lastRowScreenRegion = ui->getRowHeightController()->getScreenYRegionByViewRowIndex(lastVisibleViewRow);
328     bool removeClippedRow = !countClipped && lastRowScreenRegion.endPos() > widgetHeight;
329     return lastVisibleViewRow - (removeClippedRow ? 1 : 0);
330 }
331 
getHorizontalScrollBar() const332 GScrollBar *ScrollController::getHorizontalScrollBar() const {
333     return hScrollBar;
334 }
335 
getVerticalScrollBar() const336 GScrollBar *ScrollController::getVerticalScrollBar() const {
337     return vScrollBar;
338 }
339 
sl_zoomScrollBars()340 void ScrollController::sl_zoomScrollBars() {
341     zoomHorizontalScrollBarPrivate();
342     zoomVerticalScrollBarPrivate();
343     emit si_visibleAreaChanged();
344 }
345 
sl_updateScrollBars()346 void ScrollController::sl_updateScrollBars() {
347     updateHorizontalScrollBarPrivate();
348     updateVerticalScrollBarPrivate();
349     emit si_visibleAreaChanged();
350 }
351 
sl_collapsibleModelIsAboutToBeChanged()352 void ScrollController::sl_collapsibleModelIsAboutToBeChanged() {
353     savedFirstVisibleMaRow = getFirstVisibleMaRowIndex(true);
354     savedFirstVisibleMaRowOffset = getScreenPosition().y() -
355                                    ui->getRowHeightController()->getGlobalYPositionByMaRowIndex(savedFirstVisibleMaRow);
356 }
357 
sl_collapsibleModelChanged()358 void ScrollController::sl_collapsibleModelChanged() {
359     int firstVisibleMaRowOffset = ui->getRowHeightController()->getGlobalYPositionByMaRowIndex(savedFirstVisibleMaRow);
360     setVScrollbarValue(firstVisibleMaRowOffset + savedFirstVisibleMaRowOffset);
361     updateVerticalScrollBar();
362 }
363 
getAdditionalXOffset() const364 int ScrollController::getAdditionalXOffset() const {
365     return hScrollBar->value() % maEditor->getColumnWidth();
366 }
367 
getAdditionalYOffset() const368 int ScrollController::getAdditionalYOffset() const {
369     int maRow = ui->getRowHeightController()->getMaRowIndexByGlobalYPosition(vScrollBar->value());
370     int viewRow = ui->getRowHeightController()->getGlobalYPositionByMaRowIndex(maRow);
371     return vScrollBar->value() - viewRow;
372 }
373 
getHorizontalRangeToDrawIn(int widgetWidth) const374 U2Region ScrollController::getHorizontalRangeToDrawIn(int widgetWidth) const {
375     return U2Region(hScrollBar->value(), widgetWidth);
376 }
377 
getVerticalRangeToDrawIn(int widgetHeight) const378 U2Region ScrollController::getVerticalRangeToDrawIn(int widgetHeight) const {
379     return U2Region(vScrollBar->value(), widgetHeight);
380 }
381 
zoomHorizontalScrollBarPrivate()382 void ScrollController::zoomHorizontalScrollBarPrivate() {
383     CHECK(!maEditor->isAlignmentEmpty(), );
384     SignalBlocker signalBlocker(hScrollBar);
385     Q_UNUSED(signalBlocker);
386 
387     const int previousAlignmentWidth = hScrollBar->maximum() + ui->getSequenceArea()->width();
388     const double previousRelation = static_cast<double>(hScrollBar->value()) / previousAlignmentWidth;
389     updateHorizontalScrollBarPrivate();
390     hScrollBar->setValue(previousRelation * ui->getBaseWidthController()->getTotalAlignmentWidth());
391 }
392 
zoomVerticalScrollBarPrivate()393 void ScrollController::zoomVerticalScrollBarPrivate() {
394     CHECK(!maEditor->isAlignmentEmpty(), );
395     SignalBlocker signalBlocker(vScrollBar);
396     Q_UNUSED(signalBlocker);
397 
398     const int previousAlignmentHeight = vScrollBar->maximum() + ui->getSequenceArea()->height();
399     const double previousRelation = static_cast<double>(vScrollBar->value()) / previousAlignmentHeight;
400     updateVerticalScrollBarPrivate();
401     vScrollBar->setValue(previousRelation * ui->getRowHeightController()->getTotalAlignmentHeight());
402 }
403 
updateHorizontalScrollBarPrivate()404 void ScrollController::updateHorizontalScrollBarPrivate() {
405     SAFE_POINT(nullptr != hScrollBar, "Horizontal scrollbar is not initialized", );
406     SignalBlocker signalBlocker(hScrollBar);
407     Q_UNUSED(signalBlocker);
408 
409     CHECK_EXT(!maEditor->isAlignmentEmpty(), hScrollBar->setVisible(false), );
410 
411     const int alignmentLength = maEditor->getAlignmentLen();
412     const int columnWidth = maEditor->getColumnWidth();
413     const int sequenceAreaWidth = ui->getSequenceArea()->width();
414 
415     hScrollBar->setMinimum(0);
416     hScrollBar->setMaximum(qMax(0, alignmentLength * columnWidth - sequenceAreaWidth));
417     hScrollBar->setSingleStep(columnWidth);
418     hScrollBar->setPageStep(sequenceAreaWidth);
419 
420     const int numVisibleBases = getLastVisibleBase(sequenceAreaWidth) - getFirstVisibleBase();
421     SAFE_POINT(numVisibleBases <= alignmentLength, "Horizontal scrollbar appears unexpectedly: numVisibleBases is too small", );
422     hScrollBar->setVisible(numVisibleBases < alignmentLength);
423 }
424 
updateVerticalScrollBarPrivate()425 void ScrollController::updateVerticalScrollBarPrivate() {
426     SAFE_POINT(nullptr != vScrollBar, "Vertical scrollbar is not initialized", );
427     SignalBlocker signalBlocker(vScrollBar);
428     Q_UNUSED(signalBlocker);
429 
430     CHECK_EXT(!maEditor->isAlignmentEmpty(), vScrollBar->setVisible(false), );
431 
432     const int viewRowCount = ui->getSequenceArea()->getViewRowCount();
433     const int sequenceAreaHeight = ui->getSequenceArea()->height();
434     const int totalAlignmentHeight = ui->getRowHeightController()->getTotalAlignmentHeight();
435 
436     vScrollBar->setMinimum(0);
437     vScrollBar->setMaximum(qMax(0, totalAlignmentHeight - sequenceAreaHeight));
438     vScrollBar->setSingleStep(ui->getRowHeightController()->getSingleRowHeight());
439     vScrollBar->setPageStep(sequenceAreaHeight);
440 
441     int firstVisibleViewRowIndex = getFirstVisibleViewRowIndex();
442     int lastVisibleViewRowIndex = getLastVisibleViewRowIndex(sequenceAreaHeight);
443     int numVisibleSequences = lastVisibleViewRowIndex - firstVisibleViewRowIndex + 1;
444     SAFE_POINT(numVisibleSequences <= viewRowCount, "Vertical scrollbar appears unexpectedly: numVisibleSequences is too small", );
445     vScrollBar->setVisible(numVisibleSequences < viewRowCount);
446 }
447 
getViewPosByScreenPoint(const QPoint & point,bool reportOverflow) const448 QPoint ScrollController::getViewPosByScreenPoint(const QPoint &point, bool reportOverflow) const {
449     int column = ui->getBaseWidthController()->screenXPositionToColumn(point.x());
450     int row = ui->getRowHeightController()->getViewRowIndexByScreenYPosition(point.y());
451     QPoint result(column, row);
452     if (ui->getSequenceArea()->isInRange(result)) {
453         return result;
454     }
455     if (reportOverflow) {
456         row = row == -1 && point.y() > 0 ? ui->getSequenceArea()->getViewRowCount() : row;
457         column = qMin(column, maEditor->getAlignmentLen());
458         return QPoint(column, row);
459     }
460     return QPoint(-1, -1);
461 }
462 
463 }  // namespace U2
464