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