GCC Code Coverage Report


Directory: ./
File: src/hpp-manipulation-graph.cc
Date: 2024-12-13 15:45:52
Exec Total Coverage
Lines: 0 342 0.0%
Branches: 0 776 0.0%

Line Branch Exec Source
1 // BSD 2-Clause License
2
3 // Copyright (c) 2015 - 2018, hpp-plot
4 // Authors: Heidy Dallard, Joseph Mirabel
5 // All rights reserved.
6
7 // Redistribution and use in source and binary forms, with or without
8 // modification, are permitted provided that the following conditions
9 // are met:
10
11 // * Redistributions of source code must retain the above copyright
12 // notice, this list of conditions and the following disclaimer.
13
14 // * Redistributions in binary form must reproduce the above copyright
15 // notice, this list of conditions and the following disclaimer in
16 // the documentation and/or other materials provided with the
17 // distribution.
18
19 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 // "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 // LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
22 // FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
23 // COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
24 // INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
26 // SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
27 // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
28 // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
29 // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
30 // OF THE POSSIBILITY OF SUCH DAMAGE.
31
32 #include "hpp/plot/hpp-manipulation-graph.hh"
33
34 #include <QGVEdge.h>
35 #include <QGVNode.h>
36 #include <QtGui/qtextdocument.h>
37 #include <assert.h>
38
39 #include <QDebug>
40 #include <QDir>
41 #include <QInputDialog>
42 #include <QLayout>
43 #include <QMap>
44 #include <QMenu>
45 #include <QMessageBox>
46 #include <QPushButton>
47 #include <QTemporaryFile>
48 #include <QTimer>
49 #include <iostream>
50 #include <limits>
51
52 #if (QT_VERSION < QT_VERSION_CHECK(5, 0, 0))
53 #define ESCAPE(q) Qt::escape(q)
54 #else
55 #define ESCAPE(q) q.toHtmlEscaped()
56 #endif
57
58 namespace hpp {
59 namespace plot {
60 namespace {
61 void initConfigProjStat(::hpp::ConfigProjStat& p) {
62 p.success = 0;
63 p.error = 0;
64 p.nbObs = 0;
65 }
66 } // namespace
67 GraphAction::GraphAction(HppManipulationGraphWidget* parent)
68 : QAction(parent), gw_(parent) {
69 connect(this, SIGNAL(triggered()), SLOT(transferSignal()));
70 }
71
72 void GraphAction::transferSignal() {
73 hpp::ID id;
74 if (gw_->selectionID(id)) emit activated(id);
75 }
76
77 HppManipulationGraphWidget::HppManipulationGraphWidget(
78 corbaServer::manipulation::Client* hpp_, QWidget* parent)
79 : GraphWidget("Manipulation graph", parent),
80 manip_(hpp_),
81 showWaypoints_(new QPushButton(QIcon::fromTheme("view-refresh"),
82 "&Show waypoints", buttonBox_)),
83 statButton_(new QPushButton(QIcon::fromTheme("view-refresh"),
84 "&Statistics", buttonBox_)),
85 updateStatsTimer_(new QTimer(this)),
86 currentId_(-1),
87 showNodeId_(-1),
88 showEdgeId_(-1) {
89 graphInfo_.id = -1;
90 statButton_->setCheckable(true);
91 showWaypoints_->setCheckable(true);
92 showWaypoints_->setChecked(false);
93 buttonBox_->layout()->addWidget(statButton_);
94 buttonBox_->layout()->addWidget(showWaypoints_);
95 updateStatsTimer_->setInterval(1000);
96 updateStatsTimer_->setSingleShot(false);
97
98 connect(updateStatsTimer_, SIGNAL(timeout()), SLOT(updateStatistics()));
99 connect(statButton_, SIGNAL(clicked(bool)), SLOT(startStopUpdateStats(bool)));
100 connect(scene_, SIGNAL(selectionChanged()), SLOT(selectionChanged()));
101 }
102
103 HppManipulationGraphWidget::~HppManipulationGraphWidget() {
104 qDeleteAll(nodeContextMenuActions_);
105 qDeleteAll(edgeContextMenuActions_);
106 delete updateStatsTimer_;
107 }
108
109 void HppManipulationGraphWidget::addNodeContextMenuAction(GraphAction* action) {
110 nodeContextMenuActions_.append(action);
111 }
112
113 void HppManipulationGraphWidget::addEdgeContextMenuAction(GraphAction* action) {
114 edgeContextMenuActions_.append(action);
115 }
116
117 void HppManipulationGraphWidget::client(
118 corbaServer::manipulation::Client* hpp) {
119 manip_ = hpp;
120 }
121
122 bool hpp::plot::HppManipulationGraphWidget::selectionID(ID& id) {
123 id = currentId_;
124 return currentId_ != -1;
125 }
126
127 void HppManipulationGraphWidget::fillScene() {
128 if (manip_ == NULL) return;
129 hpp::GraphComp_var graph = new hpp::GraphComp;
130 hpp::GraphElements_var elmts = new hpp::GraphElements;
131 try {
132 manip_->graph()->getGraph(graph.out(), elmts.out());
133
134 graphName_ = graph->name;
135 scene_->setGraphAttribute("label", QString(graph->name));
136
137 scene_->setGraphAttribute("splines", "spline");
138 // scene_->setGraphAttribute("rankdir", "LR");
139 scene_->setGraphAttribute("outputorder", "edgesfirst");
140 scene_->setGraphAttribute("nodesep", "0.5");
141 scene_->setGraphAttribute("esep", "0.8");
142 scene_->setGraphAttribute("sep", "1");
143
144 scene_->setNodeAttribute("shape", "circle");
145 scene_->setNodeAttribute("style", "filled");
146 scene_->setNodeAttribute("fillcolor", "white");
147 // scene_->setNodeAttribute("height", "1.2");
148 // scene_->setEdgeAttribute("minlen", "3");
149
150 graphInfo_.id = graph->id;
151 graphInfo_.constraintStr = getConstraints(graphInfo_.id);
152
153 // Add the nodes
154 nodes_.clear();
155 edges_.clear();
156 bool hideW = !showWaypoints_->isChecked();
157 QMap<hpp::ID, bool> nodeIsWaypoint;
158 QMap<hpp::ID, bool> edgeVisible;
159
160 // initialize node counters.
161 for (std::size_t i = 0; i < elmts->nodes.length(); ++i) {
162 if (elmts->nodes[(CORBA::ULong)i].id > graph->id) {
163 ::hpp::ID id = elmts->nodes[(CORBA::ULong)i].id;
164 nodeIsWaypoint[id] = false;
165 }
166 }
167
168 // Find what edge are visible and count the nodes accordingly.
169 for (std::size_t i = 0; i < elmts->edges.length(); ++i) {
170 if (elmts->edges[(CORBA::ULong)i].id > graph->id) {
171 ::hpp::ID id = elmts->edges[(CORBA::ULong)i].id;
172 ::CORBA::Long weight = manip_->graph()->getWeight(id);
173
174 for (std::size_t k = 0;
175 k < elmts->edges[(CORBA::ULong)i].waypoints.length(); ++k)
176 nodeIsWaypoint[elmts->edges[(CORBA::ULong)i]
177 .waypoints[(CORBA::ULong)k]] = true;
178
179 bool hasWaypoints =
180 elmts->edges[(CORBA::ULong)i].waypoints.length() > 0;
181 // If show Waypoint and this is not a waypoint edge
182 // or hide Waypoint and this is not a transition inside a
183 // WaypointEdge
184 edgeVisible[id] = (!hideW && !hasWaypoints) || (hideW && weight >= 0);
185 }
186 }
187
188 for (std::size_t i = 0; i < elmts->nodes.length(); ++i) {
189 if (elmts->nodes[(CORBA::ULong)i].id > graph->id) {
190 Q_ASSERT(nodeIsWaypoint.contains(elmts->nodes[(CORBA::ULong)i].id));
191
192 if (hideW && nodeIsWaypoint[elmts->nodes[(CORBA::ULong)i].id]) {
193 qDebug() << "Ignoring node" << elmts->nodes[(CORBA::ULong)i].name;
194 continue;
195 }
196 QString nodeName(elmts->nodes[(CORBA::ULong)i].name);
197 nodeName.replace(" : ", "\n");
198 QGVNode* n = scene_->addNode(nodeName);
199 if (i == 0) scene_->setRootNode(n);
200 NodeInfo ni;
201 ni.id = elmts->nodes[(CORBA::ULong)i].id;
202 ni.node = n;
203 ni.constraintStr = getConstraints(ni.id);
204 nodeInfos_[n] = ni;
205 n->setFlag(QGraphicsItem::ItemIsMovable, true);
206 n->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
207 nodes_[elmts->nodes[(CORBA::ULong)i].id] = n;
208
209 if (nodeIsWaypoint[ni.id]) n->setAttribute("shape", "hexagon");
210 }
211 }
212 for (std::size_t i = 0; i < elmts->edges.length(); ++i) {
213 if (elmts->edges[(CORBA::ULong)i].id > graph->id) {
214 Q_ASSERT(edgeVisible.contains(elmts->edges[(CORBA::ULong)i].id) &&
215 nodeIsWaypoint.contains(elmts->edges[(CORBA::ULong)i].start) &&
216 nodeIsWaypoint.contains(elmts->edges[(CORBA::ULong)i].end));
217 if (!edgeVisible[elmts->edges[(CORBA::ULong)i].id]) {
218 qDebug() << "Ignoring edge" << elmts->edges[(CORBA::ULong)i].name;
219 continue;
220 }
221 EdgeInfo ei;
222 QGVEdge* e =
223 scene_->addEdge(nodes_[elmts->edges[(CORBA::ULong)i].start],
224 nodes_[elmts->edges[(CORBA::ULong)i].end], "");
225 ei.name = QString::fromLocal8Bit(elmts->edges[(CORBA::ULong)i].name);
226 ei.id = elmts->edges[(CORBA::ULong)i].id;
227 CORBA::String_var cnname = manip_->graph()->getContainingNode(ei.id);
228 ei.containingNodeName = QString::fromLocal8Bit((char*)cnname);
229 ei.edge = e;
230 updateWeight(ei, true);
231
232 if (elmts->edges[(CORBA::ULong)i].waypoints.length() > 0) {
233 ei.constraintStr =
234 tr("<p><h4>Waypoint transition</h4>"
235 "This transition has %1 waypoints.<br/>"
236 "To see the constraints of the transition inside,<br/>"
237 "re-draw the graph after enabling \"Show waypoints\"</p>")
238 .arg(elmts->edges[(CORBA::ULong)i].waypoints.length());
239 ei.shortStr = "";
240 } else {
241 ei.constraintStr = getConstraints(ei.id);
242
243 if (manip_->graph()->isShort(ei.id)) ei.shortStr = "<h4>Short</h4>";
244 }
245
246 // If this is a transition inside a WaypointEdge
247 if (ei.weight < 0) {
248 e->setAttribute("weight", "3");
249 if (elmts->edges[(CORBA::ULong)i].start >=
250 elmts->edges[(CORBA::ULong)i].end)
251 e->setAttribute("constraint", "false");
252 }
253
254 edgeInfos_[e] = ei;
255 edges_[ei.id] = e;
256 }
257 }
258 } catch (const hpp::Error& e) {
259 qDebug() << e.msg;
260 }
261 }
262
263 void HppManipulationGraphWidget::updateStatistics() {
264 if (manip_ == NULL) {
265 updateStatsTimer_->stop();
266 statButton_->setChecked(false);
267 return;
268 }
269 try {
270 foreach (QGraphicsItem* elmt, scene_->items()) {
271 QGVNode* node = dynamic_cast<QGVNode*>(elmt);
272 if (node) {
273 NodeInfo& ni = nodeInfos_[node];
274 manip_->graph()->getConfigProjectorStats(ni.id, ni.configStat,
275 ni.pathStat);
276 ni.freq = manip_->graph()->getFrequencyOfNodeInRoadmap(
277 ni.id, ni.freqPerCC.out());
278 float sr = (ni.configStat.nbObs > 0) ? (float)ni.configStat.success /
279 (float)ni.configStat.nbObs
280 : 0.f / 0.f;
281 QString colorcode =
282 (ni.configStat.nbObs > 0)
283 ? QColor(255, (int)(sr * 255), (int)(sr * 255)).name()
284 : "white";
285 const QString& fillcolor = node->getAttribute("fillcolor");
286 if (!(fillcolor == colorcode)) {
287 node->setAttribute("fillcolor", colorcode);
288 node->updateLayout();
289 }
290 continue;
291 }
292 QGVEdge* edge = dynamic_cast<QGVEdge*>(elmt);
293 if (edge) {
294 EdgeInfo& ei = edgeInfos_[edge];
295 manip_->graph()->getConfigProjectorStats(ei.id, ei.configStat,
296 ei.pathStat);
297 manip_->graph()->getEdgeStat(ei.id, ei.errors.out(), ei.freqs.out());
298 float sr = (ei.configStat.nbObs > 0) ? (float)ei.configStat.success /
299 (float)ei.configStat.nbObs
300 : 0.f / 0.f;
301 QString colorcode = (ei.configStat.nbObs > 0)
302 ? QColor(255 - (int)(sr * 255), 0, 0).name()
303 : "";
304 const QString& color = edge->getAttribute("color");
305 if (!(color == colorcode)) {
306 edge->setAttribute("color", colorcode);
307 edge->updateLayout();
308 }
309 continue;
310 }
311 }
312 scene_->update();
313 selectionChanged();
314 } catch (const CORBA::Exception&) {
315 updateStatsTimer_->stop();
316 statButton_->setChecked(false);
317 throw;
318 }
319 }
320
321 void HppManipulationGraphWidget::showNodeOfConfiguration(
322 const hpp::floatSeq& cfg) {
323 static bool lastlog = false;
324 if (manip_ == NULL) return;
325 if (showNodeId_ >= 0) {
326 // Do unselect
327 nodes_[showNodeId_]->setAttribute("fillcolor", "white");
328 nodes_[showNodeId_]->updateLayout();
329 }
330 try {
331 manip_->graph()->getNode(cfg, showNodeId_);
332 // Do select
333 if (nodes_.contains(showNodeId_)) {
334 nodes_[showNodeId_]->setAttribute("fillcolor", "green");
335 nodes_[showNodeId_]->updateLayout();
336 scene_->update();
337 } else {
338 qDebug() << "Node" << showNodeId_
339 << "does not exist. Refer the graph may solve the issue.";
340 showNodeId_ = -1;
341 }
342 lastlog = false;
343 } catch (const hpp::Error& e) {
344 if (!lastlog)
345 qDebug() << "HppManipulationGraphWidget::showNodeOfConfiguration"
346 << e.msg;
347 lastlog = true;
348 }
349 }
350
351 void HppManipulationGraphWidget::showEdge(const hpp::ID& edgeId) {
352 if (showEdgeId_ >= 0) {
353 // Do unselect
354 edges_[showEdgeId_]->setAttribute("color", "");
355 edges_[showEdgeId_]->updateLayout();
356 }
357 showEdgeId_ = edgeId;
358 // Do select
359 if (edges_.contains(showEdgeId_)) {
360 edges_[showEdgeId_]->setAttribute("color", "green");
361 edges_[showEdgeId_]->updateLayout();
362 scene_->update();
363 } else {
364 showEdgeId_ = -1;
365 }
366 }
367
368 void HppManipulationGraphWidget::nodeContextMenu(QGVNode* node) {
369 const NodeInfo& ni = nodeInfos_[node];
370 hpp::ID id = currentId_;
371 currentId_ = ni.id;
372
373 QMenu cm("Node context menu", this);
374 foreach (GraphAction* action, nodeContextMenuActions_) {
375 cm.addAction(action);
376 }
377 cm.exec(QCursor::pos());
378
379 currentId_ = id;
380 }
381
382 void HppManipulationGraphWidget::nodeDoubleClick(QGVNode* node) {
383 const NodeInfo& ni = nodeInfos_[node];
384 displayNodeConstraint(ni.id);
385 }
386
387 void HppManipulationGraphWidget::displayNodeConstraint(hpp::ID id) {
388 if (manip_ == NULL) return;
389 CORBA::String_var str;
390 manip_->graph()->displayNodeConstraints(id, str.out());
391 QString nodeStr(str);
392 constraintInfo_->setText(nodeStr);
393 }
394
395 void HppManipulationGraphWidget::displayEdgeConstraint(hpp::ID id) {
396 if (manip_ == NULL) return;
397 CORBA::String_var str;
398 manip_->graph()->displayEdgeConstraints(id, str.out());
399 QString nodeStr(str);
400 constraintInfo_->setText(nodeStr);
401 }
402
403 void HppManipulationGraphWidget::displayEdgeTargetConstraint(hpp::ID id) {
404 if (manip_ == NULL) return;
405 CORBA::String_var str;
406 manip_->graph()->displayEdgeTargetConstraints(id, str.out());
407 QString nodeStr(str);
408 constraintInfo_->setText(nodeStr);
409 }
410
411 void HppManipulationGraphWidget::edgeContextMenu(QGVEdge* edge) {
412 const EdgeInfo& ei = edgeInfos_[edge];
413 hpp::ID id = currentId_;
414 currentId_ = ei.id;
415
416 QMenu cm("Edge context menu", this);
417 foreach (GraphAction* action, edgeContextMenuActions_) {
418 cm.addAction(action);
419 }
420 cm.exec(QCursor::pos());
421
422 currentId_ = id;
423 }
424
425 void HppManipulationGraphWidget::edgeDoubleClick(QGVEdge* edge) {
426 EdgeInfo& ei = edgeInfos_[edge];
427 bool ok;
428 ::CORBA::Long w = QInputDialog::getInt(
429 this, "Update edge weight", tr("Edge %1 weight").arg(ei.name), ei.weight,
430 0, std::numeric_limits<int>::max(), 1, &ok);
431 if (ok) {
432 updateWeight(ei, w);
433 edge->updateLayout();
434 }
435 }
436
437 void HppManipulationGraphWidget::selectionChanged() {
438 QList<QGraphicsItem*> items = scene_->selectedItems();
439 currentId_ = -1;
440
441 QString type, name;
442 ::hpp::ID id;
443 QString end;
444 QString constraints;
445 QString weight;
446
447 if (items.size() == 0) {
448 if (graphInfo_.id < 0) {
449 elmtInfo_->setText("No info");
450 return;
451 }
452 type = "Graph";
453 id = graphInfo_.id;
454 constraints = graphInfo_.constraintStr;
455 } else if (items.size() == 1) {
456 QGVNode* node = dynamic_cast<QGVNode*>(items.first());
457 QGVEdge* edge = dynamic_cast<QGVEdge*>(items.first());
458 if (node) {
459 type = "Node";
460 name = node->label();
461 const NodeInfo& ni = nodeInfos_[node];
462 id = ni.id;
463 currentId_ = id;
464 constraints = ni.constraintStr;
465 end = QString("<p><h4>Nb node in roadmap:</h4> %1</p>").arg(ni.freq);
466 end.append("<p><h4>Nb node in roadmap per connected component</h4>\n");
467 for (std::size_t i = 0; i < ni.freqPerCC->length(); ++i) {
468 end.append(QString(" %1,").arg(ni.freqPerCC.in()[(CORBA::ULong)i]));
469 }
470 end.append("</p>");
471 } else if (edge) {
472 type = "Edge";
473 const EdgeInfo& ei = edgeInfos_[edge];
474 name = ei.name;
475 id = ei.id;
476 currentId_ = id;
477 weight = QString("<li>Weight: %1</li>").arg(ei.weight);
478 end = "<p>Extension results<ul>";
479 for (std::size_t i = 0;
480 i < std::min(ei.errors->length(), ei.freqs->length()); ++i) {
481 end.append(QString("<li>%1: %2</li>")
482 .arg(QString(ei.errors.in()[(CORBA::ULong)i]))
483 .arg(ei.freqs.in()[(CORBA::ULong)i]));
484 }
485 end.append("</ul></p>");
486 end.append(QString("<p><h4>Containing node</h4>\n%1</p>")
487 .arg(ei.containingNodeName));
488 constraints = ei.constraintStr;
489 } else {
490 return;
491 }
492 }
493 elmtInfo_->setText(QString("<h4>%1 %2</h4><ul>"
494 "<li>Id: %3</li>"
495 "%4"
496 "</ul>%5%6")
497 .arg(type)
498 .arg(ESCAPE(name))
499 .arg(id)
500 .arg(weight)
501 .arg(end)
502 .arg(constraints));
503 }
504
505 void HppManipulationGraphWidget::startStopUpdateStats(bool start) {
506 if (start)
507 updateStatsTimer_->start();
508 else
509 updateStatsTimer_->stop();
510 }
511
512 HppManipulationGraphWidget::NodeInfo::NodeInfo()
513 : id(-1), freq(0), freqPerCC(new ::hpp::intSeq()) {
514 initConfigProjStat(configStat);
515 initConfigProjStat(pathStat);
516 }
517
518 HppManipulationGraphWidget::EdgeInfo::EdgeInfo() : id(-1), edge(NULL) {
519 initConfigProjStat(configStat);
520 initConfigProjStat(pathStat);
521 errors = new Names_t();
522 freqs = new intSeq();
523 }
524
525 void HppManipulationGraphWidget::updateWeight(EdgeInfo& ei, bool get) {
526 if (manip_ == NULL) return;
527 if (get) ei.weight = manip_->graph()->getWeight(ei.id);
528 if (ei.edge == NULL) return;
529 if (ei.weight <= 0) {
530 ei.edge->setAttribute("style", "dashed");
531 ei.edge->setAttribute("penwidth", "1");
532 } else {
533 ei.edge->setAttribute("style", "filled");
534 ei.edge->setAttribute("penwidth", QString::number(1 + (ei.weight - 1 / 5)));
535 }
536 }
537
538 void HppManipulationGraphWidget::updateWeight(EdgeInfo& ei,
539 const ::CORBA::Long w) {
540 if (manip_ == NULL) return;
541 manip_->graph()->setWeight(ei.id, w);
542 ei.weight = w;
543 updateWeight(ei, false);
544 }
545
546 QString HppManipulationGraphWidget::getConstraints(hpp::ID id) {
547 assert(manip_ != NULL);
548 QString ret;
549 hpp::Names_t_var c = new hpp::Names_t;
550 manip_->graph()->getNumericalConstraints(id, c);
551 ret.append("<p><h4>Applied constraints</h4>");
552 if (c->length() > 0) {
553 ret.append("<ul>");
554 for (unsigned i = 0; i < c->length(); i++) {
555 ret.append(QString("<li>%1</li>").arg(c[i].in()));
556 }
557 ret.append("</ul></p>");
558 } else
559 ret.append("No constraints applied</p>");
560 return ret;
561 }
562 } // namespace plot
563 } // namespace hpp
564