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 "ADVClipboard.h"
23 
24 #include <QApplication>
25 #include <QClipboard>
26 #include <QMenu>
27 #include <QMessageBox>
28 #include <QTextStream>
29 
30 #include <U2Core/AnnotationSelection.h>
31 #include <U2Core/DNAAlphabet.h>
32 #include <U2Core/DNASequenceObject.h>
33 #include <U2Core/DNASequenceSelection.h>
34 #include <U2Core/DNATranslation.h>
35 #include <U2Core/L10n.h>
36 #include <U2Core/SequenceUtils.h>
37 #include <U2Core/U2OpStatusUtils.h>
38 #include <U2Core/U2SafePoints.h>
39 #include <U2Core/U2SequenceUtils.h>
40 
41 #include <U2Gui/GUIUtils.h>
42 
43 #include "ADVConstants.h"
44 #include "ADVSequenceObjectContext.h"
45 #include "AnnotatedDNAView.h"
46 
47 #ifdef Q_OS_WIN
48 #    include <Windows.h>
49 #endif
50 
51 namespace U2 {
52 
53 const QString ADVClipboard::COPY_FAILED_MESSAGE = QApplication::translate("ADVClipboard", "Cannot put sequence data into the clipboard buffer.\n"
54                                                                                           "Probably, the data are too big.");
ADVClipboard(AnnotatedDNAView * c)55 ADVClipboard::ADVClipboard(AnnotatedDNAView *c)
56     : QObject(c), ctx(c) {
57     // TODO: listen seqadded/seqremoved!!
58 
59     connect(ctx, SIGNAL(si_activeSequenceWidgetChanged(ADVSequenceWidget *, ADVSequenceWidget *)), SLOT(sl_onActiveSequenceChanged()));
60 
61     foreach (ADVSequenceObjectContext *sCtx, ctx->getSequenceContexts()) {
62         connectSequence(sCtx);
63     }
64 
65     copySequenceAction = new QAction(QIcon(":/core/images/copy_sequence.png"), tr("Copy selected sequence"), this);
66     copySequenceAction->setObjectName("Copy sequence");
67     copySequenceAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_C));
68     connect(copySequenceAction, SIGNAL(triggered()), SLOT(sl_copySequence()));
69 
70     copyComplementSequenceAction = new QAction(QIcon(":/core/images/copy_complement_sequence.png"), tr("Copy selected complementary 5'-3' sequence"), this);
71     copyComplementSequenceAction->setObjectName("Copy reverse complement sequence");
72     copyComplementSequenceAction->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_C));
73     connect(copyComplementSequenceAction, SIGNAL(triggered()), SLOT(sl_copyComplementSequence()));
74 
75     copyTranslationAction = new QAction(QIcon(":/core/images/copy_translation.png"), tr("Copy amino acids"), this);
76     copyTranslationAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_T));
77     copyTranslationAction->setObjectName(ADV_COPY_TRANSLATION_ACTION);
78     connect(copyTranslationAction, SIGNAL(triggered()), SLOT(sl_copyTranslation()));
79 
80     copyComplementTranslationAction = new QAction(QIcon(":/core/images/copy_complement_translation.png"), tr("Copy amino acids of complementary 5'-3' strand"), this);
81     copyComplementTranslationAction->setObjectName("Copy reverse complement translation");
82     copyComplementTranslationAction->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_T));
83     connect(copyComplementTranslationAction, SIGNAL(triggered()), SLOT(sl_copyComplementTranslation()));
84 
85     copyAnnotationSequenceAction = new QAction(QIcon(":/core/images/copy_annotation_sequence.png"), tr("Copy annotation sequence"), this);
86     copyAnnotationSequenceAction->setObjectName("action_copy_annotation_sequence");
87     connect(copyAnnotationSequenceAction, SIGNAL(triggered()), SLOT(sl_copyAnnotationSequence()));
88 
89     copyAnnotationSequenceTranslationAction = new QAction(QIcon(":/core/images/copy_annotation_translation.png"), tr("Copy annotation amino acids"), this);
90     copyAnnotationSequenceTranslationAction->setObjectName("Copy annotation sequence translation");
91     connect(copyAnnotationSequenceTranslationAction, SIGNAL(triggered()), SLOT(sl_copyAnnotationSequenceTranslation()));
92 
93     copyQualifierAction = new QAction(QIcon(":/core/images/copy_qualifier.png"), tr("Copy qualifier text"), this);
94     copyQualifierAction->setEnabled(false);
95 
96     pasteSequenceAction = createPasteSequenceAction(this);
97     updateActions();
98 }
99 
getCopySequenceAction() const100 QAction *ADVClipboard::getCopySequenceAction() const {
101     return copySequenceAction;
102 }
103 
getCopyComplementAction() const104 QAction *ADVClipboard::getCopyComplementAction() const {
105     return copyComplementSequenceAction;
106 }
107 
getCopyTranslationAction() const108 QAction *ADVClipboard::getCopyTranslationAction() const {
109     return copyTranslationAction;
110 }
111 
getCopyComplementTranslationAction() const112 QAction *ADVClipboard::getCopyComplementTranslationAction() const {
113     return copyComplementTranslationAction;
114 }
115 
getCopyAnnotationSequenceAction() const116 QAction *ADVClipboard::getCopyAnnotationSequenceAction() const {
117     return copyAnnotationSequenceAction;
118 }
119 
getCopyAnnotationSequenceTranslationAction() const120 QAction *ADVClipboard::getCopyAnnotationSequenceTranslationAction() const {
121     return copyAnnotationSequenceTranslationAction;
122 }
123 
getCopyQualifierAction() const124 QAction *ADVClipboard::getCopyQualifierAction() const {
125     return copyQualifierAction;
126 }
127 
getPasteSequenceAction() const128 QAction *ADVClipboard::getPasteSequenceAction() const {
129     return pasteSequenceAction;
130 }
131 
connectSequence(ADVSequenceObjectContext * sCtx)132 void ADVClipboard::connectSequence(ADVSequenceObjectContext *sCtx) {
133     connect(sCtx->getSequenceSelection(),
134             SIGNAL(si_selectionChanged(LRegionsSelection *, const QVector<U2Region> &, const QVector<U2Region> &)),
135             SLOT(sl_onDNASelectionChanged(LRegionsSelection *, const QVector<U2Region> &, const QVector<U2Region> &)));
136 
137     connect(sCtx->getAnnotatedDNAView()->getAnnotationsSelection(),
138             SIGNAL(si_selectionChanged(AnnotationSelection *, const QList<Annotation *> &, const QList<Annotation *> &)),
139             SLOT(sl_onAnnotationSelectionChanged(AnnotationSelection *, const QList<Annotation *> &, const QList<Annotation *> &)));
140 }
141 
sl_onDNASelectionChanged(LRegionsSelection *,const QVector<U2Region> &,const QVector<U2Region> &)142 void ADVClipboard::sl_onDNASelectionChanged(LRegionsSelection *, const QVector<U2Region> &, const QVector<U2Region> &) {
143     updateActions();
144 }
145 
sl_onAnnotationSelectionChanged(AnnotationSelection *,const QList<Annotation * > &,const QList<Annotation * > &)146 void ADVClipboard::sl_onAnnotationSelectionChanged(AnnotationSelection *, const QList<Annotation *> &, const QList<Annotation *> &) {
147     updateActions();
148 }
149 
copySequenceSelection(bool complement,bool amino)150 void ADVClipboard::copySequenceSelection(bool complement, bool amino) {
151     ADVSequenceObjectContext *seqCtx = getSequenceContext();
152     if (seqCtx == nullptr) {
153         QMessageBox::critical(QApplication::activeWindow(), L10N::errorTitle(), "No sequence selected!");
154         return;
155     }
156 
157     QString res;
158     QVector<U2Region> regions = seqCtx->getSequenceSelection()->getSelectedRegions();
159     if (!regions.isEmpty()) {
160         U2SequenceObject *seqObj = seqCtx->getSequenceObject();
161         DNATranslation *complTT = complement ? seqCtx->getComplementTT() : nullptr;
162         DNATranslation *aminoTT = amino ? seqCtx->getAminoTT() : nullptr;
163         U2OpStatus2Log os;
164         QList<QByteArray> seqParts = U2SequenceUtils::extractRegions(seqObj->getSequenceRef(), regions, complTT, aminoTT, false, os);
165         if (os.hasError()) {
166             QMessageBox::critical(QApplication::activeWindow(), L10N::errorTitle(), tr("An error occurred during getting sequence data: %1").arg(os.getError()));
167             return;
168         }
169         res = U1SequenceUtils::joinRegions(seqParts);
170     }
171     putIntoClipboard(res);
172 }
173 
copyAnnotationSelection(const bool amino)174 void ADVClipboard::copyAnnotationSelection(const bool amino) {
175     const QList<Annotation *> &selectedAnnotationList = ctx->getAnnotationsSelection()->getAnnotations();
176     QByteArray res;
177     for (auto annotation : qAsConst(selectedAnnotationList)) {
178         if (!res.isEmpty()) {
179             res.append('\n');
180         }
181         ADVSequenceObjectContext *seqCtx = ctx->getSequenceContext(annotation->getGObject());
182         if (seqCtx == nullptr) {
183             res.append(U2Msa::GAP_CHAR);  // insert gap instead of the sequence, if the sequence is not available.
184             continue;
185         }
186         DNATranslation *complTT = annotation->getStrand().isCompementary() ? seqCtx->getComplementTT() : nullptr;
187         DNATranslation *aminoTT = amino ? seqCtx->getAminoTT() : nullptr;
188         U2OpStatus2Log os;
189         // BUG528: add alphabet symbol role: insertion mark and use it instead of the U2Msa::GAP_CHAR
190         AnnotationSelection::getSequenceInRegions(res, annotation->getRegions(), U2Msa::GAP_CHAR, seqCtx->getSequenceRef(), complTT, aminoTT, os);
191         CHECK_OP(os, );
192     }
193     putIntoClipboard(res);
194 }
195 
putIntoClipboard(const QString & data)196 void ADVClipboard::putIntoClipboard(const QString &data) {
197     CHECK(!data.isEmpty(), );
198     try {
199         QApplication::clipboard()->setText(data);
200     } catch (...) {
201         QMessageBox::critical(QApplication::activeWindow(), L10N::errorTitle(), COPY_FAILED_MESSAGE);
202     }
203 }
204 
sl_copySequence()205 void ADVClipboard::sl_copySequence() {
206     copySequenceSelection(false, false);
207 }
208 
sl_copyComplementSequence()209 void ADVClipboard::sl_copyComplementSequence() {
210     copySequenceSelection(true, false);
211 }
212 
sl_copyTranslation()213 void ADVClipboard::sl_copyTranslation() {
214     copySequenceSelection(false, true);
215 }
216 
sl_copyComplementTranslation()217 void ADVClipboard::sl_copyComplementTranslation() {
218     copySequenceSelection(true, true);
219 }
220 
sl_copyAnnotationSequence()221 void ADVClipboard::sl_copyAnnotationSequence() {
222     copyAnnotationSelection(false);
223 }
224 
sl_copyAnnotationSequenceTranslation()225 void ADVClipboard::sl_copyAnnotationSequenceTranslation() {
226     copyAnnotationSelection(true);
227 }
228 
sl_setCopyQualifierActionStatus(bool isEnabled,QString text)229 void ADVClipboard::sl_setCopyQualifierActionStatus(bool isEnabled, QString text) {
230     copyQualifierAction->setEnabled(isEnabled);
231     copyQualifierAction->setText(text);
232 }
233 
updateActions()234 void ADVClipboard::updateActions() {
235     ADVSequenceObjectContext *seqCtx = getSequenceContext();
236     CHECK(seqCtx != nullptr, );
237 
238     DNASequenceSelection *sel = seqCtx->getSequenceSelection();
239     SAFE_POINT(nullptr != sel, "DNASequenceSelection isn't found.", );
240 
241     const DNAAlphabet *alphabet = seqCtx->getAlphabet();
242     SAFE_POINT(nullptr != alphabet, "DNAAlphabet isn't found.", );
243 
244     const bool isNucleic = alphabet->isNucleic();
245     if (!isNucleic) {
246         copyTranslationAction->setVisible(false);
247         copyComplementSequenceAction->setVisible(false);
248         copyComplementTranslationAction->setVisible(false);
249 
250         copyAnnotationSequenceAction->setText(tr("Copy annotation"));
251         copyAnnotationSequenceTranslationAction->setVisible(false);
252     }
253 
254     auto setActionsEnabled =
255         [](const QList<QAction *> &copyActions, const bool isEnabled) {
256             for (QAction *action : qAsConst(copyActions)) {
257                 if (action != nullptr) {
258                     action->setEnabled(isEnabled);
259                 }
260             }
261         };
262     auto setActionShortcutsEnabled =
263         [](const QList<QAction *> &copyActions, const bool isShortcutEnabled) {
264             SAFE_POINT(copyActions.size() == 4, "copyActions size must be 4!", );
265             // [0] is copy sequence direct.
266             copyActions[0]->setShortcut(isShortcutEnabled ? QKeySequence(Qt::CTRL | Qt::Key_C) : QKeySequence());
267 
268             // [1] is copy sequence complement.
269             if (copyActions[1] != nullptr) {
270                 copyActions[1]->setShortcut(isShortcutEnabled ? QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_C) : QKeySequence());
271             }
272 
273             // [2] is copy amino direct.
274             copyActions[2]->setShortcut(isShortcutEnabled ? QKeySequence(Qt::CTRL | Qt::Key_T) : QKeySequence());
275 
276             // [3] is copy amino complement.
277             if (copyActions[3] != nullptr) {
278                 copyActions[3]->setShortcut(isShortcutEnabled ? QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_T) : QKeySequence());
279             }
280         };
281 
282     auto setActionsAndShortcutsEnabled =
283         [&setActionsEnabled, &setActionShortcutsEnabled](const QList<QAction *> &copyActions, const bool isEnabled) {
284             setActionsEnabled(copyActions, isEnabled);
285             setActionShortcutsEnabled(copyActions, isEnabled);
286         };
287 
288     const bool hasSequenceSelection = !sel->getSelectedRegions().isEmpty();
289     const bool hasAnnotationSelection = !ctx->getAnnotationsSelection()->isEmpty();
290     // Create lists of 4 selection actions for sequence & annotation selections: copy/copyComplement/copyTranslation/copyComplementTranslation.
291     QList<QAction *> sequenceActions = QList<QAction *>() << copySequenceAction << copyComplementSequenceAction << copyTranslationAction << copyComplementTranslationAction;
292     QList<QAction *> annotationActions = QList<QAction *>() << copyAnnotationSequenceAction << nullptr << copyAnnotationSequenceTranslationAction << nullptr;
293     if (!hasSequenceSelection && !hasAnnotationSelection) {
294         setActionsEnabled(sequenceActions, false);
295         setActionShortcutsEnabled(sequenceActions, true);  // Assign shortcuts to the currently disabled actions, so they will visually appear in the menu.
296         setActionsAndShortcutsEnabled(annotationActions, false);
297     } else if (hasSequenceSelection && hasAnnotationSelection) {
298         setActionsAndShortcutsEnabled(sequenceActions, true);
299         setActionsEnabled(annotationActions, true);
300         setActionShortcutsEnabled(annotationActions, false);
301     } else {
302         setActionsAndShortcutsEnabled(sequenceActions, hasSequenceSelection);
303         setActionsAndShortcutsEnabled(annotationActions, hasAnnotationSelection);
304     }
305 }
306 
addCopyMenu(QMenu * m)307 void ADVClipboard::addCopyMenu(QMenu *m) {
308     QMenu *copyMenu = new QMenu(tr("Copy/Paste"), m);
309     copyMenu->menuAction()->setObjectName(ADV_MENU_COPY);
310 
311     copyMenu->addAction(copySequenceAction);
312     copyMenu->addAction(copyComplementSequenceAction);
313     copyMenu->addAction(copyTranslationAction);
314     copyMenu->addAction(copyComplementTranslationAction);
315     copyMenu->addSeparator();
316     copyMenu->addAction(copyAnnotationSequenceAction);
317     copyMenu->addAction(copyAnnotationSequenceTranslationAction);
318     copyMenu->addSeparator();
319     copyMenu->addAction(copyQualifierAction);
320     copyMenu->addSeparator();
321     copyMenu->addAction(pasteSequenceAction);
322 
323     m->addMenu(copyMenu);
324 }
325 
createPasteSequenceAction(QObject * parent)326 QAction *ADVClipboard::createPasteSequenceAction(QObject *parent) {
327     QAction *action = new QAction(QIcon(":/core/images/paste.png"), tr("Paste sequence"), parent);
328     action->setObjectName("Paste sequence");
329     action->setShortcuts(QKeySequence::Paste);
330     action->setShortcutContext(Qt::WidgetWithChildrenShortcut);
331     return action;
332 }
333 
getSequenceContext() const334 ADVSequenceObjectContext *ADVClipboard::getSequenceContext() const {
335     return ctx->getActiveSequenceContext();
336 }
337 
sl_onActiveSequenceChanged()338 void ADVClipboard::sl_onActiveSequenceChanged() {
339     updateActions();
340 }
341 }  // namespace U2
342