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