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