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 "MoveToObjectMaController.h"
23
24 #include <QMessageBox>
25
26 #include <U2Core/AddSequencesToAlignmentTask.h>
27 #include <U2Core/AppContext.h>
28 #include <U2Core/Counter.h>
29 #include <U2Core/DocumentModel.h>
30 #include <U2Core/GObject.h>
31 #include <U2Core/GObjectTypes.h>
32 #include <U2Core/GObjectUtils.h>
33 #include <U2Core/L10n.h>
34 #include <U2Core/MultiTask.h>
35 #include <U2Core/MultipleSequenceAlignmentObject.h>
36 #include <U2Core/U2Mod.h>
37 #include <U2Core/U2OpStatusUtils.h>
38
39 #include <U2Formats/ExportTasks.h>
40
41 #include <U2Gui/DialogUtils.h>
42 #include <U2Gui/GUIUtils.h>
43 #include <U2Gui/LastUsedDirHelper.h>
44 #include <U2Gui/OpenViewTask.h>
45
46 #include <U2View/MSAEditor.h>
47 #include <U2View/MaCollapseModel.h>
48 #include <U2View/MaEditor.h>
49 #include <U2View/MaEditorSelection.h>
50 #include <U2View/MaEditorWgt.h>
51
52 namespace U2 {
53
MoveToObjectMaController(MaEditor * maEditor)54 MoveToObjectMaController::MoveToObjectMaController(MaEditor *maEditor)
55 : QObject(maEditor), MaEditorContext(maEditor) {
56 moveSelectionToAnotherObjectAction = new QAction(tr("Move selected rows to another alignment"));
57 moveSelectionToAnotherObjectAction->setObjectName("move_selection_to_another_object");
58 connect(moveSelectionToAnotherObjectAction, &QAction::triggered, this, &MoveToObjectMaController::showMoveSelectedRowsToAnotherObjectMenu);
59
60 moveSelectionToNewFileAction = new QAction(tr("Create a new alignment"));
61 moveSelectionToNewFileAction->setObjectName("move_selection_to_new_file");
62 connect(moveSelectionToNewFileAction, &QAction::triggered, this, &MoveToObjectMaController::runMoveSelectedRowsToNewFileDialog);
63
64 connect(editor, &MaEditor::si_updateActions, this, &MoveToObjectMaController::updateActions);
65 connect(editor, &MaEditor::si_buildMenu, this, &MoveToObjectMaController::buildMenu);
66 }
67
buildMoveSelectionToAnotherObjectMenu() const68 QMenu *MoveToObjectMaController::buildMoveSelectionToAnotherObjectMenu() const {
69 QMenu *menu = new QMenu(moveSelectionToAnotherObjectAction->text());
70 menu->setEnabled(moveSelectionToAnotherObjectAction->isEnabled());
71 CHECK(menu->isEnabled(), menu);
72
73 menu->addAction(moveSelectionToNewFileAction);
74
75 QList<GObject *> writableMsaObjects = GObjectUtils::findAllObjects(UOF_LoadedOnly, GObjectTypes::MULTIPLE_SEQUENCE_ALIGNMENT, true);
76 writableMsaObjects.removeOne(maObject);
77 std::stable_sort(writableMsaObjects.begin(), writableMsaObjects.end(), [&](const GObject *o1, const GObject *o2) {
78 return o1->getGObjectName().compare(o2->getGObjectName(), Qt::CaseInsensitive);
79 });
80 menu->addSeparator();
81 if (writableMsaObjects.isEmpty()) {
82 QAction *noObjectsAction = menu->addAction(tr("No other alignment objects in the project"), []() {});
83 noObjectsAction->setObjectName("no_other_objects_item");
84 noObjectsAction->setEnabled(false);
85 }
86 QIcon objectMenuIcon(":core/images/msa.png");
87 for (const GObject *object : qAsConst(writableMsaObjects)) {
88 GObjectReference reference(object);
89 QString fileName = object->getDocument()->getURL().fileName();
90 QString menuItemText = object->getGObjectName() + " [" + fileName + "] ";
91 QAction *action = menu->addAction(objectMenuIcon, menuItemText, [this, reference]() {
92 GCounter::increment("MoveSelectedMsaRowsToNewObject");
93 GObject *referenceObject = GObjectUtils::selectObjectByReference(reference, UOF_LoadedOnly);
94 CHECK_EXT(referenceObject != nullptr, QMessageBox::critical(ui, L10N::errorTitle(), L10N::errorObjectNotFound(reference.objName)), );
95 CHECK_EXT(!referenceObject->isStateLocked(), QMessageBox::critical(ui, L10N::errorTitle(), L10N::errorObjectIsReadOnly(reference.objName)), );
96
97 auto targetMsaObject = qobject_cast<MultipleSequenceAlignmentObject *>(referenceObject);
98 CHECK_EXT(targetMsaObject != nullptr, QMessageBox::critical(ui, L10N::errorTitle(), L10N::nullPointerError(reference.objName)), );
99
100 QList<int> selectedViewRowIndexes = getSelection().getSelectedRowIndexes();
101 QList<int> selectedMaRowIndexes = collapseModel->getMaRowIndexesByViewRowIndexes(selectedViewRowIndexes, true);
102 QList<qint64> rowIdsToRemove = maObject->getRowIdsByRowIndexes(selectedMaRowIndexes);
103 CHECK_EXT(!rowIdsToRemove.isEmpty(), QMessageBox::critical(ui, L10N::errorTitle(), L10N::internalError()), );
104 QList<DNASequence> sequencesWithGapsToMove;
105 for (int maRowIndex : qAsConst(selectedMaRowIndexes)) {
106 MultipleAlignmentRow row = maObject->getRow(maRowIndex);
107 QByteArray sequenceWithGaps = row->getSequenceWithGaps(true, false);
108 sequencesWithGapsToMove << DNASequence(row->getName(), sequenceWithGaps, maObject->getAlphabet());
109 }
110 auto addRowsTask = new AddSequenceObjectsToAlignmentTask(targetMsaObject, sequencesWithGapsToMove, -1, true);
111 auto removeRowsTask = new RemoveRowsFromMaObjectTask(editor, rowIdsToRemove);
112 AppContext::getTaskScheduler()->registerTopLevelTask(new MultiTask(tr("Move rows to another alignment"), {addRowsTask, removeRowsTask}));
113 });
114 action->setObjectName(fileName); // For UI testing.
115 }
116 return menu;
117 }
118
showMoveSelectedRowsToAnotherObjectMenu()119 void MoveToObjectMaController::showMoveSelectedRowsToAnotherObjectMenu() {
120 QScopedPointer<QMenu> menu(buildMoveSelectionToAnotherObjectMenu());
121 menu->exec(QCursor::pos());
122 }
123
updateActions()124 void MoveToObjectMaController::updateActions() {
125 int countOfSelectedRows = getSelection().getCountOfSelectedRows();
126 bool isMoveOk = !maObject->isStateLocked() && countOfSelectedRows > 0 && countOfSelectedRows < maObject->getNumRows();
127 moveSelectionToAnotherObjectAction->setEnabled(isMoveOk);
128 moveSelectionToNewFileAction->setEnabled(isMoveOk);
129 }
130
buildMenu(GObjectView *,QMenu * menu,const QString & menuType)131 void MoveToObjectMaController::buildMenu(GObjectView *, QMenu *menu, const QString &menuType) {
132 CHECK(menuType == MsaEditorMenuType::CONTEXT, );
133 QMenu *exportMenu = GUIUtils::findSubMenu(menu, MSAE_MENU_EXPORT);
134 SAFE_POINT(exportMenu != nullptr, "exportMenu is null", );
135 QAction *menuAction = exportMenu->addMenu(buildMoveSelectionToAnotherObjectMenu());
136 menuAction->setObjectName(moveSelectionToAnotherObjectAction->objectName());
137 }
138
runMoveSelectedRowsToNewFileDialog()139 void MoveToObjectMaController::runMoveSelectedRowsToNewFileDialog() {
140 GCounter::increment("MoveSelectedMsaRowsToNewFile");
141
142 // Get the file name to move rows to first.
143 LastUsedDirHelper lod;
144 DocumentFormatConstraints formatConstraints;
145 formatConstraints.supportedObjectTypes << GObjectTypes::MULTIPLE_SEQUENCE_ALIGNMENT;
146 QString filter = DialogUtils::prepareDocumentsFileFilter(formatConstraints, false);
147 QString selectedFilter = DialogUtils::prepareDocumentsFileFilter(BaseDocumentFormats::CLUSTAL_ALN, false);
148 lod.url = U2FileDialog::getSaveFileName(ui, tr("Select a new file to move selected rows"), lod, filter, &selectedFilter);
149 CHECK(!lod.url.isEmpty(), );
150
151 QString url = lod.url;
152 QFileInfo urlInfo(url);
153 QString fileExtension = urlInfo.suffix();
154 DocumentFormatRegistry *formatRegistry = AppContext::getDocumentFormatRegistry();
155 DocumentFormat *format = formatRegistry->selectFormatByFileExtension(fileExtension);
156 if (format == nullptr) {
157 format = formatRegistry->getFormatById(BaseDocumentFormats::CLUSTAL_ALN);
158 }
159 QStringList extensions = format->getSupportedDocumentFileExtensions();
160 if (!extensions.isEmpty() && !extensions.contains(fileExtension)) {
161 url += "." + extensions.first();
162 }
163
164 // Create a sub-alignment from moved rows.
165 QList<int> selectedViewRowIndexes = getSelection().getSelectedRowIndexes();
166 QList<int> selectedMaRowIndexes = collapseModel->getMaRowIndexesByViewRowIndexes(selectedViewRowIndexes, true);
167 QList<qint64> rowIdsToRemove = maObject->getRowIdsByRowIndexes(selectedMaRowIndexes);
168 SAFE_POINT(!rowIdsToRemove.isEmpty(), "rowIdsToRemove is empty", );
169
170 MultipleSequenceAlignment msaToExport;
171 msaToExport->setName(urlInfo.baseName());
172 msaToExport->setAlphabet(maObject->getAlphabet());
173 for (int maRowIndex : qAsConst(selectedMaRowIndexes)) {
174 const MultipleAlignmentRow &row = maObject->getRow(maRowIndex);
175 msaToExport->addRow(row->getName(), row->getSequenceWithGaps(true, true));
176 }
177
178 // Run 2 tasks: first create a new document, next remove moved rows from the original document.
179 auto createNewMsaTask = new AddDocumentAndOpenViewTask(new ExportAlignmentTask(msaToExport, url, format->getFormatId()));
180 auto removeRowsTask = new RemoveRowsFromMaObjectTask(editor, rowIdsToRemove);
181 auto task = new MultiTask(tr("Export alignment rows to a new file"), {createNewMsaTask, removeRowsTask});
182 AppContext::getTaskScheduler()->registerTopLevelTask(task);
183 }
184
185 /************************************************************************/
186 /* RemoveRowsFromMsaObjectTask */
187 /************************************************************************/
RemoveRowsFromMaObjectTask(MaEditor * _maEditor,const QList<qint64> & _rowIds)188 RemoveRowsFromMaObjectTask::RemoveRowsFromMaObjectTask(MaEditor *_maEditor, const QList<qint64> &_rowIds)
189 : Task(tr("Remove rows from alignment"), TaskFlag_RunInMainThread), maEditor(_maEditor), rowIds(_rowIds) {
190 }
191
run()192 void RemoveRowsFromMaObjectTask::run() {
193 CHECK(!maEditor.isNull(), ); // The editor may be closed while the task in the queue.
194
195 MultipleAlignmentObject *maObject = maEditor->getMaObject();
196 CHECK_EXT(rowIds.size() < maObject->getNumRows(), setError(tr("Can't remove all rows from the alignment")), );
197 U2UseCommonUserModStep userModStep(maObject->getEntityRef(), stateInfo);
198 CHECK_OP(stateInfo, );
199
200 maObject->removeRowsById(rowIds);
201 // If not cleared explicitly another row is auto-selected and the result may be misinterpret like not all rows were moved.
202 maEditor->getSelectionController()->clearSelection();
203 }
204
205 } // namespace U2
206