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 *> ©Actions, 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 *> ©Actions, 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 *> ©Actions, 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