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 "ExternalToolsDashboardWidget.h"
23
24 #include <QApplication>
25 #include <QClipboard>
26 #include <QHBoxLayout>
27 #include <QPainter>
28
29 #include <U2Core/U2SafePoints.h>
30
31 #include <U2Gui/HoverQLabel.h>
32
33 #include "DomUtils.h"
34
35 namespace U2 {
36
37 #define NODE_KIND_ACTOR 1
38 #define NODE_KIND_TOOL 2
39 #define NODE_KIND_RUN 3
40 #define NODE_KIND_COMMAND 4
41 #define NODE_KIND_OUTPUT 5
42 #define NODE_KIND_LOG_CONTENT 6
43
44 #define NODE_CLASS_ACTOR QString("actor-node")
45 #define NODE_CLASS_TOOL QString("actor-tick-node")
46 #define NODE_CLASS_RUN QString("tool-run-node")
47 #define NODE_CLASS_COMMAND QString("command")
48 #define NODE_CLASS_CONTENT QString("content")
49 #define NODE_CLASS_IMPORTANT QString("badge-important")
50 #define NODE_CLASS_BADGE QString("badge")
51
52 const QString ExternalToolsDashboardWidget::TREE_ID("treeRoot");
53
fixOldStyleOpenFileJs(const QString & html)54 static QString fixOldStyleOpenFileJs(const QString &html) {
55 return QString(html).replace("onclick=\"openLog('", "href=\"file://").replace("/logs')", "/logs").replace("_log.txt')", "_log.txt");
56 }
57
ExternalToolsDashboardWidget(const QDomElement & dom,const WorkflowMonitor * monitor)58 ExternalToolsDashboardWidget::ExternalToolsDashboardWidget(const QDomElement &dom, const WorkflowMonitor *monitor)
59 : monitor(monitor) {
60 setMinimumWidth(1100); // TODO: make it expanding.
61
62 // A frame with rounded borders around the content.
63 auto frameLayout = new QVBoxLayout();
64 frameLayout->setContentsMargins(12, 12, 12, 12);
65 setLayout(frameLayout);
66 auto frameWidget = new QWidget();
67 frameWidget->setObjectName("frameWidget");
68 frameWidget->setStyleSheet("#frameWidget {border: 1px solid #ddd; padding: 8px; border-radius: 6px;}");
69 frameLayout->addWidget(frameWidget);
70
71 // Vertical layout with all nodes.
72 layout = new QVBoxLayout();
73 layout->setMargin(12);
74 layout->setSpacing(0);
75 frameWidget->setLayout(layout);
76
77 QList<QDomElement> actorElementList = DomUtils::findChildElementsByClass(DomUtils::findElementById(dom, TREE_ID), NODE_CLASS_ACTOR, 2);
78 for (auto actorSpan : qAsConst(actorElementList)) {
79 auto actorNode = new ExternalToolsTreeNode(NODE_KIND_ACTOR, actorSpan.attribute("id"), actorSpan.text(), nullptr);
80 layout->addWidget(actorNode);
81 topLevelNodes << actorNode;
82
83 QList<QDomElement> toolElementList = DomUtils::findChildElementsByClass(actorSpan.nextSiblingElement("ul"), NODE_CLASS_TOOL, 2);
84 for (auto toolSpan : qAsConst(toolElementList)) {
85 auto toolNode = new ExternalToolsTreeNode(NODE_KIND_TOOL, toolSpan.attribute("id"), toolSpan.text(), actorNode);
86 layout->addWidget(toolNode);
87 addLimitationWarningIfNeeded(toolNode, DomUtils::findParentByTag(toolSpan, "ul"));
88
89 QList<QDomElement> runElementList = DomUtils::findChildElementsByClass(toolSpan.nextSiblingElement("ul"), NODE_CLASS_RUN, 2);
90 for (auto runSpan : qAsConst(runElementList)) {
91 auto runNode = new ExternalToolsTreeNode(NODE_KIND_RUN, runSpan.attribute("id"), runSpan.text(), toolNode, DomUtils::hasClass(runSpan, NODE_CLASS_IMPORTANT));
92 layout->addWidget(runNode);
93 addLimitationWarningIfNeeded(runNode, DomUtils::findParentByTag(runSpan, "ul"));
94
95 QDomElement commandSpan = runSpan.nextSiblingElement("ul").firstChildElement("li").firstChildElement("span");
96 if (!commandSpan.isNull()) {
97 auto commandNode = new ExternalToolsTreeNode(NODE_KIND_COMMAND, commandSpan.attribute("id"), commandSpan.text(), runNode);
98 layout->addWidget(commandNode);
99
100 QDomElement commandContentSpan = commandSpan.nextSiblingElement("ul").firstChildElement("li").firstChildElement("span");
101 if (!commandContentSpan.isNull()) {
102 auto commandContentNode = new ExternalToolsTreeNode(NODE_KIND_LOG_CONTENT, commandContentSpan.attribute("id"), commandContentSpan.text(), commandNode);
103 layout->addWidget(commandContentNode);
104 }
105
106 QDomNode outputLi = commandSpan.parentNode(); // previous node for the real outputLi.
107 while (true) {
108 outputLi = outputLi.nextSiblingElement("li");
109 QDomElement outputSpan = outputLi.firstChildElement("span");
110 if (outputLi.isNull() || outputSpan.isNull()) {
111 break;
112 }
113 auto outputNode = new ExternalToolsTreeNode(NODE_KIND_OUTPUT, outputSpan.attribute("id"), outputSpan.text(), runNode, DomUtils::hasClass(outputSpan, NODE_CLASS_IMPORTANT));
114 layout->addWidget(outputNode);
115
116 QDomElement outputContentSpan = outputSpan.nextSiblingElement("ul").firstChildElement("li").firstChildElement("span");
117 if (!outputContentSpan.isNull()) {
118 QString outputHtml = fixOldStyleOpenFileJs(DomUtils::toString(outputContentSpan, false));
119 layout->addWidget(new ExternalToolsTreeNode(NODE_KIND_LOG_CONTENT, outputContentSpan.attribute("id"), outputHtml, outputNode));
120 }
121 }
122 }
123 }
124 }
125 }
126 if (!actorElementList.isEmpty()) {
127 addLimitationWarningIfNeeded(nullptr, DomUtils::findParentByTag(actorElementList.first(), "ul"));
128 }
129 }
130
isValidDom(const QDomElement & dom)131 bool ExternalToolsDashboardWidget::isValidDom(const QDomElement &dom) {
132 return !DomUtils::findElementById(dom, TREE_ID).isNull();
133 }
134
addLimitationWarningIfNeeded(ExternalToolsTreeNode * parentNode,const QDomElement & listHeadElement)135 void ExternalToolsDashboardWidget::addLimitationWarningIfNeeded(ExternalToolsTreeNode *parentNode, const QDomElement &listHeadElement) {
136 QDomElement span = listHeadElement.lastChildElement("li").firstChildElement("span");
137 if (!DomUtils::hasClass(span, "limitation-message")) {
138 return;
139 }
140 QString text = fixOldStyleOpenFileJs(DomUtils::toString(span, false));
141 addLimitationWarning(parentNode, text);
142 }
143
addLimitationWarning(ExternalToolsTreeNode * parentNode,const QString & limitationMessage)144 void ExternalToolsDashboardWidget::addLimitationWarning(ExternalToolsTreeNode *parentNode, const QString &limitationMessage) {
145 QString message = limitationMessage;
146 if (message.isEmpty()) {
147 SAFE_POINT(monitor != nullptr, "WorkflowMonitor is null!", );
148 message = "Messages limit on the dashboard exceeded. See <a href=\"" + monitor->getLogsDir() + "\">log files</a>.";
149 }
150 auto limitationLabel = new QLabel("<code>" + message + "</code>");
151 limitationLabel->setStyleSheet("font-size: 16px; background-color: #F0F0F0; color: black; padding: 5px;");
152 limitationLabel->setOpenExternalLinks(true);
153 if (parentNode == nullptr) {
154 if (!limitationWarningHtml.isEmpty()) {
155 return;
156 }
157 layout->addSpacing(20);
158 layout->addWidget(limitationLabel);
159 limitationWarningHtml = message;
160 } else {
161 if (!parentNode->limitationWarningHtml.isEmpty()) {
162 return;
163 }
164 parentNode->limitationWarningHtml = message;
165 int lastChildIndex = parentNode->children.isEmpty() ? 0 : layout->indexOf(parentNode->children.last());
166 layout->insertSpacing(lastChildIndex, 20);
167 layout->insertWidget(lastChildIndex, limitationLabel);
168 }
169 }
170
toHtml() const171 QString ExternalToolsDashboardWidget::toHtml() const {
172 CHECK(!topLevelNodes.isEmpty(), "");
173 QString html = "<ul id=\"" + TREE_ID + "\">";
174 for (auto node : qAsConst(topLevelNodes)) {
175 html += node->toHtml();
176 }
177 if (!limitationWarningHtml.isEmpty()) {
178 html += "<li><span class=\"badge limitation-message\">" + limitationWarningHtml + "</span></li>";
179 }
180 html += "</ul>";
181 return html;
182 }
183
findNode(const QList<ExternalToolsTreeNode * > & nodeList,const QString & objectName)184 static ExternalToolsTreeNode *findNode(const QList<ExternalToolsTreeNode *> &nodeList, const QString &objectName) {
185 for (const auto &node : qAsConst(nodeList)) {
186 if (node->objectName() == objectName) {
187 return node;
188 }
189 }
190 return nullptr;
191 }
192
193 #define MAX_SAME_LEVEL_NODES 100
194 #define MAX_OUTPUT_CONTENT_SIZE 100000
195
addLogEntry(const Monitor::LogEntry & entry)196 void ExternalToolsDashboardWidget::addLogEntry(const Monitor::LogEntry &entry) {
197 SAFE_POINT(monitor != nullptr, "WorkflowMonitor instance is null!", );
198 QString newLine = QString(entry.lastLine)
199 .replace("<", "<")
200 .replace(">", ">")
201 .replace("\n", "<br/>")
202 .replace("\r", "");
203
204 QString actorNodeId = "actor_" + entry.actorId;
205 ExternalToolsTreeNode *actorNode = findNode(topLevelNodes, actorNodeId);
206 if (actorNode == nullptr) {
207 if (topLevelNodes.size() >= MAX_SAME_LEVEL_NODES) {
208 addLimitationWarning();
209 return;
210 }
211 actorNode = addNodeToLayout(new ExternalToolsTreeNode(NODE_KIND_ACTOR, actorNodeId, entry.actorName, nullptr));
212 topLevelNodes << actorNode;
213 }
214
215 QString toolNodeId = actorNodeId + "_run_" + QString::number(entry.actorRunNumber);
216 ExternalToolsTreeNode *toolNode = findNode(actorNode->children, toolNodeId);
217 if (toolNode == nullptr) {
218 if (actorNode->children.size() > MAX_SAME_LEVEL_NODES) {
219 addLimitationWarning(actorNode);
220 return;
221 }
222 QString toolNodeText = entry.actorName + " run " + QString::number(entry.actorRunNumber);
223 toolNode = addNodeToLayout(new ExternalToolsTreeNode(NODE_KIND_TOOL, toolNodeId, toolNodeText, actorNode));
224 }
225
226 bool isImportant = entry.contentType == 0;
227 QString runNodeId = toolNodeId + "_tool_" + entry.toolName + "_run_" + QString::number(entry.toolRunNumber);
228 ExternalToolsTreeNode *runNode = findNode(toolNode->children, runNodeId);
229 if (runNode == nullptr) {
230 if (toolNode->children.size() > MAX_SAME_LEVEL_NODES) {
231 addLimitationWarning(toolNode);
232 return;
233 }
234 QString runNodeText = entry.toolName + " run" + (entry.toolRunNumber > 1 ? " " + QString::number(entry.toolRunNumber) : "");
235 runNode = addNodeToLayout(new ExternalToolsTreeNode(NODE_KIND_RUN, runNodeId, runNodeText, toolNode, isImportant));
236 } else if (!runNode->isImportant && isImportant) {
237 runNode->isImportant = true;
238 runNode->badgeLabel->switchToImportantStyle();
239 }
240
241 QString outputNodeId = toolNodeId + (entry.contentType == 0 ? "_stderr" : (entry.contentType == 1 ? "_stdout" : "_command"));
242 int outputNodeKind = entry.contentType == 2 ? NODE_KIND_COMMAND : NODE_KIND_OUTPUT;
243 ExternalToolsTreeNode *outputNode = findNode(runNode->children, outputNodeId);
244 QString outputNodeText = entry.contentType == 0 ? "Output log (stderr)" : (entry.contentType == 1 ? "Output log (stdout)" : "Command");
245 if (outputNode == nullptr) {
246 outputNode = addNodeToLayout(new ExternalToolsTreeNode(outputNodeKind, outputNodeId, outputNodeText, runNode, isImportant));
247 }
248
249 QString outputContentNodeId = outputNodeId + "_content";
250 ExternalToolsTreeNode *outputContentNode = findNode(outputNode->children, outputContentNodeId);
251 if (outputContentNode == nullptr) {
252 outputContentNode = addNodeToLayout(new ExternalToolsTreeNode(NODE_KIND_LOG_CONTENT, outputContentNodeId, "", outputNode));
253 }
254
255 if (!outputContentNode->isLogFull) {
256 QString logLine = newLine;
257 if (outputContentNode->content.length() + newLine.length() > MAX_OUTPUT_CONTENT_SIZE) {
258 outputContentNode->isLogFull = true;
259 QString logUrl = monitor->getLogUrl(entry.actorId, entry.actorRunNumber, entry.toolName, entry.toolRunNumber, entry.contentType);
260 logLine = "<br/><br/>The external tool output is too large and can't be visualized on the dashboard. Find full output in <a href=\"" + logUrl + "\">log file</a>.";
261 }
262 outputContentNode->content.append(logLine);
263 outputContentNode->badgeLabel->logView->setHtml("<code>" + outputContentNode->content + "</code>");
264 }
265 }
266
addNodeToLayout(ExternalToolsTreeNode * node)267 ExternalToolsTreeNode *ExternalToolsDashboardWidget::addNodeToLayout(ExternalToolsTreeNode *node) {
268 if (node->parent == nullptr) {
269 layout->addWidget(node);
270 } else {
271 auto prevNode = node->parent->getLastChildInHierarchyOrSelf();
272 int prevIndex = layout->indexOf(prevNode);
273 layout->insertWidget(prevIndex, node);
274 }
275 return node;
276 }
277
278 #define TREE_NODE_X_OFFSET 50
279
getLevelByNodeKind(int kind)280 static int getLevelByNodeKind(int kind) {
281 switch (kind) {
282 case NODE_KIND_ACTOR:
283 return 0;
284 case NODE_KIND_TOOL:
285 return 1;
286 case NODE_KIND_RUN:
287 return 2;
288 case NODE_KIND_COMMAND:
289 case NODE_KIND_OUTPUT:
290 return 3;
291 case NODE_KIND_LOG_CONTENT:
292 return 4;
293 }
294 SAFE_POINT(false, "Unknown kind: " + QString::number(kind), 0);
295 }
296
ExternalToolsTreeNode(int kind,const QString & objectName,const QString & content,ExternalToolsTreeNode * parent,bool isImportant)297 ExternalToolsTreeNode::ExternalToolsTreeNode(int kind, const QString &objectName, const QString &content, ExternalToolsTreeNode *parent, bool isImportant)
298 : kind(kind), parent(parent), content(content), isImportant(isImportant), isLogFull(false), badgeLabel(nullptr) {
299 Q_ASSERT(!objectName.isEmpty());
300 setObjectName(objectName);
301 if (parent != nullptr) {
302 parent->children << this;
303 }
304 setContentsMargins(0, 5, 0, 5);
305 auto layout = new QHBoxLayout();
306 layout->setMargin(0);
307 layout->setSpacing(0);
308 setLayout(layout);
309
310 int level = getLevelByNodeKind(kind);
311 layout->addSpacing(level * TREE_NODE_X_OFFSET);
312 badgeLabel = new BadgeLabel(kind, content, isImportant);
313 layout->addWidget(badgeLabel);
314
315 if (badgeLabel->titleLabel) {
316 connect(badgeLabel->titleLabel, SIGNAL(clicked()), SLOT(sl_toggle()));
317 }
318
319 if (badgeLabel->copyButton && kind == NODE_KIND_RUN) {
320 connect(badgeLabel->copyButton, SIGNAL(clicked()), SLOT(sl_copyRunCommand()));
321 }
322
323 setVisible(level <= getLevelByNodeKind(NODE_KIND_RUN));
324 }
325
isExpanded() const326 bool ExternalToolsTreeNode::isExpanded() const {
327 return children.size() > 0 && children.first()->isVisible();
328 }
329
getLastChildInHierarchyOrSelf()330 ExternalToolsTreeNode *ExternalToolsTreeNode::getLastChildInHierarchyOrSelf() {
331 return children.isEmpty() ? this : children.last()->getLastChildInHierarchyOrSelf();
332 }
333
sl_toggle()334 void ExternalToolsTreeNode::sl_toggle() {
335 bool isExpandedBefore = isExpanded();
336 bool isExpandedAfter = !isExpandedBefore;
337
338 // Auto-expand command & output nodes when RUN node is clicked.
339 bool expandAllChildren = isExpandedAfter && getLevelByNodeKind(kind) >= getLevelByNodeKind(NODE_KIND_RUN);
340
341 for (auto child : qAsConst(children)) {
342 child->updateExpandCollapseState(isExpandedAfter, expandAllChildren);
343 }
344 }
345
updateExpandCollapseState(bool isParentExpanded,bool isApplyToAllLevelOfChildren)346 void ExternalToolsTreeNode::updateExpandCollapseState(bool isParentExpanded, bool isApplyToAllLevelOfChildren) {
347 this->setVisible(isParentExpanded);
348 if (!isParentExpanded) { // make all children invisible (we use flat VBOX layout model for the tree, so parent must hide children manually).
349 for (auto child : qAsConst(children)) {
350 child->updateExpandCollapseState(false);
351 }
352 } else if (isApplyToAllLevelOfChildren) { // make children on all levels visible.
353 for (auto child : qAsConst(children)) {
354 child->updateExpandCollapseState(true, true);
355 }
356 }
357 }
358
sl_copyRunCommand()359 void ExternalToolsTreeNode::sl_copyRunCommand() {
360 if (kind == NODE_KIND_RUN && !children.isEmpty() && !children[0]->children.isEmpty()) {
361 QApplication::clipboard()->setText(children[0]->children[0]->content);
362 }
363 }
364
isLastChild(const ExternalToolsTreeNode * node)365 static bool isLastChild(const ExternalToolsTreeNode *node) {
366 return node != nullptr && node->parent != nullptr && node->parent->children.last() == node;
367 }
368
369 #define BRANCH_X_PADDING 15
370
paintEvent(QPaintEvent * event)371 void ExternalToolsTreeNode::paintEvent(QPaintEvent *event) {
372 QWidget::paintEvent(event);
373 if (width() == 0 || height() == 0) {
374 return;
375 }
376
377 QPainter painter(this);
378 painter.setPen(QPen(QBrush(QColor("#999999")), 1));
379
380 for (const ExternalToolsTreeNode *node = this; node != nullptr; node = node->parent) {
381 int level = getLevelByNodeKind(node->kind);
382 int x = (level - 1) * TREE_NODE_X_OFFSET + BRANCH_X_PADDING;
383 if (node == this) {
384 int horizontalLineY = height() / 2;
385 if (node->kind != NODE_KIND_ACTOR) {
386 painter.drawLine(x, 0, x, isLastChild(node) ? horizontalLineY : height()); // vertical line from from the parent to the Y-center.
387 painter.drawLine(x, horizontalLineY, x + TREE_NODE_X_OFFSET - 5, horizontalLineY); // horizontal line to the node.
388 }
389 if (!children.isEmpty() && isExpanded()) { // part of the link to the first child.
390 int childX = level * TREE_NODE_X_OFFSET + BRANCH_X_PADDING;
391 painter.drawLine(childX, horizontalLineY, childX, height());
392 }
393 } else if (!isLastChild(node)) {
394 painter.drawLine(x, 0, x, height());
395 }
396 }
397 }
398
getSpanClass() const399 QString ExternalToolsTreeNode::getSpanClass() const {
400 QString result = NODE_CLASS_BADGE + (isImportant ? " " + NODE_CLASS_IMPORTANT : "");
401 switch (kind) {
402 case NODE_KIND_ACTOR:
403 return result + " " + NODE_CLASS_ACTOR;
404 case NODE_KIND_TOOL:
405 return result + " " + NODE_CLASS_TOOL;
406 case NODE_KIND_RUN:
407 return result + " " + NODE_CLASS_RUN;
408 case NODE_KIND_LOG_CONTENT:
409 return result + " " + NODE_CLASS_CONTENT;
410 }
411 return result;
412 }
413
toHtml() const414 QString ExternalToolsTreeNode::toHtml() const {
415 QString html = "<li>\n";
416 html += "<span id=\"" + objectName() + "\" class=\"" + getSpanClass() + "\">" + content + "</span>\n";
417 if (!children.isEmpty()) {
418 html += "<ul>\n";
419 for (auto child : qAsConst(children)) {
420 html += child->toHtml();
421 }
422 html += "</ul>\n";
423 }
424 if (!limitationWarningHtml.isEmpty()) {
425 html += "<li><span class=\"badge limitation-message\">" + limitationWarningHtml + "</span></li>\n";
426 }
427 html += "</li>\n";
428 return html;
429 }
430
431 #define RUN_NODE_NORMAL_COLOR "#50A976"
432 #define RUN_NODE_IMPORTANT_COLOR "#CC6666"
433
getBadgeLabelStyle(int kind,bool isImportant)434 static QString getBadgeLabelStyle(int kind, bool isImportant) {
435 QString style = "border-radius: 6px; padding: 2px 4px; color: white;";
436 switch (kind) {
437 case NODE_KIND_ACTOR:
438 return style + "background-color: #92939E;";
439 case NODE_KIND_TOOL:
440 return style + "background-color: #bdb0a0;";
441 case NODE_KIND_RUN:
442 return style + QString("background-color: ") + (isImportant ? RUN_NODE_IMPORTANT_COLOR : RUN_NODE_NORMAL_COLOR) + ";";
443 case NODE_KIND_COMMAND:
444 return style + "background-color: #79ACAC;";
445 case NODE_KIND_OUTPUT:
446 return style + QString("background-color: ") + (isImportant ? RUN_NODE_IMPORTANT_COLOR : "#6699CC") + ";";
447 case NODE_KIND_LOG_CONTENT:
448 return style + "font-size: 16px; background-color: #F0F0F0; color: black;";
449 }
450 return style;
451 };
452
BadgeLabel(int kind,const QString & text,bool isImportant)453 BadgeLabel::BadgeLabel(int kind, const QString &text, bool isImportant)
454 : kind(kind), titleLabel(nullptr), copyButton(nullptr), logView(nullptr) {
455 auto layout = new QHBoxLayout();
456 layout->setMargin(0);
457 layout->setSpacing(0);
458 setLayout(layout);
459
460 QString style = getBadgeLabelStyle(kind, isImportant);
461 bool isCopyButtonVisible = kind == NODE_KIND_RUN;
462 QString finalStyle = isCopyButtonVisible ? style + ";border-top-right-radius: 0; border-bottom-right-radius: 0;" : style;
463 if (kind == NODE_KIND_LOG_CONTENT) {
464 logView = new QTextBrowser();
465 logView->setStyleSheet("QTextBrowser {" + finalStyle + "}");
466 logView->setTextInteractionFlags(Qt::TextBrowserInteraction);
467 logView->setContextMenuPolicy(Qt::DefaultContextMenu);
468 logView->setOpenExternalLinks(true);
469 logView->setMinimumHeight(qBound(56, 30 * qMax(text.count("\n"), text.size() / 84), 400));
470 logView->setMaximumHeight(800);
471 logView->setHtml("<code>" + text + "</code>");
472 layout->addWidget(logView);
473 } else {
474 titleLabel = new HoverQLabel(text, "QLabel {" + finalStyle + "}", "QLabel {" + finalStyle + "; color: black;}");
475 layout->addWidget(titleLabel);
476 }
477
478 if (isCopyButtonVisible) {
479 QString copyButtonStyle = style + ";border-top-left-radius: 0; border-bottom-left-radius: 0; border-left: 1px solid #eee;";
480 copyButton = new HoverQLabel("", "QLabel {" + copyButtonStyle + "}", "QLabel {" + copyButtonStyle + "; color: black; background: #777;}");
481 copyButton->setPixmap(QPixmap(":U2Designer/images/copy.png"));
482 copyButton->setObjectName("copyButton");
483 copyButton->setToolTip(tr("Copy command line"));
484 layout->addWidget(copyButton);
485 }
486 if (kind != NODE_KIND_LOG_CONTENT) {
487 layout->addStretch(1);
488 }
489 }
490
switchToImportantStyle()491 void BadgeLabel::switchToImportantStyle() {
492 CHECK(kind == NODE_KIND_RUN, );
493 titleLabel->normalStyle = titleLabel->normalStyle.replace(RUN_NODE_NORMAL_COLOR, RUN_NODE_IMPORTANT_COLOR);
494 titleLabel->hoveredStyle = titleLabel->hoveredStyle.replace(RUN_NODE_NORMAL_COLOR, RUN_NODE_IMPORTANT_COLOR);
495 titleLabel->setStyleSheet(titleLabel->normalStyle);
496 copyButton->setStyleSheet(copyButton->styleSheet().replace(RUN_NODE_NORMAL_COLOR, RUN_NODE_IMPORTANT_COLOR));
497 }
498
499 } // namespace U2
500