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 <math.h>
23 
24 #include <QPainter>
25 
26 #include <U2Core/AppContext.h>
27 #include <U2Core/L10n.h>
28 #include <U2Core/MultipleSequenceAlignmentObject.h>
29 #include <U2Core/Settings.h>
30 #include <U2Core/U2OpStatusUtils.h>
31 #include <U2Core/U2SafePoints.h>
32 
33 #include "MSAEditor.h"
34 #include "MSAEditorOffsetsView.h"
35 #include "MSAEditorSequenceArea.h"
36 #include "MaEditorNameList.h"
37 #include "McaEditor.h"
38 #include "helpers/DrawHelper.h"
39 #include "helpers/RowHeightController.h"
40 #include "helpers/ScrollController.h"
41 
42 namespace U2 {
43 
44 #define SETTINGS_SHOW_OFFSETS "show_offsets"
45 
MSAEditorOffsetsViewController(MaEditorWgt * maEditorUi,MaEditor * ed,MaEditorSequenceArea * sa)46 MSAEditorOffsetsViewController::MSAEditorOffsetsViewController(MaEditorWgt *maEditorUi, MaEditor *ed, MaEditorSequenceArea *sa)
47     : QObject(maEditorUi) {
48     seqArea = sa;
49     editor = ed;
50 
51     leftWidget = new MSAEditorOffsetsViewWidget(maEditorUi, ed, seqArea, true);
52     leftWidget->setObjectName("msa_editor_offsets_view_widget_left");
53     rightWidget = new MSAEditorOffsetsViewWidget(maEditorUi, ed, seqArea, false);
54     rightWidget->setObjectName("msa_editor_offsets_view_widget_right");
55 
56     connect(maEditorUi->getScrollController(), SIGNAL(si_visibleAreaChanged()), SLOT(sl_updateOffsets()));
57     connect(editor, SIGNAL(si_fontChanged(const QFont &)), SLOT(sl_updateOffsets()));
58 
59     MultipleAlignmentObject *mobj = editor->getMaObject();
60     SAFE_POINT(nullptr != mobj, L10N::nullPointerError("multiple alignment object"), );
61     connect(mobj, SIGNAL(si_alignmentChanged(const MultipleAlignment &, const MaModificationInfo &)), SLOT(sl_updateOffsets()));
62 
63     seqArea->installEventFilter(this);
64 
65     Settings *s = AppContext::getSettings();
66     bool showOffsets = s->getValue(editor->getSettingsRoot() + SETTINGS_SHOW_OFFSETS, true).toBool();
67 
68     toggleColumnsViewAction = new QAction(tr("Show offsets"), this);
69     toggleColumnsViewAction->setObjectName("show_offsets");
70     toggleColumnsViewAction->setCheckable(true);
71     toggleColumnsViewAction->setChecked(showOffsets);
72     connect(toggleColumnsViewAction, SIGNAL(triggered(bool)), SLOT(sl_showOffsets(bool)));
73     connect(editor, SIGNAL(si_referenceSeqChanged(qint64)), SLOT(sl_updateOffsets()));
74     connect(editor, SIGNAL(si_completeUpdate()), SLOT(sl_updateOffsets()));
75     updateOffsets();
76 }
77 
sl_updateOffsets()78 void MSAEditorOffsetsViewController::sl_updateOffsets() {
79     updateOffsets();
80 }
81 
eventFilter(QObject * o,QEvent * e)82 bool MSAEditorOffsetsViewController::eventFilter(QObject *o, QEvent *e) {
83     if (o == seqArea) {
84         if (e->type() == QEvent::Resize || e->type() == QEvent::Show) {
85             updateOffsets();
86         }
87     }
88     return false;
89 }
90 
sl_showOffsets(bool show)91 void MSAEditorOffsetsViewController::sl_showOffsets(bool show) {
92     updateOffsets();
93     Settings *s = AppContext::getSettings();
94     SAFE_POINT(s != nullptr, "AppContext settings is NULL", );
95     s->setValue(editor->getSettingsRoot() + SETTINGS_SHOW_OFFSETS, show);
96 }
97 
updateOffsets()98 void MSAEditorOffsetsViewController::updateOffsets() {
99     if (leftWidget->parentWidget() != nullptr) {
100         const bool vis = toggleColumnsViewAction->isChecked();
101         leftWidget->setVisible(vis);
102         rightWidget->setVisible(vis);
103     }
104 
105     leftWidget->updateView();
106     rightWidget->updateView();
107 }
108 
MSAEditorOffsetsViewWidget(MaEditorWgt * maEditorUi,MaEditor * ed,MaEditorSequenceArea * sa,bool sp)109 MSAEditorOffsetsViewWidget::MSAEditorOffsetsViewWidget(MaEditorWgt *maEditorUi, MaEditor *ed, MaEditorSequenceArea *sa, bool sp)
110     : seqArea(sa),
111       editor(ed),
112       showStartPos(sp),
113       completeRedraw(true) {
114     connect(maEditorUi, SIGNAL(si_completeRedraw()), SLOT(sl_completeRedraw()));
115 }
116 
sl_completeRedraw()117 void MSAEditorOffsetsViewWidget::sl_completeRedraw() {
118     completeRedraw = true;
119     update();
120 }
121 
122 #define OFFS_WIDGET_BORDER 3
updateView()123 void MSAEditorOffsetsViewWidget::updateView() {
124     const int aliLen = editor->getMaObject()->getLength();
125     QFont f = getOffsetsFont();
126     QFontMetrics fm(f, this);
127     int aliLenStrLen = int(log10((double)aliLen)) + 1;
128     int w = OFFS_WIDGET_BORDER + fm.width('X') * aliLenStrLen + OFFS_WIDGET_BORDER;
129     w += (showStartPos ? fm.width('[') : fm.width(']'));
130     setFixedWidth(w);
131     completeRedraw = true;
132     update();
133 }
134 
paintEvent(QPaintEvent *)135 void MSAEditorOffsetsViewWidget::paintEvent(QPaintEvent *) {
136     SAFE_POINT(isVisible(), "Attempting to paint an invisible widget", );
137     const QSize s = size() * devicePixelRatio();
138     if (s != cachedView.size()) {
139         cachedView = QPixmap(s);
140         cachedView.setDevicePixelRatio(devicePixelRatio());
141         completeRedraw = true;
142     }
143     if (completeRedraw) {
144         QPainter pCached(&cachedView);
145         drawAll(pCached);
146         completeRedraw = false;
147     }
148     QPainter p(this);
149     p.drawPixmap(0, 0, cachedView);
150 }
151 
getOffsetsFont()152 QFont MSAEditorOffsetsViewWidget::getOffsetsFont() {
153     QFont f = editor->getFont();
154     f.setPointSize(qMax(f.pointSize() - 1, 6));
155     return f;
156 }
157 
getBaseCounts(int seqNum,int aliPos,bool inclAliPos) const158 int MSAEditorOffsetsViewWidget::getBaseCounts(int seqNum, int aliPos, bool inclAliPos) const {
159     const MultipleAlignmentRow &row = editor->getMaObject()->getRow(seqNum);
160     const int endPos = inclAliPos ? aliPos + 1 : aliPos;
161 
162     return (endPos < row->getCoreStart()) ? 0 : row->getBaseCount(endPos);
163 }
164 
drawAll(QPainter & painter)165 void MSAEditorOffsetsViewWidget::drawAll(QPainter &painter) {
166     QLinearGradient gradient(0, 0, width(), 0);
167     QColor lg(0xDA, 0xDA, 0xDA);
168     QColor dg(0x4A, 0x4A, 0x4A);
169     gradient.setColorAt(0.00, lg);
170     gradient.setColorAt(0.25, Qt::white);
171     gradient.setColorAt(0.75, Qt::white);
172     gradient.setColorAt(1.00, lg);
173     painter.fillRect(rect(), QBrush(gradient));
174 
175     const int widgetWidth = width();
176 
177     QFont font = getOffsetsFont();
178     QFontMetrics fm(font, this);
179     painter.setFont(font);
180 
181     MaEditorWgt *ui = editor->getUI();
182     int alignmentLength = editor->getMaObject()->getLength();
183     int lbw = fm.width('[');
184     int rbw = fm.width(']');
185     int pos = showStartPos ? ui->getScrollController()->getFirstVisibleBase(true) : ui->getScrollController()->getLastVisibleBase(seqArea->width(), true);
186 
187     QList<int> visibleRows = ui->getDrawHelper()->getVisibleMaRowIndexes(height());
188 
189     const MultipleAlignment alignment = editor->getMaObject()->getMultipleAlignment();
190     U2OpStatusImpl os;
191     const int refSeq = alignment->getRowIndexByRowId(editor->getReferenceRowId(), os);
192 
193     foreach (const int rowNumber, visibleRows) {
194         const U2Region yRange = ui->getRowHeightController()->getScreenYRegionByMaRowIndex(rowNumber);
195         int offs = getBaseCounts(rowNumber, pos, !showStartPos);
196         int seqSize = getBaseCounts(rowNumber, alignmentLength - 1, true);
197         QString offset = offs + 1 > seqSize ? QString::number(seqSize) : QString::number(offs + 1);
198         if (showStartPos && offs == 0) {
199             painter.setPen(Qt::black);
200             QRect lbr(OFFS_WIDGET_BORDER, yRange.startPos, lbw, yRange.length);
201             if (rowNumber == refSeq) {
202                 drawRefSequence(painter, lbr);
203             }
204             painter.drawText(lbr, Qt::AlignTop, "[");
205         } else if (!showStartPos && offs == seqSize) {
206             painter.setPen(Qt::black);
207             QRect rbr(widgetWidth - OFFS_WIDGET_BORDER - rbw, yRange.startPos, rbw, yRange.length);
208             if (rowNumber == refSeq) {
209                 drawRefSequence(painter, rbr);
210             }
211             painter.drawText(rbr, Qt::AlignTop, "]");
212             offset = QString::number(offs);
213         } else {
214             painter.setPen(dg);
215         }
216         QRect tr(OFFS_WIDGET_BORDER + (showStartPos ? lbw : 0), yRange.startPos, widgetWidth - 2 * OFFS_WIDGET_BORDER - (showStartPos ? lbw : rbw), yRange.length);
217         if (rowNumber == refSeq) {
218             drawRefSequence(painter, tr);
219         }
220         painter.drawText(tr, Qt::AlignRight | Qt::AlignTop, offset);
221     }
222 }
223 
drawRefSequence(QPainter & p,const QRect & r)224 void MSAEditorOffsetsViewWidget::drawRefSequence(QPainter &p, const QRect &r) {
225     p.fillRect(r, QColor("#9999CC"));
226 }
227 
228 }  // namespace U2
229