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 "MSAEditorTreeManager.h"
23 
24 #include <QApplication>
25 #include <QMessageBox>
26 
27 #include <U2Algorithm/MSADistanceAlgorithm.h>
28 #include <U2Algorithm/MSADistanceAlgorithmRegistry.h>
29 #include <U2Algorithm/PhyTreeGeneratorRegistry.h>
30 #include <U2Algorithm/PhyTreeGeneratorTask.h>
31 
32 #include <U2Core/AppContext.h>
33 #include <U2Core/BaseDocumentFormats.h>
34 #include <U2Core/DocumentModel.h>
35 #include <U2Core/DocumentUtils.h>
36 #include <U2Core/GObjectRelationRoles.h>
37 #include <U2Core/GUrlUtils.h>
38 #include <U2Core/IOAdapterUtils.h>
39 #include <U2Core/LoadDocumentTask.h>
40 #include <U2Core/PhyTreeObject.h>
41 #include <U2Core/ProjectModel.h>
42 #include <U2Core/QObjectScopedPointer.h>
43 #include <U2Core/SaveDocumentTask.h>
44 #include <U2Core/TaskSignalMapper.h>
45 #include <U2Core/U2OpStatusUtils.h>
46 #include <U2Core/U2SafePoints.h>
47 
48 #include <U2Gui/DialogUtils.h>
49 #include <U2Gui/LastUsedDirHelper.h>
50 #include <U2Gui/U2FileDialog.h>
51 
52 #include <U2View/MSAEditor.h>
53 #include <U2View/MSAEditorSequenceArea.h>
54 #include <U2View/MaEditorNameList.h>
55 
56 #include "MsaEditorTreeTabArea.h"
57 #include "ov_msa/phy_tree/MSAEditorMultiTreeViewer.h"
58 #include "ov_msa/phy_tree_options/TreeOptionsWidgetFactory.h"
59 #include "ov_phyltree/GraphicsRectangularBranchItem.h"
60 #include "ov_phyltree/TreeViewer.h"
61 #include "ov_phyltree/TreeViewerTasks.h"
62 #include "phyltree/CreatePhyTreeDialogController.h"
63 
64 namespace U2 {
MSAEditorTreeManager(MSAEditor * msaEditor)65 MSAEditorTreeManager::MSAEditorTreeManager(MSAEditor *msaEditor)
66     : QObject(msaEditor), editor(msaEditor), msaObject(nullptr), addExistingTree(false) {
67     SAFE_POINT(editor != nullptr, "Invalid parameter were passed into constructor MSAEditorTreeManager", );
68 
69     Project *project = AppContext::getProject();
70     SAFE_POINT(project != nullptr, "Invalid project detected", );
71 
72     connect(project, SIGNAL(si_documentRemoved(Document *)), SLOT(sl_onDocumentRemovedFromProject(Document *)));
73 }
74 
sl_onDocumentRemovedFromProject(Document * doc)75 void MSAEditorTreeManager::sl_onDocumentRemovedFromProject(Document *doc) {
76     CHECK(msaObject != nullptr, );
77     if (doc == msaObject->getDocument()) {
78         msaObject = nullptr;
79         return;
80     }
81     QList<GObjectRelation> treeRelationList = msaObject->findRelatedObjectsByRole(ObjectRole_PhylogeneticTree);
82     CHECK(!treeRelationList.isEmpty(), );
83 
84     for (const GObjectRelation &treeRelation : qAsConst(treeRelationList)) {
85         if (treeRelation.ref.entityRef.isValid() && doc->getObjectById(treeRelation.ref.entityRef.entityId) != nullptr) {
86             msaObject->removeObjectRelation(treeRelation);
87         }
88     }
89 }
90 
loadRelatedTrees()91 void MSAEditorTreeManager::loadRelatedTrees() {
92     msaObject = editor->getMaObject();
93     QList<GObjectRelation> treeRelationList = msaObject->findRelatedObjectsByRole(ObjectRole_PhylogeneticTree);
94     CHECK(!treeRelationList.isEmpty(), );
95 
96     for (const GObjectRelation &treeRelation : qAsConst(treeRelationList)) {
97         const QString &treeFileName = treeRelation.getDocURL();
98         Document *doc = AppContext::getProject()->findDocumentByURL(treeFileName);
99         if (doc != nullptr) {
100             loadTreeFromFile(treeFileName);
101         }
102     }
103 }
104 
buildTreeWithDialog()105 void MSAEditorTreeManager::buildTreeWithDialog() {
106     msaObject = editor->getMaObject();
107     QStringList phyTreeGeneratorNameList = AppContext::getPhyTreeGeneratorRegistry()->getNameList();
108     addExistingTree = false;
109     if (phyTreeGeneratorNameList.isEmpty()) {
110         QMessageBox::information(editor->getUI(), tr("Calculate phy tree"), tr("No algorithms for building phylogenetic tree are available."));
111         return;
112     }
113 
114     QObjectScopedPointer<CreatePhyTreeDialogController> dlg = new CreatePhyTreeDialogController(editor->getUI(), msaObject, settings);
115     const int rc = dlg->exec();
116     CHECK(!dlg.isNull(), );
117     CHECK(rc == QDialog::Accepted, );
118 
119     settings.rowsOrder = msaObject->getMultipleAlignment()->getRowNames();
120     buildTree(settings);
121 }
122 
buildTree(const CreatePhyTreeSettings & buildSettings)123 void MSAEditorTreeManager::buildTree(const CreatePhyTreeSettings &buildSettings) {
124     createPhyTreeGeneratorTask(buildSettings);
125 }
126 
sl_refreshTree(MSAEditorTreeViewer * treeViewer)127 void MSAEditorTreeManager::sl_refreshTree(MSAEditorTreeViewer *treeViewer) {
128     CHECK(canRefreshTree(treeViewer), );
129 
130     createPhyTreeGeneratorTask(treeViewer->getCreatePhyTreeSettings(), true, treeViewer);
131 }
132 
createPhyTreeGeneratorTask(const CreatePhyTreeSettings & buildSettings,bool refreshExistingTree,MSAEditorTreeViewer * treeViewer)133 void MSAEditorTreeManager::createPhyTreeGeneratorTask(const CreatePhyTreeSettings &buildSettings, bool refreshExistingTree, MSAEditorTreeViewer *treeViewer) {
134     const MultipleSequenceAlignment msa = msaObject->getMultipleAlignment();
135     settings = buildSettings;
136 
137     auto treeGeneratorTask = new PhyTreeGeneratorLauncherTask(msa, settings);
138     if (refreshExistingTree) {
139         activeRefreshTasks[treeViewer] = treeGeneratorTask;
140         connect(new TaskSignalMapper(treeGeneratorTask), SIGNAL(si_taskSucceeded(Task *)), SLOT(sl_treeRebuildingFinished(Task *)));
141         connect(treeViewer, SIGNAL(destroyed()), treeGeneratorTask, SLOT(sl_onCalculationCanceled()));
142     } else {
143         connect(new TaskSignalMapper(treeGeneratorTask), SIGNAL(si_taskSucceeded(Task *)), SLOT(sl_openTree(Task *)));
144     }
145     AppContext::getTaskScheduler()->registerTopLevelTask(treeGeneratorTask);
146 }
147 
sl_treeRebuildingFinished(Task * _treeBuildTask)148 void MSAEditorTreeManager::sl_treeRebuildingFinished(Task *_treeBuildTask) {
149     auto treeBuildTask = qobject_cast<PhyTreeGeneratorLauncherTask *>(_treeBuildTask);
150     if (treeBuildTask == nullptr || treeBuildTask->isCanceled()) {
151         return;
152     }
153 
154     MSAEditorTreeViewer *refreshingTree = activeRefreshTasks.key(treeBuildTask);
155     CHECK(refreshingTree != nullptr, );
156     activeRefreshTasks.remove(refreshingTree);
157 
158     PhyTreeObject *treeObj = refreshingTree->getPhyObject();
159     treeObj->setTree(treeBuildTask->getResult());
160 }
161 
canRefreshTree(MSAEditorTreeViewer * treeViewer)162 bool MSAEditorTreeManager::canRefreshTree(MSAEditorTreeViewer *treeViewer) {
163     bool canRefresh = (treeViewer->getParentAlignmentName() == msaObject->getMultipleAlignment()->getName());
164     return canRefresh && !activeRefreshTasks.contains(treeViewer);
165 }
166 
sl_openTree(Task * treeBuildTask)167 void MSAEditorTreeManager::sl_openTree(Task *treeBuildTask) {
168     CHECK(treeBuildTask != nullptr && !treeBuildTask->isCanceled(), )
169 
170     PhyTreeGeneratorLauncherTask *treeGeneratorTask = qobject_cast<PhyTreeGeneratorLauncherTask *>(treeBuildTask);
171     CHECK(treeGeneratorTask != nullptr, );
172 
173     const GUrl &msaURL = msaObject->getDocument()->getURL();
174     SAFE_POINT(!msaURL.isEmpty(), QString("Tree URL in MSAEditorTreeManager::sl_openTree() is empty"), );
175 
176     Project *p = AppContext::getProject();
177     treeDocument = nullptr;
178     PhyTreeObject *newTreeObj = nullptr;
179     QString treeFileName = settings.fileUrl.getURLString();
180     if (treeFileName.isEmpty()) {
181         treeFileName = GUrlUtils::rollFileName(msaURL.dirPath() + "/" + msaURL.baseFileName() + ".nwk", DocumentUtils::getNewDocFileNameExcludesHint());
182     }
183 
184     DocumentFormat *df = AppContext::getDocumentFormatRegistry()->getFormatById(BaseDocumentFormats::NEWICK);
185     IOAdapterFactory *iof = IOAdapterUtils::get(BaseIOAdapters::LOCAL_FILE);
186 
187     const QList<Document *> documents = AppContext::getProject()->getDocuments();
188     bool isNewDocument = true;
189     for (Document *doc : qAsConst(documents)) {
190         if (treeFileName == doc->getURLString()) {
191             treeDocument = doc;
192             isNewDocument = false;
193             break;
194         }
195     }
196 
197     if (treeDocument == nullptr) {
198         U2OpStatus2Log os;
199         treeDocument = df->createNewLoadedDocument(iof, treeFileName, os);
200         CHECK_OP(os, );
201     }
202 
203     if (isNewDocument) {
204         U2OpStatus2Log os;
205         newTreeObj = PhyTreeObject::createInstance(treeGeneratorTask->getResult(), "Tree", treeDocument->getDbiRef(), os);
206         CHECK_OP(os, );
207         treeDocument->addObject(newTreeObj);
208     } else if (!treeDocument->isLoaded()) {
209         phyTree = treeGeneratorTask->getResult();
210         LoadUnloadedDocumentTask *t = new LoadUnloadedDocumentTask(treeDocument);
211         connect(new TaskSignalMapper(t), SIGNAL(si_taskSucceeded(Task *)), SLOT(sl_onPhyTreeDocLoaded(Task *)));
212         AppContext::getTaskScheduler()->registerTopLevelTask(t);
213         return;
214     } else {
215         const QList<GObject *> &objects = treeDocument->getObjects();
216         for (GObject *obj : qAsConst(objects)) {
217             PhyTreeObject *treeObj = qobject_cast<PhyTreeObject *>(obj);
218             if (treeObj) {
219                 treeObj->setTree(treeGeneratorTask->getResult());
220                 newTreeObj = treeObj;
221             }
222         }
223     }
224 
225     if (!p->getDocuments().contains(treeDocument)) {
226         p->addDocument(treeDocument);
227     }
228 
229     if (isNewDocument) {
230         msaObject->addObjectRelation(GObjectRelation(GObjectReference(newTreeObj), ObjectRole_PhylogeneticTree));
231     }
232 
233     AppContext::getTaskScheduler()->registerTopLevelTask(new SaveDocumentTask(treeDocument));
234 
235     openTreeViewer(newTreeObj);
236 }
237 
sl_onPhyTreeDocLoaded(Task * task)238 void MSAEditorTreeManager::sl_onPhyTreeDocLoaded(Task *task) {
239     auto loadTask = qobject_cast<LoadUnloadedDocumentTask *>(task);
240     treeDocument = loadTask->getDocument();
241     PhyTreeObject *treeObj = nullptr;
242     for (GObject *obj : qAsConst(treeDocument->getObjects())) {
243         treeObj = qobject_cast<PhyTreeObject *>(obj);
244         if (treeObj != nullptr) {
245             treeObj->setTree(phyTree);
246             break;
247         }
248     }
249     openTreeViewer(treeObj);
250 }
251 
openTreeViewer(PhyTreeObject * treeObj)252 void MSAEditorTreeManager::openTreeViewer(PhyTreeObject *treeObj) {
253     Task *openTask = settings.displayWithAlignmentEditor
254                          ? new MSAEditorOpenTreeViewerTask(treeObj, this)
255                          : new OpenTreeViewerTask(treeObj, this);
256     AppContext::getTaskScheduler()->registerTopLevelTask(openTask);
257 }
258 
sl_openTreeTaskFinished(Task * task)259 void MSAEditorTreeManager::sl_openTreeTaskFinished(Task *task) {
260     auto createTreeViewerTask = qobject_cast<CreateMSAEditorTreeViewerTask *>(task);
261     CHECK(createTreeViewerTask != nullptr, );
262 
263     if (!settings.displayWithAlignmentEditor) {
264         GObjectViewWindow *w = new GObjectViewWindow(createTreeViewerTask->getTreeViewer(), editor->getName(), !createTreeViewerTask->getStateData().isEmpty());
265         MWMDIManager *mdiManager = AppContext::getMainWindow()->getMDIManager();
266         mdiManager->addMDIWindow(w);
267         return;
268     }
269 
270     auto treeViewer = qobject_cast<MSAEditorTreeViewer *>(createTreeViewerTask->getTreeViewer());
271     SAFE_POINT(treeViewer != nullptr, tr("Can not convert TreeViewer* to MSAEditorTreeViewer* in function MSAEditorTreeManager::sl_openTreeTaskFinished(Task* t)"), );
272 
273     // TODO: pass MSA editor to the constructor of MSAEditorTreeViewer and avoid extra state when MSAEditorTreeViewer has no msaEditor assigned.
274     treeViewer->setMSAEditor(editor);
275 
276     auto viewWindow = new GObjectViewWindow(treeViewer, editor->getName(), !createTreeViewerTask->getStateData().isEmpty());
277     connect(viewWindow, SIGNAL(si_windowClosed(GObjectViewWindow *)), this, SLOT(sl_onWindowClosed(GObjectViewWindow *)));
278 
279     MsaEditorWgt *msaUI = editor->getUI();
280     msaUI->addTreeView(viewWindow);
281 
282     if (!addExistingTree) {
283         treeViewer->setCreatePhyTreeSettings(settings);
284         treeViewer->setParentAignmentName(msaObject->getMultipleAlignment()->getName());
285     }
286 
287     if (settings.syncAlignmentWithTree) {
288         treeViewer->enableSyncMode();
289     }
290 
291     connect(treeViewer, SIGNAL(si_refreshTree(MSAEditorTreeViewer *)), SLOT(sl_refreshTree(MSAEditorTreeViewer *)));
292 }
293 
openTreeFromFile()294 void MSAEditorTreeManager::openTreeFromFile() {
295     LastUsedDirHelper h;
296     QString filter = DialogUtils::prepareDocumentsFileFilter(BaseDocumentFormats::NEWICK, false, QStringList());
297     QString file;
298 #ifdef Q_OS_DARWIN
299     if (qgetenv(ENV_GUI_TEST).toInt() == 1 && qgetenv(ENV_USE_NATIVE_DIALOGS).toInt() == 0) {
300         file = U2FileDialog::getOpenFileName(QApplication::activeWindow(), tr("Select files to open..."), h.dir, filter, 0, QFileDialog::DontUseNativeDialog);
301     } else
302 #endif
303         file = U2FileDialog::getOpenFileName(QApplication::activeWindow(), tr("Select files to open..."), h.dir, filter);
304     CHECK(!file.isEmpty(), );
305     if (QFileInfo(file).exists()) {
306         h.url = file;
307         loadTreeFromFile(file);
308     }
309 }
310 
loadTreeFromFile(const QString & treeFileName)311 void MSAEditorTreeManager::loadTreeFromFile(const QString &treeFileName) {
312     addExistingTree = true;
313     bool isNewDocument = true;
314     Document *doc = nullptr;
315     const QList<Document *> documents = AppContext::getProject()->getDocuments();
316     foreach (doc, documents) {
317         if (doc->getURLString() == treeFileName) {
318             isNewDocument = false;
319             break;
320         }
321     }
322 
323     if (isNewDocument || !doc->isLoaded()) {
324         if (!isNewDocument && !doc->isLoaded()) {
325             if (AppContext::getProject()->getDocuments().contains(doc)) {
326                 AppContext::getProject()->removeDocument(doc);
327             }
328         }
329         U2OpStatus2Log os;
330         IOAdapterFactory *ioFactory = IOAdapterUtils::get(BaseIOAdapters::LOCAL_FILE);
331         DocumentFormat *documentFormat = AppContext::getDocumentFormatRegistry()->getFormatById(BaseDocumentFormats::NEWICK);
332         doc = documentFormat->loadDocument(ioFactory, treeFileName, QVariantMap(), os);
333         CHECK(doc != nullptr, );
334         AppContext::getProject()->addDocument(doc);
335     }
336 
337     const QList<GObject *> treeObjectList = doc->findGObjectByType(GObjectTypes::PHYLOGENETIC_TREE);
338     for (GObject *obj : qAsConst(treeObjectList)) {
339         auto treeObject = qobject_cast<PhyTreeObject *>(obj);
340         msaObject->addObjectRelation(GObjectRelation(GObjectReference(treeObject), ObjectRole_PhylogeneticTree));
341         if (treeObject == nullptr) {
342             continue;
343         }
344         const MSAEditorMultiTreeViewer *multiTreeViewer = getMultiTreeViewer();
345         if (multiTreeViewer == nullptr || !multiTreeViewer->getTreeNames().contains(doc->getName())) {
346             AppContext::getTaskScheduler()->registerTopLevelTask(new MSAEditorOpenTreeViewerTask(treeObject, this));
347         }
348     }
349 }
350 
sl_onWindowClosed(GObjectViewWindow * viewWindow)351 void MSAEditorTreeManager::sl_onWindowClosed(GObjectViewWindow *viewWindow) {
352     MSAEditorMultiTreeViewer *multiTreeViewer = getMultiTreeViewer();
353     CHECK(multiTreeViewer != nullptr, );
354     multiTreeViewer->sl_onTabCloseRequested(viewWindow);
355 }
356 
getMultiTreeViewer() const357 MSAEditorMultiTreeViewer *MSAEditorTreeManager::getMultiTreeViewer() const {
358     SAFE_POINT(editor != nullptr, tr("Incorrect reference to the MSAEditor"), nullptr);
359     MsaEditorWgt *msaEditorUi = editor->getUI();
360     SAFE_POINT(msaEditorUi != nullptr, tr("Incorrect reference to the MSAEditor"), nullptr);
361     return msaEditorUi->getMultiTreeViewer();
362 }
363 
364 }  // namespace U2
365