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 "ExternalToolSupportSettingsController.h"
23 
24 #include <QMessageBox>
25 #include <QToolButton>
26 
27 #include <U2Core/AppContext.h>
28 #include <U2Core/AppSettings.h>
29 #include <U2Core/CustomExternalTool.h>
30 #include <U2Core/L10n.h>
31 #include <U2Core/Settings.h>
32 #include <U2Core/Theme.h>
33 #include <U2Core/U2SafePoints.h>
34 #include <U2Core/UserApplicationsSettings.h>
35 
36 #include <U2Gui/DialogUtils.h>
37 #include <U2Gui/GUIUtils.h>
38 #include <U2Gui/LastUsedDirHelper.h>
39 #include <U2Gui/ShowHideSubgroupWidget.h>
40 
41 #include "ExternalToolSupportSettings.h"
42 #include "custom_tools/ImportCustomToolsTask.h"
43 #include "custom_tools/ImportExternalToolDialog.h"
44 
45 namespace U2 {
46 
47 static const int TOOLKIT_TYPE = QTreeWidgetItem::UserType + 1;
48 
49 /////////////////////////////////////////////
50 ////ExternalToolSupportSettingsPageController
ExternalToolSupportSettingsPageController(QObject * p)51 ExternalToolSupportSettingsPageController::ExternalToolSupportSettingsPageController(QObject *p)
52     : AppSettingsGUIPageController(tr("External Tools"), ExternalToolSupportSettingsPageId, p) {
53 }
54 
getSavedState()55 AppSettingsGUIPageState *ExternalToolSupportSettingsPageController::getSavedState() {
56     return new ExternalToolSupportSettingsPageState(AppContext::getExternalToolRegistry()->getAllEntries());
57 }
58 
saveState(AppSettingsGUIPageState *)59 void ExternalToolSupportSettingsPageController::saveState(AppSettingsGUIPageState * /*state*/) {
60     ExternalToolSupportSettings::saveExternalToolsToAppConfig();
61 }
62 
createWidget(AppSettingsGUIPageState * state)63 AppSettingsGUIPageWidget *ExternalToolSupportSettingsPageController::createWidget(AppSettingsGUIPageState *state) {
64     ExternalToolSupportSettingsPageWidget *r = new ExternalToolSupportSettingsPageWidget(this);
65     r->setState(state);
66     return r;
67 }
68 
getHelpPageId() const69 const QString &ExternalToolSupportSettingsPageController::getHelpPageId() const {
70     return helpPageId;
71 }
72 
73 const QString ExternalToolSupportSettingsPageController::helpPageId = QString("65929361");
74 
75 //////////////////////////////////////////////
76 ////ExternalToolSupportSettingsPageState
77 
ExternalToolSupportSettingsPageState(const QList<ExternalTool * > & ets)78 ExternalToolSupportSettingsPageState::ExternalToolSupportSettingsPageState(const QList<ExternalTool *> &ets)
79     : externalTools(ets) {
80 }
81 
getExternalTools() const82 QList<ExternalTool *> ExternalToolSupportSettingsPageState::getExternalTools() const {
83     return externalTools;
84 }
85 
86 /////////////////////////////////////////////
87 ////ExternalToolSupportSettingsPageWidget
88 
89 const QString ExternalToolSupportSettingsPageWidget::INSTALLED = QObject::tr("Installed");
90 const QString ExternalToolSupportSettingsPageWidget::NOT_INSTALLED = QObject::tr("Not installed");
91 const QString ExternalToolSupportSettingsPageWidget::ET_DOWNLOAD_INFO = QObject::tr("<html><head/><body><p>Download <a href=\"http://ugene.net/download-all.html\"><span style=\" text-decoration: underline; color:#1866af;\">tools executables</span></a> and configure the tools paths. </p></body></html>");
92 
93 const QString ExternalToolSupportSettingsPageWidget::SUPPORTED_ID = "integrated tools";
94 const QString ExternalToolSupportSettingsPageWidget::CUSTOM_ID = "custom tools";
95 const QString ExternalToolSupportSettingsPageWidget::INFORMATION_ID = "info";
96 
ExternalToolSupportSettingsPageWidget(ExternalToolSupportSettingsPageController * ctrl)97 ExternalToolSupportSettingsPageWidget::ExternalToolSupportSettingsPageWidget(ExternalToolSupportSettingsPageController *ctrl) {
98     Q_UNUSED(ctrl);
99 
100     setupUi(this);
101     defaultDescriptionText = descriptionTextBrowser->toPlainText();
102 
103     selectToolPackLabel->setText(ET_DOWNLOAD_INFO);
104     versionLabel->hide();
105     binaryPathLabel->hide();
106 
107     supportedToolsShowHideWidget = new ShowHideSubgroupWidget(SUPPORTED_ID, tr("Supported tools"), integratedToolsInnerWidget, true);
108     integratedToolsContainerWidget->layout()->addWidget(supportedToolsShowHideWidget);
109 
110     customToolsShowHideWidget = new ShowHideSubgroupWidget(CUSTOM_ID, tr("Custom tools"), customToolsInnerWidget, false);
111     customToolsContainerWidget->layout()->addWidget(customToolsShowHideWidget);
112 
113     infoShowHideWidget = new ShowHideSubgroupWidget(INFORMATION_ID, tr("Additional information"), infoInnerWidget, true);
114     infoContainerWidget->layout()->addWidget(infoShowHideWidget);
115 
116     twIntegratedTools->setColumnWidth(0, this->geometry().width() / 3);
117     twCustomTools->setColumnWidth(0, this->geometry().width() / 3);
118 
119     twIntegratedTools->installEventFilter(this);
120     twCustomTools->installEventFilter(this);
121 
122     connect(pbImport, SIGNAL(clicked()), SLOT(sl_importCustomToolButtonClicked()));
123     connect(pbDelete, SIGNAL(clicked()), SLOT(sl_deleteCustomToolButtonClicked()));
124     connect(selectToolPackButton, SIGNAL(clicked()), this, SLOT(sl_onBrowseToolPackPath()));
125     connect(selectToolPackLabel, SIGNAL(linkActivated(QString)), this, SLOT(sl_linkActivated(QString)));
126     connect(twCustomTools, SIGNAL(itemSelectionChanged()), SLOT(sl_itemSelectionChanged()));
127     connect(twIntegratedTools, SIGNAL(itemSelectionChanged()), SLOT(sl_itemSelectionChanged()));
128 
129     ExternalToolRegistry *etRegistry = AppContext::getExternalToolRegistry();
130     connect(etRegistry, SIGNAL(si_toolAdded(const QString &)), SLOT(sl_externalToolAdded(const QString &)));
131     connect(etRegistry, SIGNAL(si_toolIsAboutToBeRemoved(const QString &)), SLOT(sl_externalToolIsAboutToBeRemoved(const QString &)));
132 }
133 
~ExternalToolSupportSettingsPageWidget()134 ExternalToolSupportSettingsPageWidget::~ExternalToolSupportSettingsPageWidget() {
135     saveShowHideSubgroupsState();
136 }
137 
createPathEditor(QWidget * parent,const QString & path) const138 QWidget *ExternalToolSupportSettingsPageWidget::createPathEditor(QWidget *parent, const QString &path) const {
139     QWidget *widget = new QWidget(parent);
140 
141     PathLineEdit *toolPathEdit = new PathLineEdit("", "executable", false, widget);
142     toolPathEdit->setObjectName("PathLineEdit");
143     toolPathEdit->setFrame(false);
144     toolPathEdit->setSizePolicy(QSizePolicy(QSizePolicy::Preferred, QSizePolicy::Preferred));
145     toolPathEdit->setText(QDir::toNativeSeparators(path));
146 
147     widget->setFocusProxy(toolPathEdit);
148     connect(toolPathEdit, SIGNAL(si_focusIn()), this, SLOT(sl_onPathEditWidgetClick()));
149     connect(toolPathEdit, SIGNAL(editingFinished()), this, SLOT(sl_toolPathChanged()));
150 
151     QToolButton *selectToolPathButton = new QToolButton(widget);
152     selectToolPathButton->setObjectName("ResetExternalTool");
153     selectToolPathButton->setVisible(true);
154     selectToolPathButton->setText("...");
155     selectToolPathButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred));
156 
157     connect(selectToolPathButton, SIGNAL(clicked()), this, SLOT(sl_onPathEditWidgetClick()));
158     connect(selectToolPathButton, SIGNAL(clicked()), toolPathEdit, SLOT(sl_onBrowse()));
159 
160     QToolButton *clearToolPathButton = new QToolButton(widget);
161     clearToolPathButton->setObjectName("ClearToolPathButton");
162     clearToolPathButton->setVisible(true);
163     clearToolPathButton->setIcon(QIcon(":external_tool_support/images/cancel.png"));
164     clearToolPathButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred));
165     clearToolPathButton->setEnabled(!path.isEmpty());
166 
167     connect(clearToolPathButton, SIGNAL(clicked()), this, SLOT(sl_onPathEditWidgetClick()));
168     connect(clearToolPathButton, SIGNAL(clicked()), toolPathEdit, SLOT(sl_clear()));
169 
170     QHBoxLayout *layout = new QHBoxLayout(widget);
171     layout->setSpacing(0);
172     layout->setMargin(0);
173     layout->addWidget(toolPathEdit);
174 
175     QHBoxLayout *buttonsLayout = new QHBoxLayout();
176     buttonsLayout->addWidget(selectToolPathButton);
177     buttonsLayout->addWidget(clearToolPathButton);
178 
179     layout->addLayout(buttonsLayout);
180     buttonsWidth = buttonsLayout->minimumSize().width();
181     descriptionTextBrowser->setOpenLinks(false);
182     connect(descriptionTextBrowser, SIGNAL(anchorClicked(const QUrl &)), SLOT(sl_onClickLink(const QUrl &)));
183 
184     return widget;
185 }
186 
createToolkitItem(QTreeWidget * treeWidget,const QString & toolkitName,const QIcon & icon)187 QTreeWidgetItem *ExternalToolSupportSettingsPageWidget::createToolkitItem(QTreeWidget *treeWidget, const QString &toolkitName, const QIcon &icon) {
188     QTreeWidgetItem *toolkitItem = new QTreeWidgetItem({toolkitName}, TOOLKIT_TYPE);
189     toolkitItem->setData(0, Qt::ItemDataRole::UserRole, toolkitName);
190     toolkitItem->setIcon(0, icon);
191     treeWidget->addTopLevelItem(toolkitItem);
192 
193     //draw widget for path select button
194     QWidget *widget = new QWidget(treeWidget);
195     QToolButton *selectToolKitPathButton = new QToolButton(widget);
196     selectToolKitPathButton->setVisible(true);
197     selectToolKitPathButton->setText("...");
198     selectToolKitPathButton->setMinimumWidth(buttonsWidth);
199     selectToolKitPathButton->setSizePolicy(QSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred));
200 
201     connect(selectToolKitPathButton, SIGNAL(clicked()), this, SLOT(sl_onPathEditWidgetClick()));
202     connect(selectToolKitPathButton, SIGNAL(clicked()), this, SLOT(sl_onBrowseToolKitPath()));
203 
204     QHBoxLayout *layout = new QHBoxLayout(widget);
205     layout->setSpacing(0);
206     layout->setMargin(0);
207     layout->addStretch();
208     layout->addWidget(selectToolKitPathButton);
209     treeWidget->setItemWidget(toolkitItem, 1, widget);
210 
211     toolkitItem->setExpanded(true);
212     return toolkitItem;
213 }
214 
sl_onClickLink(const QUrl & url)215 void ExternalToolSupportSettingsPageWidget::sl_onClickLink(const QUrl &url) {
216     const QAbstractItemModel *model = twIntegratedTools->selectionModel()->model();
217     QModelIndexList items = model->match(model->index(0, 0), Qt::DisplayRole, QVariant::fromValue(url.toEncoded()), 2, Qt::MatchRecursive);
218     if (items.isEmpty()) {
219         return;
220     }
221     twIntegratedTools->setCurrentIndex(items[0]);
222 }
223 
sl_importCustomToolButtonClicked()224 void ExternalToolSupportSettingsPageWidget::sl_importCustomToolButtonClicked() {
225     //UGENE-6553 temporary removed
226     //QObjectScopedPointer<ImportExternalToolDialog> dialog = new ImportExternalToolDialog(this);
227     //dialog->exec();
228 
229     LastUsedDirHelper lod("import external tool");
230     const QString filter = DialogUtils::prepareFileFilter("UGENE external tool config file", {"xml"}, true, {});
231     lod.url = U2FileDialog::getOpenFileName(this, tr("Select configuration file to import"), lod.dir, filter);
232     if (!lod.url.isEmpty()) {
233         AppContext::getTaskScheduler()->registerTopLevelTask(new ImportCustomToolsTask(QDir::toNativeSeparators(lod.url)));
234     }
235 }
236 
sl_deleteCustomToolButtonClicked()237 void ExternalToolSupportSettingsPageWidget::sl_deleteCustomToolButtonClicked() {
238     QList<QTreeWidgetItem *> selectedItems = twCustomTools->selectedItems();
239     CHECK(!selectedItems.isEmpty(), );
240     const QString toolId = externalToolsItems.key(selectedItems.first());
241     CHECK(!toolId.isEmpty(), );
242 
243     CustomExternalTool *tool = qobject_cast<CustomExternalTool *>(AppContext::getExternalToolRegistry()->getById(toolId));
244     SAFE_POINT(nullptr != tool, "Can't get CustomExternalTool from the registry", );
245     const QString configFilePath = tool->getConfigFilePath();
246 
247     AppContext::getExternalToolRegistry()->unregisterEntry(toolId);
248     QFile configFile(configFilePath);
249     const bool fileRemoved = configFile.remove();
250 
251     if (!fileRemoved) {
252         coreLog.details(tr("Can't remove custom external tool config file from the storage folder: %1").arg(configFilePath));
253     }
254 }
255 
sl_externalToolAdded(const QString & id)256 void ExternalToolSupportSettingsPageWidget::sl_externalToolAdded(const QString &id) {
257     ExternalTool *tool = AppContext::getExternalToolRegistry()->getById(id);
258 
259     ExternalToolInfo info;
260     info.id = id;
261     info.dirName = tool->getDirName();
262     info.name = tool->getName();
263     info.path = tool->getPath();
264     info.description = tool->getDescription();
265     info.isValid = tool->isValid();
266     info.version = tool->getVersion();
267     info.isModule = tool->isModule();
268     externalToolsInfo.insert(info.id, info);
269 
270     connect(tool, SIGNAL(si_toolValidationStatusChanged(bool)), SLOT(sl_toolValidationStatusChanged(bool)));
271 
272     QTreeWidget *treeWidget = tool->isCustom() ? twCustomTools : twIntegratedTools;
273     appendToolItem(treeWidget->invisibleRootItem(), tool);
274 }
275 
sl_externalToolIsAboutToBeRemoved(const QString & id)276 void ExternalToolSupportSettingsPageWidget::sl_externalToolIsAboutToBeRemoved(const QString &id) {
277     externalToolsInfo.remove(id);
278 
279     ExternalTool *tool = AppContext::getExternalToolRegistry()->getById(id);
280     disconnect(tool, SIGNAL(si_toolValidationStatusChanged(bool)), this, SLOT(sl_toolValidationStatusChanged(bool)));
281 
282     QTreeWidgetItem *item = externalToolsItems.value(id, nullptr);
283     if (nullptr != item) {
284         QTreeWidgetItem *parentItem = item->parent();
285         if (nullptr == parentItem) {
286             parentItem = item->treeWidget()->invisibleRootItem();
287         }
288         parentItem->takeChild(parentItem->indexOfChild(item));
289         delete item;
290 
291         if (TOOLKIT_TYPE == parentItem->type() && 0 == parentItem->childCount()) {
292             QTreeWidgetItem *grandParentItem = parentItem->treeWidget()->invisibleRootItem();
293             grandParentItem->takeChild(grandParentItem->indexOfChild(parentItem));
294         }
295         externalToolsItems.remove(id);
296     }
297 }
298 
sl_linkActivated(const QString & url)299 void ExternalToolSupportSettingsPageWidget::sl_linkActivated(const QString &url) {
300     GUIUtils::runWebBrowser(url);
301 }
302 
extractCustomTools(QList<QList<ExternalTool * >> & toolKitList,QList<ExternalTool * > & customTools)303 static void extractCustomTools(QList<QList<ExternalTool *>> &toolKitList, QList<ExternalTool *> &customTools) {
304     for (QList<ExternalTool *> &toolList : toolKitList) {
305         for (ExternalTool *tool : toolList) {
306             if (tool->isCustom()) {
307                 customTools << tool;
308                 toolList.removeAll(tool);
309             }
310         }
311     }
312 }
313 
314 /** Returns master ExternalTool if tools list contains only 1 tool and 0+ modules or nullptr otherwise. */
isMasterWithModules(const QList<ExternalTool * > & toolsList)315 static ExternalTool *isMasterWithModules(const QList<ExternalTool *> &toolsList) {
316     ExternalTool *master = nullptr;
317     for (ExternalTool *tool : qAsConst(toolsList)) {
318         if (tool->isModule()) {
319             continue;
320         }
321         if (master != nullptr) {
322             return nullptr;
323         }
324         master = tool;
325     }
326     return master;
327 }
328 
setState(AppSettingsGUIPageState * s)329 void ExternalToolSupportSettingsPageWidget::setState(AppSettingsGUIPageState *s) {
330     ExternalToolSupportSettingsPageState *state = qobject_cast<ExternalToolSupportSettingsPageState *>(s);
331     SAFE_POINT(state != nullptr, "ExternalToolSupportSettingsPageState is absent", );
332 
333     const QList<ExternalTool *> toolList = state->getExternalTools();
334     for (ExternalTool *tool : qAsConst(toolList)) {
335         ExternalToolInfo info;
336         info.id = tool->getId();
337         info.dirName = tool->getDirName();
338         info.name = tool->getName();
339         info.path = tool->getPath();
340         info.description = tool->getDescription();
341         info.isValid = tool->isValid();
342         info.version = tool->getVersion();
343         info.isModule = tool->isModule();
344         externalToolsInfo.insert(info.id, info);
345 
346         connect(tool, SIGNAL(si_toolValidationStatusChanged(bool)), SLOT(sl_toolValidationStatusChanged(bool)));
347     }
348 
349     QList<QList<ExternalTool *>> toolsPerToolkitList = AppContext::getExternalToolRegistry()->getAllEntriesSortedByToolKits();
350     QList<ExternalTool *> customToolList;
351     extractCustomTools(toolsPerToolkitList, customToolList);
352 
353     QTreeWidgetItem *rootItem = twIntegratedTools->invisibleRootItem();
354     for (QList<ExternalTool *> toolsList : qAsConst(toolsPerToolkitList)) {
355         CHECK_CONTINUE(!toolsList.isEmpty());
356 
357         if (toolsList.length() == 1) {
358             appendToolItem(rootItem, toolsList[0]);
359             continue;
360         }
361         ExternalTool *groupTool = isMasterWithModules(toolsList);
362         if (groupTool != nullptr) {
363             QTreeWidgetItem *masterItem = appendToolItem(rootItem, groupTool);
364             masterItem->setExpanded(false);
365             toolsList.removeOne(groupTool);
366             for (ExternalTool *tool : qAsConst(toolsList)) {
367                 appendToolItem(masterItem, tool, true);
368             }
369         } else {
370             groupTool = toolsList[0];
371             QTreeWidgetItem *toolkitItem = createToolkitItem(twIntegratedTools, groupTool->getToolKitName(), groupTool->getIcon());
372             for (ExternalTool *tool : qAsConst(toolsList)) {
373                 appendToolItem(toolkitItem, tool);
374             }
375         }
376     }
377 
378     QTreeWidgetItem *customToolsRootItem = twCustomTools->invisibleRootItem();
379     for (ExternalTool *tool : qAsConst(customToolList)) {
380         appendToolItem(customToolsRootItem, tool);
381     }
382 
383     Settings *settings = AppContext::getSettings();
384     supportedToolsShowHideWidget->setSubgroupOpened(settings->getValue(ExternalToolSupportSettingsPageWidget::SUPPORTED_ID, true).toBool());
385     customToolsShowHideWidget->setSubgroupOpened(settings->getValue(ExternalToolSupportSettingsPageWidget::CUSTOM_ID, false).toBool());
386     infoShowHideWidget->setSubgroupOpened(settings->getValue(ExternalToolSupportSettingsPageWidget::INFORMATION_ID, true).toBool());
387 }
388 
appendToolItem(QTreeWidgetItem * rootItem,const ExternalTool * tool,bool isModule)389 QTreeWidgetItem *ExternalToolSupportSettingsPageWidget::appendToolItem(QTreeWidgetItem *rootItem, const ExternalTool *tool, bool isModule) {
390     QTreeWidgetItem *item = new QTreeWidgetItem(QStringList() << tool->getName());
391     item->setData(0, Qt::ItemDataRole::UserRole, tool->getId());
392 
393     externalToolsItems.insert(tool->getId(), item);
394     rootItem->addChild(item);
395 
396     const ExternalToolInfo &toolInfo = externalToolsInfo.value(tool->getId());
397     QTreeWidget *treeWidget = rootItem->treeWidget();
398 
399     QWidget *toolItemWidget = isModule ? new QLabel(toolInfo.isValid ? INSTALLED : NOT_INSTALLED)
400                                        : createPathEditor(treeWidget, toolInfo.path);
401 
402     treeWidget->setItemWidget(item, 1, toolItemWidget);
403 
404     QIcon icon = toolInfo.path.isEmpty() ? tool->getGrayIcon()
405                                          : (toolInfo.isValid ? tool->getIcon()
406                                                              : tool->getWarnIcon());
407     item->setIcon(0, icon);
408     return item;
409 }
410 
setToolState(ExternalTool * tool)411 void ExternalToolSupportSettingsPageWidget::setToolState(ExternalTool *tool) {
412     QTreeWidgetItem *item = externalToolsItems.value(tool->getId(), nullptr);
413     SAFE_POINT(nullptr != item, QString("Tree item for the tool %1 not found").arg(tool->getName()), );
414 
415     externalToolsInfo[tool->getId()].isValid = tool->isValid();
416     QLabel *moduleToolLabel = qobject_cast<QLabel *>(twIntegratedTools->itemWidget(item, 1));
417     QString moduleToolState;
418     QString toolStateDesc;
419 
420     if (tool->isValid()) {
421         item->setIcon(0, AppContext::getExternalToolRegistry()->getById(tool->getId())->getIcon());
422         moduleToolState = INSTALLED;
423     } else if (!tool->getPath().isEmpty()) {
424         toolStateDesc = getToolStateDescription(tool);
425         item->setIcon(0, AppContext::getExternalToolRegistry()->getById(tool->getId())->getWarnIcon());
426         moduleToolState = NOT_INSTALLED;
427     } else {
428         item->setIcon(0, AppContext::getExternalToolRegistry()->getById(tool->getId())->getGrayIcon());
429         moduleToolState = "";
430     }
431 
432     if (moduleToolLabel) {
433         moduleToolLabel->setText(moduleToolState);
434     }
435 
436     externalToolsInfo[tool->getId()].path = tool->getPath();
437     if (!tool->getVersion().isEmpty()) {
438         externalToolsInfo[tool->getId()].version = tool->getVersion();
439     } else {
440         externalToolsInfo[tool->getId()].version = "unknown";
441     }
442 
443     QList<QTreeWidgetItem *> selectedItems = twIntegratedTools->selectedItems();
444     CHECK(selectedItems.length() > 0, );
445     QString selectedName = selectedItems.at(0)->text(0);
446 
447     if (selectedName == tool->getName()) {
448         setDescription(tool);
449     }
450 }
451 
getToolLink(const QString & toolName) const452 QString ExternalToolSupportSettingsPageWidget::getToolLink(const QString &toolName) const {
453     return "<a href='" + toolName + "'>" + toolName + "</a>";
454 }
455 
getToolStateDescription(ExternalTool * tool) const456 QString ExternalToolSupportSettingsPageWidget::getToolStateDescription(ExternalTool *tool) const {
457     QString result;
458 
459     SAFE_POINT(tool, "Tool pointer is NULL", result);
460     ExternalToolRegistry *etRegistry = AppContext::getExternalToolRegistry();
461     SAFE_POINT(etRegistry, "External tool registry is NULL", result);
462     ExternalToolManager *etManager = etRegistry->getManager();
463     SAFE_POINT(etManager, "External tool manager is NULL", result);
464 
465     ExternalToolManager::ExternalToolState state = etManager->getToolState(tool->getId());
466 
467     if (state == ExternalToolManager::NotValidByDependency) {
468         QString text = tr("External tool '%1' cannot be validated as it "
469                           "depends on other tools, some of which are not valid. "
470                           "The list of tools is the following: ")
471                            .arg(tool->getName());
472 
473         QStringList invalidDependencies;
474         QStringList dependencies = tool->getDependencies();
475         for (const QString &masterId : qAsConst(dependencies)) {
476             if (etManager->getToolState(masterId) != ExternalToolManager::Valid) {
477                 QString masterName = AppContext::getExternalToolRegistry()->getToolNameById(masterId);
478                 if (tool->getId() != masterId && tool->getToolKitName() != masterName) {
479                     invalidDependencies << getToolLink(masterName);
480                 } else {
481                     invalidDependencies << masterName;
482                 }
483             }
484         }
485         result = warn(text + invalidDependencies.join(", ")) + "<br><br>";
486     }
487 
488     if (state == ExternalToolManager::NotValid) {
489         if (tool->isModule()) {
490             QStringList toolDependencies = tool->getDependencies();
491             SAFE_POINT(!toolDependencies.isEmpty(), QString("Empty dependency list for "
492                                                             "the '%1' module tool")
493                                                         .arg(tool->getName()),
494                        result);
495             QString masterId = toolDependencies.first();
496             QString text = tr("'%1' is %2 module and it is not installed. "
497                               "Install it and restart UGENE or set another "
498                               "%2 with already installed '%1' module.")
499                                .arg(tool->getName())
500                                .arg(AppContext::getExternalToolRegistry()->getToolNameById(masterId));
501 
502             result = warn(text) + "<br><br>";
503         }
504 
505         if (tool->hasAdditionalErrorMessage()) {
506             result += warn(tool->getAdditionalErrorMessage()) + "<br><br>";
507         }
508     }
509     return result;
510 }
511 
resetDescription()512 void ExternalToolSupportSettingsPageWidget::resetDescription() {
513     descriptionTextBrowser->setPlainText(defaultDescriptionText);
514 }
515 
setDescription(ExternalTool * tool)516 void ExternalToolSupportSettingsPageWidget::setDescription(ExternalTool *tool) {
517     QString desc = tr("No description");
518     if (tool != nullptr) {
519         desc = getToolStateDescription(tool);
520         if (desc.size() == 0) {
521             desc = tool->getDescription();
522         } else {
523             desc += tool->getDescription();
524         }
525         if (tool->isValid()) {
526             desc += tr("<br><br>Version: ");
527             if (!externalToolsInfo[tool->getId()].version.isEmpty()) {
528                 desc += externalToolsInfo[tool->getId()].version;
529             } else {
530                 desc += tr("unknown");
531             }
532         }
533 
534         if (!externalToolsInfo[tool->getId()].path.isEmpty()) {
535             desc += tr("<br><br>Binary path: ");
536             desc += externalToolsInfo[tool->getId()].path;
537         }
538     }
539     descriptionTextBrowser->setText(desc + "<a href='1'></a>");
540 }
541 
warn(const QString & text) const542 QString ExternalToolSupportSettingsPageWidget::warn(const QString &text) const {
543     return "<span style=\"color:" + Theme::errorColorLabelStr() + "; font:bold;\">" + text + "</span>";
544 }
545 
eventFilter(QObject * watched,QEvent * event)546 bool ExternalToolSupportSettingsPageWidget::eventFilter(QObject *watched, QEvent *event) {
547     CHECK(event->type() == QEvent::FocusIn, false);
548 
549     QTreeWidgetItem *item = nullptr;
550     QList<QTreeWidgetItem *> selectedItems;
551     if (watched == twIntegratedTools) {
552         selectedItems = twIntegratedTools->selectedItems();
553     } else if (watched == twCustomTools) {
554         selectedItems = twCustomTools->selectedItems();
555     }
556 
557     if (!selectedItems.isEmpty()) {
558         item = selectedItems.first();
559     }
560 
561     bool itemSelected = item != nullptr;
562     if (itemSelected) {
563         QString toolId = externalToolsItems.key(item);
564         setDescription(AppContext::getExternalToolRegistry()->getById(toolId));
565     } else {
566         resetDescription();
567     }
568 
569     return false;
570 }
571 
saveShowHideSubgroupsState() const572 void ExternalToolSupportSettingsPageWidget::saveShowHideSubgroupsState() const {
573     Settings *settings = AppContext::getSettings();
574     settings->setValue(ExternalToolSupportSettingsPageWidget::SUPPORTED_ID, QVariant(supportedToolsShowHideWidget->isSubgroupOpened()));
575     settings->setValue(ExternalToolSupportSettingsPageWidget::CUSTOM_ID, QVariant(customToolsShowHideWidget->isSubgroupOpened()));
576     settings->setValue(ExternalToolSupportSettingsPageWidget::INFORMATION_ID, QVariant(infoShowHideWidget->isSubgroupOpened()));
577 }
578 
getState(QString & err) const579 AppSettingsGUIPageState *ExternalToolSupportSettingsPageWidget::getState(QString &err) const {
580     Q_UNUSED(err);
581 
582     QList<ExternalTool *> externalTools;
583     const QList<ExternalToolInfo> toolInfoList = externalToolsInfo.values();
584     for (const ExternalToolInfo &info : qAsConst(toolInfoList)) {
585         auto externalTool = new ExternalTool(info.id, info.dirName, info.name, info.path);
586         externalTool->setValid(info.isValid);
587         externalTool->setVersion(info.version);
588         externalTools.append(externalTool);
589     }
590     return new ExternalToolSupportSettingsPageState(externalTools);
591 }
592 
sl_toolPathChanged()593 void ExternalToolSupportSettingsPageWidget::sl_toolPathChanged() {
594     PathLineEdit *s = qobject_cast<PathLineEdit *>(sender());
595 
596     if (!s || !s->isModified()) {
597         return;
598     }
599 
600     QWidget *par = s->parentWidget();
601     QString path = s->text();
602     s->setModified(false);
603 
604     QList<QTreeWidgetItem *> listOfItems = twIntegratedTools->findItems("", Qt::MatchContains | Qt::MatchRecursive) << twCustomTools->findItems("", Qt::MatchContains | Qt::MatchRecursive);
605     SAFE_POINT(listOfItems.length() != 0, "ExternalToolSupportSettings, NO items are selected", );
606 
607     twIntegratedTools->clearSelection();
608     foreach (QTreeWidgetItem *item, listOfItems) {
609         QWidget *itemWid = item->treeWidget()->itemWidget(item, 1);
610         if (par == itemWid) {    //may be no good method for check QTreeWidgetItem
611             emit si_setLockState(true);
612             QString toolId = item->data(0, Qt::ItemDataRole::UserRole).toString();
613             if (path.isEmpty()) {
614                 item->setIcon(0, AppContext::getExternalToolRegistry()->getById(toolId)->getGrayIcon());
615             }
616 
617             ExternalToolManager *etManager = AppContext::getExternalToolRegistry()->getManager();
618             SAFE_POINT(etManager != nullptr, "External tool manager is null", );
619 
620             ExternalToolValidationListener *listener = new ExternalToolValidationListener(toolId);
621             connect(listener, SIGNAL(si_validationComplete()), SLOT(sl_validationComplete()));
622             StrStrMap pathMap;
623             pathMap[toolId] = path;
624             etManager->validate(QStringList() << toolId, pathMap, listener);
625         }
626     }
627 }
628 
sl_validationComplete()629 void ExternalToolSupportSettingsPageWidget::sl_validationComplete() {
630     ExternalToolValidationListener *listener = qobject_cast<ExternalToolValidationListener *>(sender());
631     SAFE_POINT(nullptr != listener, "Unexpected message sender", );
632 
633     listener->deleteLater();
634 
635     ExternalToolRegistry *etRegistry = AppContext::getExternalToolRegistry();
636     CHECK(etRegistry, );
637 
638     foreach (const QString &toolId, listener->getToolIds()) {
639         ExternalTool *tool = etRegistry->getById(toolId);
640         SAFE_POINT(nullptr != tool, QString("External tool %1 not found in the registry.").arg(toolId), );
641         setToolState(tool);
642     }
643     emit si_setLockState(false);
644 }
645 
sl_toolValidationStatusChanged(bool isValid)646 void ExternalToolSupportSettingsPageWidget::sl_toolValidationStatusChanged(bool isValid) {
647     Q_UNUSED(isValid);
648 
649     ExternalTool *s = qobject_cast<ExternalTool *>(sender());
650     SAFE_POINT(nullptr != s, "Unexpected message sender", );
651 
652     setToolState(s);
653 }
654 
sl_itemSelectionChanged()655 void ExternalToolSupportSettingsPageWidget::sl_itemSelectionChanged() {
656     QTreeWidget *treeWidget = qobject_cast<QTreeWidget *>(sender());
657     QList<QTreeWidgetItem *> selectedItems = treeWidget->selectedItems();
658 
659     pbDelete->setEnabled(!twCustomTools->selectedItems().isEmpty());
660 
661     if (selectedItems.length() == 0) {
662         descriptionTextBrowser->setText(tr("Select an external tool to view more information about it."));
663         return;
664     }
665     SAFE_POINT(selectedItems.length() != 0, "ExternalToolSupportSettings, no items selected", );
666 
667     QString id = selectedItems.at(0)->data(0, Qt::ItemDataRole::UserRole).toString();
668 
669     if (selectedItems.at(0)->type() == TOOLKIT_TYPE) {
670         QString text = AppContext::getExternalToolRegistry()->getToolkitDescription(id);
671         if (!text.isEmpty()) {
672             descriptionTextBrowser->setText(text);
673             return;
674         }
675     }
676 
677     //no description or tool custom description
678     ExternalTool *tool = AppContext::getExternalToolRegistry()->getById(id);
679     setDescription(tool);
680 }
681 
sl_onPathEditWidgetClick()682 void ExternalToolSupportSettingsPageWidget::sl_onPathEditWidgetClick() {
683     QWidget *s = qobject_cast<QWidget *>(sender());
684     SAFE_POINT(s != nullptr, "Unexpected message sender", );
685 
686     QList<QTreeWidgetItem *> listOfItems = twIntegratedTools->findItems("", Qt::MatchContains | Qt::MatchRecursive);
687     listOfItems << twCustomTools->findItems("", Qt::MatchContains | Qt::MatchRecursive);
688     SAFE_POINT(listOfItems.length() != 0, "No items were found in the tree", );
689 
690     twIntegratedTools->clearSelection();
691     twCustomTools->clearSelection();
692     foreach (QTreeWidgetItem *item, listOfItems) {
693         QWidget *par = s->parentWidget();
694         QWidget *itemWid = item->treeWidget()->itemWidget(item, 1);
695         if (itemWid == par) {
696             item->setSelected(true);
697         }
698     }
699 }
700 
701 //looks in selected folder +1 level 1 subfolders
sl_onBrowseToolKitPath()702 void ExternalToolSupportSettingsPageWidget::sl_onBrowseToolKitPath() {
703     LastUsedDirHelper lod("toolkit path");
704     QString dir;
705 
706     lod.url = dir = U2FileDialog::getExistingDirectory(this, tr("Choose Folder With Executables"), lod.dir, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
707     if (!dir.isEmpty()) {
708         assert(twIntegratedTools->selectedItems().isEmpty() == 0);
709         QString toolKitName = twIntegratedTools->selectedItems().first()->text(0);
710         QList<QTreeWidgetItem *> listOfItems = twIntegratedTools->findItems("", Qt::MatchContains | Qt::MatchRecursive);
711         assert(listOfItems.length() != 0);
712 
713         QStringList toolIds;
714         StrStrMap toolPaths;
715         foreach (QTreeWidgetItem *item, listOfItems) {
716             if (!externalToolsItems.values().contains(item)) {
717                 continue;
718             }
719             QString itemId = item->data(0, Qt::UserRole).toString();
720             ExternalTool *tool = AppContext::getExternalToolRegistry()->getById(itemId);
721             if (tool != nullptr && tool->getToolKitName() == toolKitName) {
722                 QWidget *itemWid = twIntegratedTools->itemWidget(item, 1);
723                 PathLineEdit *lineEdit = itemWid->findChild<PathLineEdit *>("PathLineEdit");
724                 LimitedDirIterator it(dir);
725                 bool fileNotFound = true;
726                 QString executableFileName = tool->getExecutableFileName();
727                 while (it.hasNext() && fileNotFound) {
728                     it.next();
729                     QString fpath = it.filePath() + QDir::separator() + executableFileName;
730 
731                     QFileInfo info(fpath);
732                     if (info.exists() && info.isFile()) {
733                         QString path = QDir::toNativeSeparators(fpath);
734                         lineEdit->setText(path);
735                         lineEdit->setModified(false);
736                         externalToolsInfo[itemId].path = path;
737                         QToolButton *clearToolPathButton = itemWid->findChild<QToolButton *>("ClearToolPathButton");
738                         assert(clearToolPathButton);
739                         clearToolPathButton->setEnabled(true);
740                         toolIds << itemId;
741                         toolPaths.insert(itemId, path);
742                         fileNotFound = false;
743                     }
744                 }
745             }
746         }
747         if (!toolIds.isEmpty()) {
748             emit si_setLockState(true);
749             ExternalToolManager *etManager = AppContext::getExternalToolRegistry()->getManager();
750             ExternalToolValidationListener *listener = new ExternalToolValidationListener(toolIds);
751             connect(listener, SIGNAL(si_validationComplete()), SLOT(sl_validationComplete()));
752             etManager->validate(toolIds, toolPaths, listener);
753         }
754     }
755 }
756 
sl_onBrowseToolPackPath()757 void ExternalToolSupportSettingsPageWidget::sl_onBrowseToolPackPath() {
758     LastUsedDirHelper lod("toolpack path");
759     QString dirPath;
760     lod.dir = dirPath = U2FileDialog::getExistingDirectory(this, tr("Select Folder With External Tools Package"), lod.dir, QFileDialog::ShowDirsOnly | QFileDialog::DontResolveSymlinks);
761 
762     if (!dirPath.isEmpty()) {
763         QDir dir = QDir(dirPath);
764         QList<QTreeWidgetItem *> listOfItems = twIntegratedTools->findItems("", Qt::MatchContains | Qt::MatchRecursive);
765         assert(listOfItems.length() != 0);
766         QStringList toolIds;
767         StrStrMap toolPaths;
768         bool isValidExternalToolsFolder = false;
769 
770         const QList<ExternalTool *> toolList = AppContext::getExternalToolRegistry()->getAllEntries();
771         for (const ExternalTool *externalTool : qAsConst(toolList)) {
772             if (externalTool->isModule()) {
773                 continue;
774             }
775             QTreeWidgetItem *item = externalToolsItems.value(externalTool->getId(), nullptr);
776             SAFE_POINT(item != nullptr, QString("Tree item not found for the tool %1").arg(externalTool->getName()), );
777 
778             const QStringList dirList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot);
779             for (QString dirName : qAsConst(dirList)) {
780                 if (externalTool->getDirName() != dirName) {
781                     continue;
782                 }
783                 isValidExternalToolsFolder = true;
784                 QWidget *itemWid = twIntegratedTools->itemWidget(item, 1);
785                 PathLineEdit *lineEdit = itemWid->findChild<PathLineEdit *>("PathLineEdit");
786 
787                 LimitedDirIterator it(dirPath + "/" + dirName);
788                 QString executableFileName = AppContext::getExternalToolRegistry()->getById(externalTool->getId())->getExecutableFileName();
789                 while (it.hasNext()) {
790                     it.next();
791                     QString executableFilePath = it.filePath() + "/" + executableFileName;
792                     if (QFileInfo(executableFilePath).isFile()) {
793                         QString path = QDir::toNativeSeparators(executableFilePath);
794                         lineEdit->setText(path);
795                         lineEdit->setModified(false);
796                         externalToolsInfo[item->data(0, Qt::UserRole).toString()].path = path;
797 
798                         QToolButton *clearToolPathButton = itemWid->findChild<QToolButton *>("ClearToolPathButton");
799                         SAFE_POINT(clearToolPathButton != nullptr, "ClearToolPathButton not found!", );
800                         clearToolPathButton->setEnabled(true);
801 
802                         toolIds << externalTool->getId();
803                         toolPaths.insert(externalTool->getId(), path);
804                         break;
805                     }
806                 }
807             }
808         }
809 
810         if (!isValidExternalToolsFolder) {
811             QMessageBox::warning(this, L10N::warningTitle(), tr("Not a valid external tools folder"), QMessageBox::Ok);
812         }
813         if (!toolIds.isEmpty()) {
814             emit si_setLockState(true);
815             ExternalToolManager *etManager = AppContext::getExternalToolRegistry()->getManager();
816             ExternalToolValidationListener *listener = new ExternalToolValidationListener(toolIds);
817             connect(listener, SIGNAL(si_validationComplete()), SLOT(sl_validationComplete()));
818             etManager->validate(toolIds, toolPaths, listener);
819         }
820     }
821 }
822 
823 ////////////////////////////////////////
824 //PathLineEdit
sl_onBrowse()825 void PathLineEdit::sl_onBrowse() {
826     LastUsedDirHelper lod(type);
827 
828     QString name;
829     if (text().isEmpty()) {
830         lod.url = name = U2FileDialog::getOpenFileName(nullptr, tr("Select a file"), lod.dir, FileFilter, 0, QFileDialog::DontConfirmOverwrite);
831     } else {
832         lod.url = name = U2FileDialog::getOpenFileName(nullptr, tr("Select a file"), text(), FileFilter, 0, QFileDialog::DontConfirmOverwrite);
833     }
834     if (!name.isEmpty()) {
835         setText(QDir::toNativeSeparators(name));
836         setModified(true);
837         emit editingFinished();
838     }
839     QToolButton *clearToolPathButton = this->parentWidget()->findChild<QToolButton *>("ClearToolPathButton");
840     assert(clearToolPathButton);
841     clearToolPathButton->setEnabled(!text().isEmpty());
842     setFocus();
843 }
844 
sl_clear()845 void PathLineEdit::sl_clear() {
846     QToolButton *s = qobject_cast<QToolButton *>(sender());
847     assert(s);
848     setText("");
849     s->setEnabled(false);
850     setModified(true);
851     emit editingFinished();
852 }
853 
focusInEvent(QFocusEvent *)854 void PathLineEdit::focusInEvent(QFocusEvent * /*event*/) {
855     emit si_focusIn();
856 }
857 
858 }    // namespace U2
859