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 "IlluminaClipStep.h"
23 
24 #include <QMainWindow>
25 
26 #include <U2Core/AppContext.h>
27 #include <U2Core/BaseDocumentFormats.h>
28 #include <U2Core/QObjectScopedPointer.h>
29 #include <U2Core/U2SafePoints.h>
30 
31 #include <U2Gui/DialogUtils.h>
32 #include <U2Gui/HelpButton.h>
33 #include <U2Gui/LastUsedDirHelper.h>
34 #include <U2Gui/MainWindow.h>
35 #include <U2Gui/U2FileDialog.h>
36 
37 #include "trimmomatic/util/LineEditHighlighter.h"
38 
39 namespace U2 {
40 namespace LocalWorkflow {
41 
42 const QString IlluminaClipStepFactory::ID = "ILLUMINACLIP";
43 
IlluminaClipStep()44 IlluminaClipStep::IlluminaClipStep()
45     : TrimmomaticStep(IlluminaClipStepFactory::ID) {
46     name = "ILLUMINACLIP";
47     description = tr("<html><head></head><body>"
48                      "<h4>ILLUMINACLIP</h4>"
49                      "<p>This step is used to find and remove Illumina adapters.</p>"
50                      "<p>Trimmomatic first compares short sections of an adapter and a read. If they match enough, "
51                      "the entire alignment between the read and adapter is scored. For paired-end reads, the \"palindrome\" "
52                      "approach is also used to improve the result. See Trimmomatic manual for details.</p>"
53                      "<p>Input the following values:</p>"
54                      "<ul>"
55                      "<li><b>Adapter sequences</b>: a FASTA file with the adapter sequences. Files for TruSeq2 "
56                      "(GAII machines), TruSeq3 (HiSeq and MiSeq machines) and Nextera kits for SE and PE reads are "
57                      "now available by default. The naming of the various sequences within the specified file "
58                      "determines how they are used.</li>"
59                      "<li><b>Seed mismatches</b>: the maximum mismatch count in short sections which will still allow "
60                      "a full match to be performed.</li>"
61                      "<li><b>Simple clip threshold</b>: a threshold for simple alignment mode. Values between 7 and 15 "
62                      "are recommended. A perfect match of a 12 base sequence will score just over 7, while 25 bases "
63                      "are needed to score 15.</li>"
64                      "<li><b>Palindrome clip threshold</b>: a threshold for palindrome alignment mode. For palindromic "
65                      "matches, a longer alignment is possible. Therefore the threshold can be in the range of 30. "
66                      "Even though this threshold is very high (requiring a match of almost 50 bases) Trimmomatic is "
67                      "still able to identify very, very short adapter fragments.</li>"
68                      "</ul>"
69                      "<p>There are also two optional parameters for palindrome mode: <b>Min adapter length</b> and "
70                      "<b>Keep both reads</b>."
71                      "</body></html>");
72 }
73 
createWidget() const74 TrimmomaticStepSettingsWidget *IlluminaClipStep::createWidget() const {
75     return new IlluminaClipSettingsWidget();
76 }
77 
serializeState(const QVariantMap & widgetState) const78 QString IlluminaClipStep::serializeState(const QVariantMap &widgetState) const {
79     QString serializedState;
80     serializedState += "\'" + widgetState.value(IlluminaClipSettingsWidget::FASTA_WITH_ADAPTERS_ETC, "").toString() + "\'";
81 
82     serializedState += ":";
83 
84     if (widgetState.contains(IlluminaClipSettingsWidget::SEED_MISMATCHES)) {
85         serializedState += QString::number(widgetState.value(IlluminaClipSettingsWidget::SEED_MISMATCHES).toInt());
86     }
87 
88     serializedState += ":";
89 
90     if (widgetState.contains(IlluminaClipSettingsWidget::PALINDROME_CLIP_THRESHOLD)) {
91         serializedState += QString::number(widgetState.value(IlluminaClipSettingsWidget::PALINDROME_CLIP_THRESHOLD).toInt());
92     }
93 
94     serializedState += ":";
95 
96     if (widgetState.contains(IlluminaClipSettingsWidget::SIMPLE_CLIP_THRESHOLD)) {
97         serializedState += QString::number(widgetState.value(IlluminaClipSettingsWidget::SIMPLE_CLIP_THRESHOLD).toInt());
98     }
99 
100     if (widgetState.value(IlluminaClipAdditionalSettingsDialog::ADDITIONAL_SETTINGS_ENABLED, false).toBool()) {
101         serializedState += ":";
102 
103         if (widgetState.contains(IlluminaClipAdditionalSettingsDialog::MIN_ADAPTER_LENGTH)) {
104             serializedState += QString::number(widgetState.value(IlluminaClipAdditionalSettingsDialog::MIN_ADAPTER_LENGTH).toInt());
105         }
106 
107         serializedState += ":";
108 
109         if (widgetState.contains(IlluminaClipAdditionalSettingsDialog::KEEP_BOTH_READS)) {
110             serializedState += widgetState.value(IlluminaClipAdditionalSettingsDialog::KEEP_BOTH_READS).toBool() ? "true" : "false";
111         }
112     }
113 
114     return serializedState;
115 }
116 
parseState(const QString & command) const117 QVariantMap IlluminaClipStep::parseState(const QString &command) const {
118     QVariantMap state;
119     QRegExp regExp(id + ":" + "\\\'([^\\\']*)\\'" + ":" + "(\\d*)" + ":" + "(\\d*)" + ":" + "(\\d*)" +
120                        "(:" + "(\\d*)" + ":" + "((true|false){0,1})" + ")?",
121                    Qt::CaseInsensitive);
122 
123     const bool matched = regExp.exactMatch(command);
124     CHECK(matched, state);
125 
126     const QString fastaWithAdaptersEtc = regExp.cap(1);
127     if (!fastaWithAdaptersEtc.isEmpty()) {
128         state[IlluminaClipSettingsWidget::FASTA_WITH_ADAPTERS_ETC] = fastaWithAdaptersEtc;
129     }
130 
131     const QString seedMismatches = regExp.cap(2);
132     if (!seedMismatches.isEmpty()) {
133         state[IlluminaClipSettingsWidget::SEED_MISMATCHES] = seedMismatches.toInt();
134     }
135 
136     const QString palindromeClipThreshold = regExp.cap(3);
137     if (!palindromeClipThreshold.isEmpty()) {
138         state[IlluminaClipSettingsWidget::PALINDROME_CLIP_THRESHOLD] = palindromeClipThreshold.toInt();
139     }
140 
141     const QString simpleClipThreshold = regExp.cap(4);
142     if (!simpleClipThreshold.isEmpty()) {
143         state[IlluminaClipSettingsWidget::SIMPLE_CLIP_THRESHOLD] = simpleClipThreshold.toInt();
144     }
145 
146     if (!regExp.cap(5).isEmpty()) {
147         state[IlluminaClipAdditionalSettingsDialog::ADDITIONAL_SETTINGS_ENABLED] = true;
148 
149         const QString minAdapterLength = regExp.cap(6);
150         if (!minAdapterLength.isEmpty()) {
151             state[IlluminaClipAdditionalSettingsDialog::MIN_ADAPTER_LENGTH] = minAdapterLength.toInt();
152         }
153 
154         const QString keepBothReads = regExp.cap(7);
155         if (!keepBothReads.isEmpty()) {
156             state[IlluminaClipAdditionalSettingsDialog::KEEP_BOTH_READS] = (keepBothReads.compare("true", Qt::CaseInsensitive) == 0);
157         }
158     }
159 
160     return state;
161 }
162 
163 const QString IlluminaClipSettingsWidget::FASTA_WITH_ADAPTERS_ETC = "fastaWithAdaptersEtc";
164 const QString IlluminaClipSettingsWidget::SEED_MISMATCHES = "seedMismatches";
165 const QString IlluminaClipSettingsWidget::PALINDROME_CLIP_THRESHOLD = "palindromeClipThreshold";
166 const QString IlluminaClipSettingsWidget::SIMPLE_CLIP_THRESHOLD = "simpleClipThreshold";
167 
168 const QString IlluminaClipSettingsWidget::DEFAULT_SE_ADAPTERS = "TruSeq3-SE.fa";
169 const QString IlluminaClipSettingsWidget::DEFAULT_PE_ADAPTERS = "TruSeq3-PE-2.fa";
170 
IlluminaClipSettingsWidget()171 IlluminaClipSettingsWidget::IlluminaClipSettingsWidget() {
172     setupUi(this);
173 
174     fileName->setText(QDir::toNativeSeparators(QDir("data:").path() + "/adapters/illumina/" + DEFAULT_SE_ADAPTERS));    // The default adapters should be set depending on another attribute value
175     new LineEditHighlighter(fileName);
176 
177     connect(fileName, SIGNAL(textChanged(QString)), SIGNAL(si_valueChanged()));
178     connect(mismatches, SIGNAL(valueChanged(int)), SIGNAL(si_valueChanged()));
179     connect(palindromeThreshold, SIGNAL(valueChanged(int)), SIGNAL(si_valueChanged()));
180     connect(simpleThreshold, SIGNAL(valueChanged(int)), SIGNAL(si_valueChanged()));
181     connect(tbBrowse, SIGNAL(clicked(bool)), SLOT(sl_browseButtonClicked()));
182     connect(pushButton, SIGNAL(clicked(bool)), SLOT(sl_optionalButtonClicked()));
183 }
184 
~IlluminaClipSettingsWidget()185 IlluminaClipSettingsWidget::~IlluminaClipSettingsWidget() {
186     emit si_widgetIsAboutToBeDestroyed(getState());
187 }
188 
validate() const189 bool IlluminaClipSettingsWidget::validate() const {
190     return !fileName->text().isEmpty();
191 }
192 
getState() const193 QVariantMap IlluminaClipSettingsWidget::getState() const {
194     QVariantMap state;
195 
196     const QString fastaWithAdaptersEtc = fileName->text();
197     if (!fastaWithAdaptersEtc.isEmpty()) {
198         state[FASTA_WITH_ADAPTERS_ETC] = fastaWithAdaptersEtc;
199     }
200 
201     state[SEED_MISMATCHES] = mismatches->value();
202     state[PALINDROME_CLIP_THRESHOLD] = palindromeThreshold->value();
203     state[SIMPLE_CLIP_THRESHOLD] = simpleThreshold->value();
204 
205     return state.unite(additionalOptions);
206 }
207 
setState(const QVariantMap & state)208 void IlluminaClipSettingsWidget::setState(const QVariantMap &state) {
209     bool contains = state.contains(FASTA_WITH_ADAPTERS_ETC);
210     if (contains) {
211         fileName->setText(state[FASTA_WITH_ADAPTERS_ETC].toString());
212     }
213 
214     contains = state.contains(SEED_MISMATCHES);
215     bool valid = false;
216     const int seedMismatches = state[SEED_MISMATCHES].toInt(&valid);
217     if (contains && valid) {
218         mismatches->setValue(seedMismatches);
219     }
220 
221     contains = state.contains(PALINDROME_CLIP_THRESHOLD);
222     const int palindromeClipThreshold = state[PALINDROME_CLIP_THRESHOLD].toInt(&valid);
223     if (contains && valid) {
224         palindromeThreshold->setValue(palindromeClipThreshold);
225     }
226 
227     contains = state.contains(SIMPLE_CLIP_THRESHOLD);
228     const int simpleClipThreshold = state[SIMPLE_CLIP_THRESHOLD].toInt(&valid);
229     if (contains && valid) {
230         simpleThreshold->setValue(simpleClipThreshold);
231     }
232 
233     additionalOptions = IlluminaClipAdditionalSettingsDialog::extractState(state);
234 }
235 
sl_browseButtonClicked()236 void IlluminaClipSettingsWidget::sl_browseButtonClicked() {
237     QString defaultDir = QDir::searchPaths(PATH_PREFIX_DATA).first() + "/adapters/illumina";
238     LastUsedDirHelper dirHelper("trimmomatic/adapters", defaultDir);
239 
240     const QString filter = DialogUtils::prepareDocumentsFileFilter(BaseDocumentFormats::FASTA, true, QStringList());
241     QString defaultFilter = DialogUtils::prepareDocumentsFileFilter(BaseDocumentFormats::FASTA, false);
242     const QString adaptersFilePath = U2FileDialog::getOpenFileName(this, tr("Open FASTA with adapters"), dirHelper.dir, filter, &defaultFilter);
243     if (!adaptersFilePath.isEmpty()) {
244         dirHelper.url = adaptersFilePath;
245         fileName->setText(adaptersFilePath);
246     }
247 }
248 
sl_optionalButtonClicked()249 void IlluminaClipSettingsWidget::sl_optionalButtonClicked() {
250     QObjectScopedPointer<IlluminaClipAdditionalSettingsDialog> additionalOptionsDialog(new IlluminaClipAdditionalSettingsDialog(additionalOptions, AppContext::getMainWindow()->getQMainWindow()));
251     const int executionResult = additionalOptionsDialog->exec();
252     if (static_cast<QDialog::DialogCode>(executionResult) == QDialog::Accepted) {
253         CHECK(!additionalOptionsDialog.isNull(), );
254         additionalOptions = additionalOptionsDialog->getState();
255     }
256 }
257 
258 const QString IlluminaClipAdditionalSettingsDialog::ADDITIONAL_SETTINGS_ENABLED = "additionalSettingsEnabled";
259 const QString IlluminaClipAdditionalSettingsDialog::MIN_ADAPTER_LENGTH = "minAdapterLength";
260 const QString IlluminaClipAdditionalSettingsDialog::KEEP_BOTH_READS = "keepBothReads";
261 
IlluminaClipAdditionalSettingsDialog(const QVariantMap & widgetState,QWidget * parent)262 IlluminaClipAdditionalSettingsDialog::IlluminaClipAdditionalSettingsDialog(const QVariantMap &widgetState, QWidget *parent)
263     : QDialog(parent) {
264     setupUi(this);
265 
266     new HelpButton(this, buttonBox, "65930159");
267 
268     buttonBox->button(QDialogButtonBox::Ok)->setText(tr("Apply"));
269 
270     keepBothCombo->addItem(tr("True"), true);
271     keepBothCombo->addItem(tr("False"), false);
272 
273     groupBox->setChecked(widgetState.value(ADDITIONAL_SETTINGS_ENABLED, false).toBool());
274     minLengthSpin->setValue(widgetState.value(MIN_ADAPTER_LENGTH, 8).toInt());
275     keepBothCombo->setCurrentIndex(keepBothCombo->findData(widgetState.value(KEEP_BOTH_READS, false).toBool()));
276 }
277 
extractState(const QVariantMap & fromState)278 QVariantMap IlluminaClipAdditionalSettingsDialog::extractState(const QVariantMap &fromState) {
279     QVariantMap state;
280     state[ADDITIONAL_SETTINGS_ENABLED] = fromState.value(ADDITIONAL_SETTINGS_ENABLED, false);
281     state[MIN_ADAPTER_LENGTH] = fromState.value(MIN_ADAPTER_LENGTH, 8);
282     state[KEEP_BOTH_READS] = fromState.value(KEEP_BOTH_READS, false);
283     return state;
284 }
285 
getState() const286 QVariantMap IlluminaClipAdditionalSettingsDialog::getState() const {
287     QVariantMap state;
288     state[ADDITIONAL_SETTINGS_ENABLED] = groupBox->isChecked();
289     state[MIN_ADAPTER_LENGTH] = minLengthSpin->value();
290     state[KEEP_BOTH_READS] = keepBothCombo->currentData();
291     return state;
292 }
293 
IlluminaClipStepFactory()294 IlluminaClipStepFactory::IlluminaClipStepFactory()
295     : TrimmomaticStepFactory(ID) {
296 }
297 
createStep() const298 IlluminaClipStep *IlluminaClipStepFactory::createStep() const {
299     return new IlluminaClipStep();
300 }
301 
302 }    // namespace LocalWorkflow
303 }    // namespace U2
304