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 "MSAEditor.h"
23 
24 #include <QDropEvent>
25 
26 #include <U2Core/AddSequencesToAlignmentTask.h>
27 #include <U2Core/AppContext.h>
28 #include <U2Core/DNASequenceObject.h>
29 #include <U2Core/GObjectSelection.h>
30 #include <U2Core/GUrlUtils.h>
31 #include <U2Core/QObjectScopedPointer.h>
32 #include <U2Core/Settings.h>
33 #include <U2Core/TaskWatchdog.h>
34 #include <U2Core/U2AlphabetUtils.h>
35 #include <U2Core/U2Mod.h>
36 #include <U2Core/U2OpStatusUtils.h>
37 
38 #include <U2Gui/DialogUtils.h>
39 #include <U2Gui/ExportImageDialog.h>
40 #include <U2Gui/GUIUtils.h>
41 #include <U2Gui/GroupHeaderImageWidget.h>
42 #include <U2Gui/GroupOptionsWidget.h>
43 #include <U2Gui/OPWidgetFactoryRegistry.h>
44 #include <U2Gui/OptionsPanel.h>
45 #include <U2Gui/OptionsPanelWidget.h>
46 #include <U2Gui/ProjectView.h>
47 
48 #include <U2View/ColorSchemaSettingsController.h>
49 #include <U2View/FindPatternMsaWidgetFactory.h>
50 
51 #include "MSAEditorOffsetsView.h"
52 #include "MSAEditorSequenceArea.h"
53 #include "MaEditorFactory.h"
54 #include "MaEditorNameList.h"
55 #include "MaEditorTasks.h"
56 #include "export/MSAImageExportTask.h"
57 #include "highlighting/MsaSchemesMenuBuilder.h"
58 #include "move_to_object/MoveToObjectMaController.h"
59 #include "overview/MaEditorOverviewArea.h"
60 #include "realign_to_alignment/RealignSequencesInAlignmentTask.h"
61 #include "view_rendering/MaEditorConsensusArea.h"
62 #include "view_rendering/MaEditorSelection.h"
63 #include "view_rendering/MaEditorSequenceArea.h"
64 
65 namespace U2 {
66 
67 const QString MsaEditorMenuType::ALIGN("msa-editor-menu-align");
68 const QString MsaEditorMenuType::ALIGN_SEQUENCES_TO_ALIGNMENT("msa-editor-menu-align-sequences-to-alignment");
69 
MSAEditor(const QString & viewName,MultipleSequenceAlignmentObject * obj)70 MSAEditor::MSAEditor(const QString &viewName, MultipleSequenceAlignmentObject *obj)
71     : MaEditor(MsaEditorFactory::ID, viewName, obj),
72       treeManager(this) {
73     selectionController = new MaEditorSelectionController(this);
74 
75     gotoAction = nullptr;
76     searchInSequencesAction = nullptr;
77     searchInSequenceNamesAction = nullptr;
78 
79     sortByNameAscendingAction = new QAction(tr("By name"), this);
80     sortByNameAscendingAction->setObjectName("action_sort_by_name");
81     sortByNameAscendingAction->setToolTip(tr("Sort selected sequences range or the whole alignment by name, ascending"));
82     connect(sortByNameAscendingAction, SIGNAL(triggered()), SLOT(sl_sortSequencesByName()));
83 
84     sortByNameDescendingAction = new QAction(tr("By name, descending"), this);
85     sortByNameDescendingAction->setObjectName("action_sort_by_name_descending");
86     sortByNameDescendingAction->setToolTip(tr("Sort selected sequences range or the whole alignment by name, descending"));
87     connect(sortByNameDescendingAction, SIGNAL(triggered()), SLOT(sl_sortSequencesByName()));
88 
89     sortByLengthAscendingAction = new QAction(tr("By length"), this);
90     sortByLengthAscendingAction->setObjectName("action_sort_by_length");
91     sortByLengthAscendingAction->setToolTip(tr("Sort selected sequences range or the whole alignment by length, ascending"));
92     connect(sortByLengthAscendingAction, SIGNAL(triggered()), SLOT(sl_sortSequencesByLength()));
93 
94     sortByLengthDescendingAction = new QAction(tr("By length, descending"), this);
95     sortByLengthDescendingAction->setObjectName("action_sort_by_length_descending");
96     sortByLengthDescendingAction->setToolTip(tr("Sort selected sequences range or the whole alignment by length, descending"));
97     connect(sortByLengthDescendingAction, SIGNAL(triggered()), SLOT(sl_sortSequencesByLength()));
98 
99     sortByLeadingGapAscendingAction = new QAction(tr("By leading gap"), this);
100     sortByLeadingGapAscendingAction->setObjectName("action_sort_by_leading_gap");
101     sortByLeadingGapAscendingAction->setToolTip(tr("Sort selected sequences range or the whole alignment by leading gap, ascending"));
102     connect(sortByLeadingGapAscendingAction, SIGNAL(triggered()), SLOT(sl_sortSequencesByLeadingGap()));
103 
104     sortByLeadingGapDescendingAction = new QAction(tr("By leading gap, descending"), this);
105     sortByLeadingGapDescendingAction->setObjectName("action_sort_by_leading_gap_descending");
106     sortByLeadingGapDescendingAction->setToolTip(tr("Sort selected sequences range or the whole alignment by leading gap, descending"));
107     connect(sortByLeadingGapDescendingAction, SIGNAL(triggered()), SLOT(sl_sortSequencesByLeadingGap()));
108 
109     openCustomSettingsAction = new QAction(tr("Create new color scheme"), this);
110     openCustomSettingsAction->setObjectName("Create new color scheme");
111     connect(openCustomSettingsAction, SIGNAL(triggered()), SLOT(sl_showCustomSettings()));
112 
113     sortGroupsBySizeAscendingAction = new QAction(tr("Sort groups, small first"), this);
114     sortGroupsBySizeAscendingAction->setObjectName("action_sort_groups_by_size_ascending");
115     sortGroupsBySizeAscendingAction->setToolTip(tr("Sort groups by number of sequences in the group, ascending"));
116     connect(sortGroupsBySizeAscendingAction, SIGNAL(triggered()), SLOT(sl_sortGroupsBySize()));
117 
118     sortGroupsBySizeDescendingAction = new QAction(tr("Sort groups, large first"), this);
119     sortGroupsBySizeDescendingAction->setObjectName("action_sort_groups_by_size_descending");
120     sortGroupsBySizeDescendingAction->setToolTip(tr("Sort groups by number of sequences in the group, descending"));
121     connect(sortGroupsBySizeDescendingAction, SIGNAL(triggered()), SLOT(sl_sortGroupsBySize()));
122 
123     saveScreenshotAction = new QAction(QIcon(":/core/images/cam2.png"), tr("Export as image"), this);
124     saveScreenshotAction->setObjectName("export_msa_as_image_action");
125     connect(saveScreenshotAction, &QAction::triggered, this, &MSAEditor::sl_exportImage);
126 
127     initZoom();
128     initFont();
129 
130     buildTreeAction = new QAction(QIcon(":/core/images/phylip.png"), tr("Build Tree"), this);
131     buildTreeAction->setObjectName("Build Tree");
132     buildTreeAction->setEnabled(!isAlignmentEmpty());
133     connect(maObject, SIGNAL(si_rowsRemoved(const QList<qint64> &)), SLOT(sl_rowsRemoved(const QList<qint64> &)));
134     connect(buildTreeAction, SIGNAL(triggered()), SLOT(sl_buildTree()));
135 
136     realignSomeSequenceAction = new QAction(QIcon(":/core/images/realign_some_sequences.png"), tr("Realign sequence(s) to other sequences"), this);
137     realignSomeSequenceAction->setObjectName("Realign sequence(s) to other sequences");
138 
139     pairwiseAlignmentWidgetsSettings = new PairwiseAlignmentWidgetsSettings;
140     if (maObject->getAlphabet() != nullptr) {
141         pairwiseAlignmentWidgetsSettings->customSettings.insert("alphabet", maObject->getAlphabet()->getId());
142     }
143 
144     convertDnaToRnaAction = new QAction(tr("Convert to RNA alphabet (T->U)"), this);
145     convertDnaToRnaAction->setObjectName("convertDnaToRnaAction");
146     convertDnaToRnaAction->setToolTip(tr("Convert alignment from DNA to RNA alphabet: replace T with U"));
147     connect(convertDnaToRnaAction, SIGNAL(triggered()), SLOT(sl_convertBetweenDnaAndRnaAlphabets()));
148 
149     convertRnaToDnaAction = new QAction(tr("Convert to DNA alphabet (U->T)"), this);
150     convertRnaToDnaAction->setObjectName("convertRnaToDnaAction");
151     convertRnaToDnaAction->setToolTip(tr("Convert alignment from RNA to DNA alphabet: replace U with T"));
152     connect(convertRnaToDnaAction, SIGNAL(triggered()), SLOT(sl_convertBetweenDnaAndRnaAlphabets()));
153 
154     convertRawToDnaAction = new QAction(tr("Convert RAW to DNA alphabet"), this);
155     convertRawToDnaAction->setObjectName("convertRawToDnaAction");
156     convertRawToDnaAction->setToolTip(tr("Convert alignment from RAW to DNA alphabet: use N for unknown symbols"));
157     connect(convertRawToDnaAction, SIGNAL(triggered()), SLOT(sl_convertRawToDnaAlphabet()));
158 
159     convertRawToAminoAction = new QAction(tr("Convert RAW to Amino alphabet"), this);
160     convertRawToAminoAction->setObjectName("convertRawToAminoAction");
161     convertRawToAminoAction->setToolTip(tr("Convert alignment from RAW to Amino alphabet: use X for unknown symbols"));
162     connect(convertRawToAminoAction, SIGNAL(triggered()), SLOT(sl_convertRawToAminoAlphabet()));
163 
164     updateActions();
165 }
166 
updateActions()167 void MSAEditor::updateActions() {
168     MaEditor::updateActions();
169     bool isReadOnly = maObject->isStateLocked();
170 
171     sortByNameAscendingAction->setEnabled(!isReadOnly);
172     sortByNameDescendingAction->setEnabled(!isReadOnly);
173     sortByLengthAscendingAction->setEnabled(!isReadOnly);
174     sortByLengthDescendingAction->setEnabled(!isReadOnly);
175 
176     if (alignSequencesToAlignmentAction != nullptr) {
177         alignSequencesToAlignmentAction->setEnabled(!isReadOnly);
178     }
179     buildTreeAction->setEnabled(!isReadOnly && !isAlignmentEmpty());
180     sl_updateRealignAction();
181 
182     auto alphabetId = maObject->getAlphabet()->getId();
183     convertDnaToRnaAction->setEnabled(!isReadOnly && alphabetId == BaseDNAAlphabetIds::NUCL_DNA_DEFAULT());
184     convertRnaToDnaAction->setEnabled(!isReadOnly && alphabetId == BaseDNAAlphabetIds::NUCL_RNA_DEFAULT());
185     convertRawToDnaAction->setEnabled(!isReadOnly && alphabetId == BaseDNAAlphabetIds::RAW());
186     convertRawToAminoAction->setEnabled(!isReadOnly && alphabetId == BaseDNAAlphabetIds::RAW());
187 
188     // Sorting of groups is enabled only on "group by content" mode.
189     // This 'virtual' mode is 100% managed by MSA Editor and is not saved to file.
190     bool isGroupBySequenceContent = getRowOrderMode() == MaEditorRowOrderMode::Sequence;
191     sortGroupsBySizeAscendingAction->setEnabled(isGroupBySequenceContent);
192     sortGroupsBySizeDescendingAction->setEnabled(isGroupBySequenceContent);
193 }
194 
sl_buildTree()195 void MSAEditor::sl_buildTree() {
196     treeManager.buildTreeWithDialog();
197 }
198 
onObjectRemoved(GObject * obj)199 bool MSAEditor::onObjectRemoved(GObject *obj) {
200     bool result = GObjectView::onObjectRemoved(obj);
201 
202     obj->disconnect(ui->getSequenceArea());
203     obj->disconnect(ui->getConsensusArea());
204     obj->disconnect(ui->getEditorNameList());
205     return result;
206 }
207 
onObjectRenamed(GObject *,const QString &)208 void MSAEditor::onObjectRenamed(GObject *, const QString &) {
209     // update title
210     OpenMaEditorTask::updateTitle(this);
211 }
212 
onCloseEvent()213 bool MSAEditor::onCloseEvent() {
214     if (ui->getOverviewArea() != nullptr) {
215         ui->getOverviewArea()->cancelRendering();
216     }
217     return true;
218 }
219 
getRowByViewRowIndex(int viewRowIndex) const220 MultipleSequenceAlignmentRow MSAEditor::getRowByViewRowIndex(int viewRowIndex) const {
221     int maRowIndex = collapseModel->getMaRowIndexByViewRowIndex(viewRowIndex);
222     return getMaObject()->getMsaRow(maRowIndex);
223 }
224 
~MSAEditor()225 MSAEditor::~MSAEditor() {
226     delete pairwiseAlignmentWidgetsSettings;
227 }
228 
buildStaticToolbar(QToolBar * tb)229 void MSAEditor::buildStaticToolbar(QToolBar *tb) {
230     tb->addAction(ui->copyFormattedSelectionAction);
231 
232     tb->addAction(saveAlignmentAction);
233     tb->addAction(saveAlignmentAsAction);
234 
235     tb->addAction(zoomInAction);
236     tb->addAction(zoomOutAction);
237     tb->addAction(zoomToSelectionAction);
238     tb->addAction(resetZoomAction);
239 
240     tb->addAction(showOverviewAction);
241     tb->addAction(changeFontAction);
242 
243     tb->addAction(saveScreenshotAction);
244     tb->addAction(buildTreeAction);
245     tb->addAction(alignAction);
246     tb->addAction(alignSequencesToAlignmentAction);
247     tb->addAction(realignSomeSequenceAction);
248 
249     GObjectView::buildStaticToolbar(tb);
250 }
251 
buildMenu(QMenu * m,const QString & type)252 void MSAEditor::buildMenu(QMenu *m, const QString &type) {
253     if (type != MsaEditorMenuType::STATIC) {
254         GObjectView::buildMenu(m, type);
255         return;
256     }
257     addAppearanceMenu(m);
258 
259     addNavigationMenu(m);
260 
261     addLoadMenu(m);
262 
263     addCopyPasteMenu(m);
264     addEditMenu(m);
265     addSortMenu(m);
266 
267     addAlignMenu(m);
268     addTreeMenu(m);
269     addStatisticsMenu(m);
270 
271     addExportMenu(m);
272 
273     addAdvancedMenu(m);
274 
275     GObjectView::buildMenu(m, type);
276 
277     GUIUtils::disableEmptySubmenus(m);
278 }
279 
addCopyPasteMenu(QMenu * m)280 void MSAEditor::addCopyPasteMenu(QMenu *m) {
281     MaEditor::addCopyPasteMenu(m);
282 
283     QMenu *copyMenu = GUIUtils::findSubMenu(m, MSAE_MENU_COPY);
284     SAFE_POINT(copyMenu != nullptr, "copyMenu is null", );
285 
286     const MaEditorSelection &selection = getSelection();
287     ui->copySelectionAction->setDisabled(selection.isEmpty());
288 
289     // TODO:? move the signal emit point to a correct location.
290     auto sequenceArea = qobject_cast<MSAEditorSequenceArea *>(ui->getSequenceArea());
291     SAFE_POINT(sequenceArea != nullptr, "sequenceArea is null", );
292     emit sequenceArea->si_copyFormattedChanging(!selection.isEmpty());
293 
294     copyMenu->addAction(ui->copySelectionAction);
295     ui->copyFormattedSelectionAction->setDisabled(selection.isEmpty());
296     copyMenu->addAction(ui->copyFormattedSelectionAction);
297     copyMenu->addAction(copyConsensusAction);
298     copyMenu->addAction(copyConsensusWithGapsAction);
299     copyMenu->addSeparator();
300     copyMenu->addAction(ui->pasteAction);
301     copyMenu->addAction(ui->pasteBeforeAction);
302     copyMenu->addSeparator();
303     copyMenu->addAction(ui->cutSelectionAction);
304 
305     copyMenu->addSeparator();
306     MaEditorNameList *nameList = ui->getEditorNameList();
307     copyMenu->addAction(nameList->copyWholeRowAction);
308 }
309 
addEditMenu(QMenu * m)310 void MSAEditor::addEditMenu(QMenu *m) {
311     QMenu *menu = m->addMenu(tr("Edit"));
312     menu->menuAction()->setObjectName(MSAE_MENU_EDIT);
313 }
314 
addSortMenu(QMenu * m)315 void MSAEditor::addSortMenu(QMenu *m) {
316     QMenu *menu = m->addMenu(tr("Sort"));
317     menu->menuAction()->setObjectName(MSAE_MENU_SORT);
318     menu->addAction(sortByNameAscendingAction);
319     menu->addAction(sortByNameDescendingAction);
320     menu->addAction(sortByLengthAscendingAction);
321     menu->addAction(sortByLengthDescendingAction);
322     menu->addAction(sortByLeadingGapAscendingAction);
323     menu->addAction(sortByLeadingGapDescendingAction);
324 
325     if (getRowOrderMode() == MaEditorRowOrderMode::Sequence) {
326         menu->addSeparator();
327         menu->addAction(sortGroupsBySizeDescendingAction);
328         menu->addAction(sortGroupsBySizeAscendingAction);
329     }
330 }
331 
addExportMenu(QMenu * m)332 void MSAEditor::addExportMenu(QMenu *m) {
333     MaEditor::addExportMenu(m);
334     QMenu *em = GUIUtils::findSubMenu(m, MSAE_MENU_EXPORT);
335     SAFE_POINT(em != nullptr, "Export menu not found", );
336     em->addAction(saveScreenshotAction);
337 }
338 
addAppearanceMenu(QMenu * m)339 void MSAEditor::addAppearanceMenu(QMenu *m) {
340     QMenu *appearanceMenu = m->addMenu(tr("Appearance"));
341     appearanceMenu->menuAction()->setObjectName(MSAE_MENU_APPEARANCE);
342 
343     appearanceMenu->addAction(showOverviewAction);
344     auto offsetsController = ui->getOffsetsViewController();
345     if (offsetsController != nullptr) {
346         appearanceMenu->addAction(offsetsController->toggleColumnsViewAction);
347     }
348     appearanceMenu->addSeparator();
349     appearanceMenu->addAction(zoomInAction);
350     appearanceMenu->addAction(zoomOutAction);
351     appearanceMenu->addAction(zoomToSelectionAction);
352     appearanceMenu->addAction(resetZoomAction);
353     appearanceMenu->addSeparator();
354 
355     addColorsMenu(appearanceMenu);
356     addHighlightingMenu(appearanceMenu);
357     appearanceMenu->addSeparator();
358 
359     appearanceMenu->addAction(changeFontAction);
360     appearanceMenu->addSeparator();
361 
362     appearanceMenu->addAction(clearSelectionAction);
363 }
364 
addColorsMenu(QMenu * m)365 void MSAEditor::addColorsMenu(QMenu *m) {
366     QMenu *colorsSchemeMenu = m->addMenu(tr("Colors"));
367     colorsSchemeMenu->menuAction()->setObjectName("Colors");
368     colorsSchemeMenu->setIcon(QIcon(":core/images/color_wheel.png"));
369     auto sequenceArea = ui->getSequenceArea();
370     foreach (QAction *a, sequenceArea->colorSchemeMenuActions) {
371         MsaSchemesMenuBuilder::addActionOrTextSeparatorToMenu(a, colorsSchemeMenu);
372     }
373     colorsSchemeMenu->addSeparator();
374 
375     QMenu *customColorSchemaMenu = new QMenu(tr("Custom schemes"), colorsSchemeMenu);
376     customColorSchemaMenu->menuAction()->setObjectName("Custom schemes");
377 
378     foreach (QAction *a, sequenceArea->customColorSchemeMenuActions) {
379         MsaSchemesMenuBuilder::addActionOrTextSeparatorToMenu(a, customColorSchemaMenu);
380     }
381 
382     if (!sequenceArea->customColorSchemeMenuActions.isEmpty()) {
383         customColorSchemaMenu->addSeparator();
384     }
385 
386     customColorSchemaMenu->addAction(openCustomSettingsAction);
387 
388     colorsSchemeMenu->addMenu(customColorSchemaMenu);
389     m->insertMenu(GUIUtils::findAction(m->actions(), MSAE_MENU_EDIT), colorsSchemeMenu);
390 }
391 
addHighlightingMenu(QMenu * m)392 void MSAEditor::addHighlightingMenu(QMenu *m) {
393     QMenu *highlightSchemeMenu = new QMenu(tr("Highlighting"), nullptr);
394 
395     highlightSchemeMenu->menuAction()->setObjectName("Highlighting");
396 
397     auto sequenceArea = ui->getSequenceArea();
398     foreach (QAction *a, sequenceArea->highlightingSchemeMenuActions) {
399         MsaSchemesMenuBuilder::addActionOrTextSeparatorToMenu(a, highlightSchemeMenu);
400     }
401     highlightSchemeMenu->addSeparator();
402     highlightSchemeMenu->addAction(sequenceArea->useDotsAction);
403     m->insertMenu(GUIUtils::findAction(m->actions(), MSAE_MENU_EDIT), highlightSchemeMenu);
404 }
405 
addNavigationMenu(QMenu * m)406 void MSAEditor::addNavigationMenu(QMenu *m) {
407     QMenu *navMenu = m->addMenu(tr("Navigation"));
408     navMenu->menuAction()->setObjectName(MSAE_MENU_NAVIGATION);
409     navMenu->addAction(gotoAction);
410     navMenu->addSeparator();
411     navMenu->addAction(searchInSequencesAction);
412     navMenu->addAction(searchInSequenceNamesAction);
413 }
414 
addTreeMenu(QMenu * m)415 void MSAEditor::addTreeMenu(QMenu *m) {
416     QMenu *em = m->addMenu(tr("Tree"));
417     // em->setIcon(QIcon(":core/images/tree.png"));
418     em->menuAction()->setObjectName(MSAE_MENU_TREES);
419     em->addAction(buildTreeAction);
420 }
421 
addAdvancedMenu(QMenu * m)422 void MSAEditor::addAdvancedMenu(QMenu *m) {
423     QMenu *menu = m->addMenu(tr("Advanced"));
424     menu->menuAction()->setObjectName(MSAE_MENU_ADVANCED);
425 
426     if (convertDnaToRnaAction->isEnabled()) {
427         menu->addAction(convertDnaToRnaAction);
428     } else if (convertRnaToDnaAction->isEnabled()) {
429         menu->addAction(convertRnaToDnaAction);
430     }
431 }
432 
addStatisticsMenu(QMenu * m)433 void MSAEditor::addStatisticsMenu(QMenu *m) {
434     QMenu *em = m->addMenu(tr("Statistics"));
435     em->setIcon(QIcon(":core/images/chart_bar.png"));
436     em->menuAction()->setObjectName(MSAE_MENU_STATISTICS);
437 }
438 
getUI() const439 MsaEditorWgt *MSAEditor::getUI() const {
440     return qobject_cast<MsaEditorWgt *>(ui);
441 }
442 
createWidget()443 QWidget *MSAEditor::createWidget() {
444     Q_ASSERT(ui == nullptr);
445     ui = new MsaEditorWgt(this);
446 
447     QString objName = "msa_editor_" + maObject->getGObjectName();
448     ui->setObjectName(objName);
449 
450     initActions();
451 
452     connect(ui, SIGNAL(customContextMenuRequested(const QPoint &)), SLOT(sl_onContextMenuRequested(const QPoint &)));
453 
454     gotoAction = new QAction(QIcon(":core/images/goto.png"), tr("Go to position…"), this);
455     gotoAction->setObjectName("action_go_to_position");
456     gotoAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_G));
457     gotoAction->setShortcutContext(Qt::WindowShortcut);
458     gotoAction->setToolTip(QString("%1 (%2)").arg(gotoAction->text()).arg(gotoAction->shortcut().toString()));
459     connect(gotoAction, SIGNAL(triggered()), ui->getSequenceArea(), SLOT(sl_goto()));
460 
461     searchInSequencesAction = new QAction(QIcon(":core/images/find_dialog.png"), tr("Search in sequences…"), this);
462     searchInSequencesAction->setObjectName("search_in_sequences");
463     searchInSequencesAction->setShortcut(QKeySequence(Qt::CTRL | Qt::Key_F));
464     searchInSequencesAction->setShortcutContext(Qt::WindowShortcut);
465     searchInSequencesAction->setToolTip(QString("%1 (%2)").arg(searchInSequencesAction->text()).arg(searchInSequencesAction->shortcut().toString()));
466     connect(searchInSequencesAction, SIGNAL(triggered()), this, SLOT(sl_searchInSequences()));
467 
468     searchInSequenceNamesAction = new QAction(QIcon(":core/images/find_dialog.png"), tr("Search in sequence names…"), this);
469     searchInSequenceNamesAction->setObjectName("search_in_sequence_names");
470     searchInSequenceNamesAction->setShortcut(QKeySequence(Qt::CTRL | Qt::SHIFT | Qt::Key_F));
471     searchInSequenceNamesAction->setShortcutContext(Qt::WindowShortcut);
472     searchInSequenceNamesAction->setToolTip(QString("%1 (%2)").arg(searchInSequenceNamesAction->text()).arg(searchInSequenceNamesAction->shortcut().toString()));
473     connect(searchInSequenceNamesAction, SIGNAL(triggered()), this, SLOT(sl_searchInSequenceNames()));
474 
475     alignAction = new QAction(QIcon(":core/images/align.png"), tr("Align"), this);
476     alignAction->setObjectName("Align");
477     connect(alignAction, SIGNAL(triggered()), this, SLOT(sl_align()));
478 
479     alignSequencesToAlignmentAction = new QAction(QIcon(":/core/images/add_to_alignment.png"), tr("Align sequence(s) to this alignment"), this);
480     alignSequencesToAlignmentAction->setObjectName("Align sequence(s) to this alignment");
481     connect(alignSequencesToAlignmentAction, SIGNAL(triggered()), this, SLOT(sl_addToAlignment()));
482 
483     setAsReferenceSequenceAction = new QAction(tr("Set this sequence as reference"), this);
484     setAsReferenceSequenceAction->setObjectName("set_seq_as_reference");
485     connect(setAsReferenceSequenceAction, SIGNAL(triggered()), SLOT(sl_setSeqAsReference()));
486 
487     unsetReferenceSequenceAction = new QAction(tr("Unset reference sequence"), this);
488     unsetReferenceSequenceAction->setObjectName("unset_reference");
489     connect(unsetReferenceSequenceAction, SIGNAL(triggered()), SLOT(sl_unsetReferenceSeq()));
490 
491     optionsPanel = new OptionsPanel(this);
492     OPWidgetFactoryRegistry *opWidgetFactoryRegistry = AppContext::getOPWidgetFactoryRegistry();
493 
494     QList<OPFactoryFilterVisitorInterface *> filters;
495     filters.append(new OPFactoryFilterVisitor(ObjViewType_AlignmentEditor));
496 
497     QList<OPWidgetFactory *> opWidgetFactories = opWidgetFactoryRegistry->getRegisteredFactories(filters);
498     foreach (OPWidgetFactory *factory, opWidgetFactories) {
499         optionsPanel->addGroup(factory);
500     }
501 
502     connect(realignSomeSequenceAction, SIGNAL(triggered()), this, SLOT(sl_realignSomeSequences()));
503     connect(maObject, SIGNAL(si_alphabetChanged(const MaModificationInfo &, const DNAAlphabet *)), SLOT(sl_updateRealignAction()));
504     connect(getSelectionController(),
505             SIGNAL(si_selectionChanged(const MaEditorSelection &, const MaEditorSelection &)),
506             SLOT(sl_updateRealignAction()));
507 
508     qDeleteAll(filters);
509 
510     connect(ui, SIGNAL(si_showTreeOP()), SLOT(sl_showTreeOP()));
511     connect(ui, SIGNAL(si_hideTreeOP()), SLOT(sl_hideTreeOP()));
512     sl_hideTreeOP();
513 
514     treeManager.loadRelatedTrees();
515 
516     new MoveToObjectMaController(this);
517 
518     initDragAndDropSupport();
519     updateActions();
520     return ui;
521 }
522 
sl_onContextMenuRequested(const QPoint &)523 void MSAEditor::sl_onContextMenuRequested(const QPoint & /*pos*/) {
524     QMenu m;
525 
526     addAppearanceMenu(&m);
527     addNavigationMenu(&m);
528     addLoadMenu(&m);
529     addCopyPasteMenu(&m);
530     addEditMenu(&m);
531     addSortMenu(&m);
532     m.addSeparator();
533 
534     addAlignMenu(&m);
535     addTreeMenu(&m);
536     addStatisticsMenu(&m);
537     addExportMenu(&m);
538     addAdvancedMenu(&m);
539 
540     m.addSeparator();
541     snp.clickPoint = QCursor::pos();
542     const QPoint nameMapped = ui->getEditorNameList()->mapFromGlobal(snp.clickPoint);
543     const qint64 hoverRowId = (0 <= nameMapped.y()) ? ui->getEditorNameList()->sequenceIdAtPos(nameMapped) : U2MsaRow::INVALID_ROW_ID;
544     if ((hoverRowId != getReferenceRowId() || U2MsaRow::INVALID_ROW_ID == getReferenceRowId()) && hoverRowId != U2MsaRow::INVALID_ROW_ID) {
545         m.addAction(setAsReferenceSequenceAction);
546     }
547     if (U2MsaRow::INVALID_ROW_ID != getReferenceRowId()) {
548         m.addAction(unsetReferenceSequenceAction);
549     }
550     m.addSeparator();
551 
552     emit si_buildMenu(this, &m, MsaEditorMenuType::CONTEXT);
553 
554     GUIUtils::disableEmptySubmenus(&m);
555 
556     m.exec(QCursor::pos());
557 }
558 
sl_showTreeOP()559 void MSAEditor::sl_showTreeOP() {
560     OptionsPanelWidget *opWidget = dynamic_cast<OptionsPanelWidget *>(optionsPanel->getMainWidget());
561     if (opWidget == nullptr) {
562         return;
563     }
564 
565     QWidget *addTreeGroupWidget = opWidget->findOptionsWidgetByGroupId("OP_MSA_ADD_TREE_WIDGET");
566     if (addTreeGroupWidget != nullptr) {
567         addTreeGroupWidget->hide();
568         opWidget->closeOptionsPanel();
569     }
570     QWidget *addTreeHeader = opWidget->findHeaderWidgetByGroupId("OP_MSA_ADD_TREE_WIDGET");
571     if (addTreeHeader != nullptr) {
572         addTreeHeader->hide();
573     }
574 
575     GroupHeaderImageWidget *header = opWidget->findHeaderWidgetByGroupId("OP_MSA_TREES_WIDGET");
576     if (header != nullptr) {
577         header->show();
578         header->changeState();
579     }
580 }
581 
sl_hideTreeOP()582 void MSAEditor::sl_hideTreeOP() {
583     OptionsPanelWidget *opWidget = dynamic_cast<OptionsPanelWidget *>(optionsPanel->getMainWidget());
584     if (opWidget == nullptr) {
585         return;
586     }
587     GroupHeaderImageWidget *header = opWidget->findHeaderWidgetByGroupId("OP_MSA_TREES_WIDGET");
588     QWidget *groupWidget = opWidget->findOptionsWidgetByGroupId("OP_MSA_TREES_WIDGET");
589     bool openAddTreeGroup = groupWidget != nullptr;
590     header->hide();
591 
592     GroupHeaderImageWidget *addTreeHeader = opWidget->findHeaderWidgetByGroupId("OP_MSA_ADD_TREE_WIDGET");
593     if (addTreeHeader != nullptr) {
594         addTreeHeader->show();
595         if (openAddTreeGroup) {
596             addTreeHeader->changeState();
597         }
598     }
599 }
600 
eventFilter(QObject *,QEvent * e)601 bool MSAEditor::eventFilter(QObject *, QEvent *e) {
602     if (e->type() == QEvent::DragEnter || e->type() == QEvent::Drop) {
603         QDropEvent *de = (QDropEvent *)e;
604         const QMimeData *md = de->mimeData();
605         const GObjectMimeData *gomd = qobject_cast<const GObjectMimeData *>(md);
606         if (gomd != nullptr) {
607             CHECK(!maObject->isStateLocked(), false)
608             U2SequenceObject *dnaObj = qobject_cast<U2SequenceObject *>(gomd->objPtr.data());
609             if (dnaObj != nullptr) {
610                 if (U2AlphabetUtils::deriveCommonAlphabet(dnaObj->getAlphabet(), maObject->getAlphabet()) == nullptr) {
611                     return false;
612                 }
613                 if (e->type() == QEvent::DragEnter) {
614                     de->acceptProposedAction();
615                 } else {
616                     U2OpStatusImpl os;
617                     DNASequence seq = dnaObj->getWholeSequence(os);
618                     seq.alphabet = dnaObj->getAlphabet();
619                     Task *task = new AddSequenceObjectsToAlignmentTask(getMaObject(), QList<DNASequence>() << seq);
620                     TaskWatchdog::trackResourceExistence(maObject, task, tr("A problem occurred during adding sequences. The multiple alignment is no more available."));
621                     AppContext::getTaskScheduler()->registerTopLevelTask(task);
622                 }
623             }
624         }
625     }
626 
627     return false;
628 }
629 
initDragAndDropSupport()630 void MSAEditor::initDragAndDropSupport() {
631     SAFE_POINT(ui != nullptr, QString("MSAEditor::ui is not initialized in MSAEditor::initDragAndDropSupport"), );
632     ui->setAcceptDrops(true);
633     ui->installEventFilter(this);
634 }
635 
sl_align()636 void MSAEditor::sl_align() {
637     QMenu menu;
638     emit si_buildMenu(this, &menu, MsaEditorMenuType::ALIGN);
639     menu.exec(QCursor::pos());
640 }
641 
sl_addToAlignment()642 void MSAEditor::sl_addToAlignment() {
643     QMenu menu;
644     emit si_buildMenu(this, &menu, MsaEditorMenuType::ALIGN_SEQUENCES_TO_ALIGNMENT);
645     menu.exec(QCursor::pos());
646 }
647 
sl_searchInSequences()648 void MSAEditor::sl_searchInSequences() {
649     auto optionsPanel = getOptionsPanel();
650     SAFE_POINT(optionsPanel != nullptr, "Internal error: options panel is NULL"
651                                         " when search in sequences was initiated!", );
652     QVariantMap options = FindPatternMsaWidgetFactory::getOptionsToActivateSearchInSequences();
653     optionsPanel->openGroupById(FindPatternMsaWidgetFactory::getGroupId(), options);
654 }
655 
sl_searchInSequenceNames()656 void MSAEditor::sl_searchInSequenceNames() {
657     auto optionsPanel = getOptionsPanel();
658     SAFE_POINT(optionsPanel != nullptr, "Internal error: options panel is NULL"
659                                         " when search in sequence names was initiated!", );
660     QVariantMap options = FindPatternMsaWidgetFactory::getOptionsToActivateSearchInNames();
661     optionsPanel->openGroupById(FindPatternMsaWidgetFactory::getGroupId(), options);
662 }
663 
sl_realignSomeSequences()664 void MSAEditor::sl_realignSomeSequences() {
665     const MaEditorSelection &selection = getSelection();
666     QList<int> selectedMaRowIndexes = collapseModel->getMaRowIndexesFromSelectionRects(selection.getRectList());
667     QList<qint64> selectedRowIds = maObject->getRowIdsByRowIndexes(selectedMaRowIndexes);
668     auto realignTask = new RealignSequencesInAlignmentTask(getMaObject(), selectedRowIds.toSet());
669     TaskWatchdog::trackResourceExistence(maObject, realignTask, tr("A problem occurred during realigning sequences. The multiple alignment is no more available."));
670     AppContext::getTaskScheduler()->registerTopLevelTask(realignTask);
671 }
672 
sl_setSeqAsReference()673 void MSAEditor::sl_setSeqAsReference() {
674     QPoint menuCallPos = snp.clickPoint;
675     QPoint nameMapped = ui->getEditorNameList()->mapFromGlobal(menuCallPos);
676     if (nameMapped.y() >= 0) {
677         qint64 newRowId = ui->getEditorNameList()->sequenceIdAtPos(nameMapped);
678         if (U2MsaRow::INVALID_ROW_ID != newRowId && newRowId != snp.seqId) {
679             setReference(newRowId);
680         }
681     }
682 }
683 
sl_unsetReferenceSeq()684 void MSAEditor::sl_unsetReferenceSeq() {
685     if (U2MsaRow::INVALID_ROW_ID != getReferenceRowId()) {
686         setReference(U2MsaRow::INVALID_ROW_ID);
687     }
688 }
689 
sl_rowsRemoved(const QList<qint64> & rowIds)690 void MSAEditor::sl_rowsRemoved(const QList<qint64> &rowIds) {
691     foreach (qint64 rowId, rowIds) {
692         if (getReferenceRowId() == rowId) {
693             sl_unsetReferenceSeq();
694             break;
695         }
696     }
697 }
698 
sl_updateRealignAction()699 void MSAEditor::sl_updateRealignAction() {
700     if (maObject->isStateLocked() || maObject->getAlphabet()->isRaw() || ui == nullptr) {
701         realignSomeSequenceAction->setDisabled(true);
702         return;
703     }
704     const MaEditorSelection &selection = getSelection();
705     int selectionWidth = selection.getWidth();
706     int selectedRowsCount = selection.getCountOfSelectedRows();
707     bool isWholeSequenceSelection = selectionWidth == maObject->getLength() && selectedRowsCount >= 1;
708     bool isAllRowsSelection = selectedRowsCount == collapseModel->getViewRowCount();
709     realignSomeSequenceAction->setEnabled(isWholeSequenceSelection && !isAllRowsSelection);
710 }
711 
buildTree()712 void MSAEditor::buildTree() {
713     sl_buildTree();
714 }
715 
getReferenceRowName() const716 QString MSAEditor::getReferenceRowName() const {
717     const MultipleAlignment alignment = getMaObject()->getMultipleAlignment();
718     U2OpStatusImpl os;
719     const int refSeq = alignment->getRowIndexByRowId(getReferenceRowId(), os);
720     return (U2MsaRow::INVALID_ROW_ID != refSeq) ? alignment->getRowNames().at(refSeq) : QString();
721 }
722 
getReferenceCharAt(int pos) const723 char MSAEditor::getReferenceCharAt(int pos) const {
724     CHECK(getReferenceRowId() != U2MsaRow::INVALID_ROW_ID, '\n');
725 
726     U2OpStatusImpl os;
727     const int refSeq = maObject->getMultipleAlignment()->getRowIndexByRowId(getReferenceRowId(), os);
728     SAFE_POINT_OP(os, '\n');
729 
730     return maObject->getMultipleAlignment()->charAt(refSeq, pos);
731 }
732 
sl_showCustomSettings()733 void MSAEditor::sl_showCustomSettings() {
734     AppContext::getAppSettingsGUI()->showSettingsDialog(ColorSchemaSettingsPageId);
735 }
736 
sortSequences(const MultipleAlignment::SortType & sortType,const MultipleAlignment::Order & sortOrder)737 void MSAEditor::sortSequences(const MultipleAlignment::SortType &sortType, const MultipleAlignment::Order &sortOrder) {
738     MultipleSequenceAlignmentObject *msaObject = getMaObject();
739     CHECK(!msaObject->isStateLocked(), );
740 
741     MultipleSequenceAlignment msa = msaObject->getMultipleAlignmentCopy();
742     const MaEditorSelection &selection = getSelection();
743     QRect selectionRect = selection.toRect();
744     U2Region sortRange = selectionRect.height() <= 1 ? U2Region() : U2Region(selectionRect.y(), selectionRect.height());
745     msa->sortRows(sortType, sortOrder, sortRange);
746 
747     // Switch into 'Original' ordering mode.
748     getUI()->getSequenceArea()->sl_toggleSequenceRowOrder(false);
749 
750     QStringList rowNames = msa->getRowNames();
751     if (rowNames != msaObject->getMultipleAlignment()->getRowNames()) {
752         U2OpStatusImpl os;
753         msaObject->updateRowsOrder(os, msa->getRowsIds());
754     }
755 }
756 
sl_sortSequencesByName()757 void MSAEditor::sl_sortSequencesByName() {
758     MultipleAlignment::Order order = sender() == sortByNameDescendingAction ? MultipleAlignment::Descending : MultipleAlignment::Ascending;
759     sortSequences(MultipleAlignment::SortByName, order);
760 }
761 
sl_sortSequencesByLength()762 void MSAEditor::sl_sortSequencesByLength() {
763     MultipleAlignment::Order order = sender() == sortByLengthDescendingAction ? MultipleAlignment::Descending : MultipleAlignment::Ascending;
764     sortSequences(MultipleAlignment::SortByLength, order);
765 }
766 
sl_sortSequencesByLeadingGap()767 void MSAEditor::sl_sortSequencesByLeadingGap() {
768     MultipleAlignment::Order order = sender() == sortByLeadingGapDescendingAction ? MultipleAlignment::Descending : MultipleAlignment::Ascending;
769     sortSequences(MultipleAlignment::SortByLeadingGap, order);
770 }
771 
sl_convertBetweenDnaAndRnaAlphabets()772 void MSAEditor::sl_convertBetweenDnaAndRnaAlphabets() {
773     CHECK(!maObject->isStateLocked(), )
774 
775     auto alphabetId = maObject->getAlphabet()->getId();
776     bool isDnaAlphabet = alphabetId == BaseDNAAlphabetIds::NUCL_DNA_DEFAULT();
777     bool isRnaAlphabet = alphabetId == BaseDNAAlphabetIds::NUCL_RNA_DEFAULT();
778     CHECK(isDnaAlphabet || isRnaAlphabet, );
779 
780     auto msaObject = getMaObject();
781     auto alphabetRegistry = AppContext::getDNAAlphabetRegistry();
782     U2OpStatus2Log os;
783     U2UseCommonUserModStep userModStep(msaObject->getEntityRef(), os);
784     auto resultAlphabet = alphabetRegistry->findById(isDnaAlphabet ? BaseDNAAlphabetIds::NUCL_RNA_DEFAULT() : BaseDNAAlphabetIds::NUCL_DNA_DEFAULT());
785     char fromChar = isDnaAlphabet ? 'T' : 'U';
786     char toChar = isDnaAlphabet ? 'U' : 'T';
787     msaObject->replaceAllCharacters(fromChar, toChar, resultAlphabet);
788 }
789 
sl_convertRawToDnaAlphabet()790 void MSAEditor::sl_convertRawToDnaAlphabet() {
791     CHECK(!maObject->isStateLocked(), )
792 
793     auto alphabetId = maObject->getAlphabet()->getId();
794     CHECK(alphabetId == BaseDNAAlphabetIds::RAW(), );
795 
796     auto msaObject = getMaObject();
797     auto alphabetRegistry = AppContext::getDNAAlphabetRegistry();
798     U2OpStatus2Log os;
799     U2UseCommonUserModStep userModStep(msaObject->getEntityRef(), os);
800     auto resultAlphabet = alphabetRegistry->findById(BaseDNAAlphabetIds::NUCL_DNA_DEFAULT());
801     QByteArray replacementMap(256, '\0');
802     replacementMap['U'] = 'T';
803     msaObject->morphAlphabet(resultAlphabet, replacementMap);
804 }
805 
sl_convertRawToAminoAlphabet()806 void MSAEditor::sl_convertRawToAminoAlphabet() {
807     CHECK(!maObject->isStateLocked(), )
808 
809     auto alphabetId = maObject->getAlphabet()->getId();
810     CHECK(alphabetId == BaseDNAAlphabetIds::RAW(), );
811 
812     auto msaObject = getMaObject();
813     auto alphabetRegistry = AppContext::getDNAAlphabetRegistry();
814     U2OpStatus2Log os;
815     U2UseCommonUserModStep userModStep(msaObject->getEntityRef(), os);
816     auto resultAlphabet = alphabetRegistry->findById(BaseDNAAlphabetIds::AMINO_DEFAULT());
817     msaObject->morphAlphabet(resultAlphabet);
818 }
819 
sl_sortGroupsBySize()820 void MSAEditor::sl_sortGroupsBySize() {
821     groupsSortOrder = sender() == sortGroupsBySizeAscendingAction ? GroupsSortOrder::Ascending : GroupsSortOrder::Descending;
822     updateCollapseModel();
823 }
824 
825 // TODO: move this function into MSA?
826 /* Groups rows by similarity. Two rows are considered equal if their sequences are equal with ignoring of gaps. */
groupRowsBySimilarity(const QList<MultipleAlignmentRow> & msaRows)827 static QList<QList<int>> groupRowsBySimilarity(const QList<MultipleAlignmentRow> &msaRows) {
828     QList<QList<int>> rowGroups;
829     QSet<int> mappedRows;  // contains indexes of the already processed rows.
830     for (int i = 0; i < msaRows.size(); i++) {
831         if (mappedRows.contains(i)) {
832             continue;
833         }
834         const MultipleAlignmentRow &row = msaRows[i];
835         QList<int> rowGroup;
836         rowGroup << i;
837         for (int j = i + 1; j < msaRows.size(); j++) {
838             const MultipleAlignmentRow &next = msaRows[j];
839             if (!mappedRows.contains(j) && MultipleAlignmentRowData::isEqualsIgnoreGaps(next.data(), row.data())) {
840                 rowGroup << j;
841                 mappedRows.insert(j);
842             }
843         }
844         rowGroups << rowGroup;
845     }
846     return rowGroups;
847 }
848 
updateCollapseModel()849 void MSAEditor::updateCollapseModel() {
850     if (rowOrderMode == MaEditorRowOrderMode::Original) {
851         // Synchronize collapsible model with a current alignment.
852         collapseModel->reset(getMaRowIds());
853         return;
854     } else if (rowOrderMode == MaEditorRowOrderMode::Free) {
855         // Check if the modification is compatible with the current view state: all rows have view properties assigned. Reset to the Original order if not.
856         QSet<qint64> maRowIds = getMaRowIds().toSet();
857         QSet<qint64> viewModelRowIds = collapseModel->getAllRowIds();
858         if (viewModelRowIds != maRowIds) {
859             rowOrderMode = MaEditorRowOrderMode::Original;
860             collapseModel->reset(getMaRowIds());
861         }
862         return;
863     }
864 
865     SAFE_POINT(rowOrderMode == MaEditorRowOrderMode::Sequence, "Unexpected row order mode", );
866 
867     // Order and group rows by sequence content.
868     MultipleSequenceAlignmentObject *msaObject = getMaObject();
869     QList<QList<int>> rowGroups = groupRowsBySimilarity(msaObject->getRows());
870     QVector<MaCollapsibleGroup> newCollapseGroups;
871 
872     QSet<qint64> maRowIdsOfNonCollapsedRowsBefore;
873     for (int i = 0; i < collapseModel->getGroupCount(); i++) {
874         const MaCollapsibleGroup *group = collapseModel->getCollapsibleGroup(i);
875         if (!group->isCollapsed) {
876             maRowIdsOfNonCollapsedRowsBefore += group->maRowIds.toSet();
877         }
878     }
879     for (int i = 0; i < rowGroups.size(); i++) {
880         const QList<int> &maRowsInGroup = rowGroups[i];
881         QList<qint64> maRowIdsInGroup = msaObject->getMultipleAlignment()->getRowIdsByRowIndexes(maRowsInGroup);
882         bool isCollapsed = !maRowIdsOfNonCollapsedRowsBefore.contains(maRowIdsInGroup[0]);
883         newCollapseGroups << MaCollapsibleGroup(maRowsInGroup, maRowIdsInGroup, isCollapsed);
884     }
885     if (groupsSortOrder != GroupsSortOrder::Original) {
886         std::stable_sort(newCollapseGroups.begin(), newCollapseGroups.end(), [&](const MaCollapsibleGroup &g1, const MaCollapsibleGroup &g2) {
887             int size1 = g1.maRowIds.size();
888             int size2 = g2.maRowIds.size();
889             return groupsSortOrder == GroupsSortOrder::Ascending ? size1 < size2 : size2 < size1;
890         });
891     }
892     collapseModel->update(newCollapseGroups);
893 }
894 
setRowOrderMode(MaEditorRowOrderMode mode)895 void MSAEditor::setRowOrderMode(MaEditorRowOrderMode mode) {
896     if (mode == rowOrderMode) {
897         return;
898     }
899     MaEditor::setRowOrderMode(mode);
900     freeModeMasterMarkersSet.clear();
901     updateCollapseModel();
902     updateActions();
903 }
904 
getFreeModeMasterMarkersSet() const905 const QSet<QObject *> &MSAEditor::getFreeModeMasterMarkersSet() const {
906     return freeModeMasterMarkersSet;
907 }
908 
addFreeModeMasterMarker(QObject * marker)909 void MSAEditor::addFreeModeMasterMarker(QObject *marker) {
910     freeModeMasterMarkersSet.insert(marker);
911 }
912 
removeFreeModeMasterMarker(QObject * marker)913 void MSAEditor::removeFreeModeMasterMarker(QObject *marker) {
914     freeModeMasterMarkersSet.remove(marker);
915 }
916 
getSelectionController() const917 MaEditorSelectionController *MSAEditor::getSelectionController() const {
918     return selectionController;
919 }
920 
sl_exportImage()921 void MSAEditor::sl_exportImage() {
922     MSAImageExportController controller(ui);
923     QWidget *parentWidget = (QWidget *)AppContext::getMainWindow()->getQMainWindow();
924     QString fileName = GUrlUtils::fixFileName(maObject->getGObjectName());
925     QObjectScopedPointer<ExportImageDialog> dlg = new ExportImageDialog(&controller,
926                                                                         ExportImageDialog::MSA,
927                                                                         fileName,
928                                                                         ExportImageDialog::NoScaling,
929                                                                         parentWidget);
930     dlg->exec();
931 }
932 
933 }  // namespace U2
934