VertexTableWidget.cpp
Go to the documentation of this file.
1/*
2 * This file is part of ArmarX.
3 *
4 * ArmarX is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License version 2 as
6 * published by the Free Software Foundation.
7 *
8 * ArmarX is distributed in the hope that it will be useful, but
9 * WITHOUT ANY WARRANTY; without even the implied warranty of
10 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 * GNU General Public License for more details.
12 *
13 * You should have received a copy of the GNU General Public License
14 * along with this program. If not, see <http://www.gnu.org/licenses/>.
15 *
16 * @author Rainer Kartmann ( rainer dot kartmann at kit dot edu )
17 * @date 2021
18 * @copyright http://www.gnu.org/licenses/gpl-2.0.txt
19 * GNU General Public License
20 */
21
22#include "VertexTableWidget.h"
23
24#include <algorithm>
25#include <functional>
26
27#include <QAction>
28#include <QHeaderView>
29#include <QMenu>
30#include <qabstractitemview.h>
31#include <qaction.h>
32#include <qcolor.h>
33#include <qfont.h>
34#include <qglobal.h>
35#include <qhashfunctions.h>
36#include <qheaderview.h>
37#include <qlist.h>
38#include <qmenu.h>
39#include <qnamespace.h>
40#include <qobjectdefs.h>
41#include <qpair.h>
42#include <qpoint.h>
43
45
48
49#include "utils.h"
50
52{
53
55 {
56 QStringList columns{"Provider", "Name", "X [mm]", "Y [mm]", "Yaw [\u00b0]"};
57 setColumnCount(columns.size());
58 setHorizontalHeaderLabels(columns);
59 horizontalHeader()->setResizeMode(0, QHeaderView::Stretch);
60 horizontalHeader()->setVisible(true);
61
62 setEditTriggers(QAbstractItemView::NoEditTriggers);
63 setSortingEnabled(true);
64
65 setAlternatingRowColors(true);
66
67 setSelectionMode(QAbstractItemView::SingleSelection);
68 setSelectionBehavior(QAbstractItemView::SelectRows);
69
70 // QString styleSheet = this->styleSheet();
71 // styleSheet = styleSheet + "\n" + "selection-background-color: #FF8000;";
72 // setStyleSheet(styleSheet);
73
74 setContextMenuPolicy(Qt::CustomContextMenu);
75 connect(this, &This::customContextMenuRequested, this, &This::makeContextMenu);
76 }
77
78 QTableWidgetItem*
80 {
81 // We need to disable sorting to prevent the new row from being moved away.
82 setSortingEnabled(false);
83
84 QTableWidgetItem* result = nullptr;
85 {
86 int row = rowCount();
87 setRowCount(row + 1);
88
89 for (int col = 0; col < columnCount(); col++)
90 {
91 // Just fill with vanilla items, they will get values in the update.
92 auto* item = new QTableWidgetItem{};
93 setItem(row, col, item);
94
95 if (col >= 2) // skip provider and name
96 {
97 item->setTextAlignment(Qt::AlignRight | Qt::AlignVCenter);
98 }
99 if (col == 0)
100 {
101 result = item;
102 }
103 }
104 }
105
106 // Enable sorting - `row` could now be moved away.
107 setSortingEnabled(true);
108
109 ARMARX_CHECK_NOT_NULL(result);
110 return result;
111 }
112
113 void
114 VertexTableWidget::updateVertex(GuiGraph::Vertex vertex)
115 {
116 const core::Pose_d pose = core::Pose(vertex.attrib().getPose().toEigen()).cast<qreal>();
117 char format = 'f';
118 const int precision = 2;
119
120 // Changing the values may trigger a re-sort.
121 setSortingEnabled(false);
122
123 int row = this->row(vertex.attrib().tableWidgetItem);
124
125 QString locationProviderDisplayName =
126 QString::fromStdString(vertex.attrib().getProviderName());
127
128 QString locationDisplayName = QString::fromStdString(vertex.attrib().getLocationName());
129 if (vertex.attrib().changed)
130 {
131 locationDisplayName += "*";
132 }
133 item(row, 0)->setText(locationProviderDisplayName);
134 item(row, 1)->setText(locationDisplayName);
135 item(row, 1)->setData(Qt::UserRole,
136 QString::fromStdString(vertex.attrib().getLocationName()));
137 item(row, 2)->setText(QString::number(pose(0, 3), format, precision));
138 item(row, 3)->setText(QString::number(pose(1, 3), format, precision));
139 item(row, 4)->setText(QString::number(getYawAngleDegree(pose), format, precision));
140
141 setSortingEnabled(true);
142
143 QColor bgColor = vertex.attrib().highlighted ? bgColorSelected : bgColorDefault;
144 QFont font;
145 font.setBold(vertex.attrib().highlighted);
146 for (int col = 0; col < columnCount(); ++col)
147 {
148 auto* item = this->item(row, col);
150
151 // item->setData(Qt::BackgroundRole, bgColor);
152 item->setFont(font);
153 }
154 }
155
156 void
157 VertexTableWidget::removeVertex(GuiGraph::Vertex& vertex)
158 {
159 if (currentItem() == vertex.attrib().tableWidgetItem)
160 {
161 setCurrentItem(nullptr);
162 }
163
164 removeRow(row(vertex.attrib().tableWidgetItem));
165 vertex.attrib().tableWidgetItem = nullptr;
166 }
167
168 QList<QTableWidgetItem*>
173
174 QString
175 VertexTableWidget::_nameOf(QTableWidgetItem* item)
176 {
177 return item->data(Qt::UserRole).toString();
178 }
179
180 void
182 {
183 QList<QTableWidgetItem*> items = selectedVertexItems();
184
185 QMenu menu;
186 if (items.size() == 0)
187 {
188 QAction* action = menu.addSection("No locations selected");
189 action->setEnabled(false);
190 }
191
192 // Partners selected
193 if (items.size() == 2)
194 {
195 menu.addSection("Selected pair of locations");
196
197 // Generate actions for connecting these two nodes.
198 std::sort(items.begin(),
199 items.end(),
200 [](QTableWidgetItem* first, QTableWidgetItem* second)
201 { return _nameOf(first) < _nameOf(second); });
202 QTableWidgetItem* first = items[0];
203 QTableWidgetItem* second = items[1];
204
205 connect(menu.addAction("Add edge '" + _nameOf(first) + "' " + utils::arrowRight + " '" +
206 _nameOf(second) + "'"),
207 &QAction::triggered,
208 [this, first, second]() { emit newEdgesRequested({{first, second}}); });
209 connect(menu.addAction("Add edge '" + _nameOf(first) + "' " + utils::arrowLeft + " '" +
210 _nameOf(second) + "'"),
211 &QAction::triggered,
212 [this, first, second]() { emit newEdgesRequested({{second, first}}); });
213 connect(menu.addAction("Add edges '" + _nameOf(first) + "' " + utils::arrowBoth + " '" +
214 _nameOf(second) + "'"),
215 &QAction::triggered,
216 [this, first, second]()
217 { emit newEdgesRequested({{first, second}, {second, first}}); });
218 }
219
220 // Partners via context menu
221 if (items.size() > 0)
222 {
223 QString edges = items.size() == 1 ? "edge" : "edges";
224 QString desc = items.size() == 1 ? "'" + _nameOf(items[0]) + "'"
225 : QString::number(items.size()) + " locations";
226
227 if (items.size() == 1)
228 {
229 // QAction* deleteAction = menu.addAction("Delete location '" + );
230 menu.addSection("Selected single location " + desc);
231 }
232 else
233 {
234 menu.addSection("Selected bulk of " + desc);
235 }
236
237 using Item = QTableWidgetItem;
238 using ListOfEdges = QList<QPair<Item*, Item*>>;
239 using AppendFunc =
240 std::function<void(ListOfEdges & edges, Item * selected, Item * action)>;
241
242 auto addBulkAddEdgeActions = [this, &items](QMenu* submenu, AppendFunc appendFunc)
243 {
244 if (items.size() == rowCount())
245 {
246 QAction* a = submenu->addAction("No other locations");
247 a->setDisabled(true);
248 }
249 for (int row = 0; row < rowCount(); ++row)
250 {
251 QTableWidgetItem* action = this->item(row, 0);
252 if (items.count(action) == 0) // Do no generate self-edges
253 {
254 QAction* a = submenu->addAction(_nameOf(action));
255 connect(a,
256 &QAction::triggered,
257 this,
258 [this, items, action, appendFunc]()
259 {
260 QList<QPair<QTableWidgetItem*, QTableWidgetItem*>> edges;
261 for (auto* selected : items)
262 {
263 appendFunc(edges, selected, action);
264 }
265 emit newEdgesRequested(edges);
266 });
267 }
268 }
269 };
270
271 addBulkAddEdgeActions(
272 menu.addMenu("Add " + edges + " " + desc + " " + utils::arrowRight + " ..."),
273 [](ListOfEdges& edges, Item* selected, Item* action)
274 { edges.append(QPair{selected, action}); });
275 addBulkAddEdgeActions(
276 menu.addMenu("Add " + edges + " " + desc + " " + utils::arrowLeft + " ..."),
277 [](ListOfEdges& edges, Item* selected, Item* action)
278 { edges.append(QPair{action, selected}); });
279 addBulkAddEdgeActions(
280 menu.addMenu("Add " + edges + " " + desc + " " + utils::arrowBoth + " ..."),
281 [](ListOfEdges& edges, Item* selected, Item* action)
282 {
283 edges.append(QPair{selected, action});
284 edges.append(QPair{action, selected});
285 });
286
287
288 auto connectBulkRemoveEdgeAction =
289 [this, &items](QAction* action, utils::EdgeDirection edgeDirection)
290 {
291 connect(action,
292 &QAction::triggered,
293 this,
294 [this, items, edgeDirection]()
295 { emit edgeRemovalRequested(items, edgeDirection); });
296 };
297
298 connectBulkRemoveEdgeAction(
299 menu.addAction("Remove all edges " + utils::arrowRight + " " + desc),
300 utils::EdgeDirection::To);
301 connectBulkRemoveEdgeAction(
302 menu.addAction("Remove all edges " + utils::arrowLeft + " " + desc),
303 utils::EdgeDirection::From);
304 connectBulkRemoveEdgeAction(
305 menu.addAction("Remove all edges " + utils::arrowBoth + " " + desc),
306 utils::EdgeDirection::Bidirectional);
307 }
308
309
310 menu.addSection("Manage Locations");
311 connect(menu.addAction("Create new location ..."),
312 &QAction::triggered,
313 this,
314 [this]() { emit newVertexRequested(); });
315
316 menu.exec(mapToGlobal(pos));
317 }
318
319} // namespace armarx::navigation::qt_plugins::location_graph_editor
void newEdgesRequested(QList< QPair< QTableWidgetItem *, QTableWidgetItem * > > edges)
#define ARMARX_CHECK_NOT_NULL(ptr)
This macro evaluates whether ptr is not null and if it turns out to be false it will throw an Express...
Eigen::Isometry3f Pose
Definition basic_types.h:31
Eigen::Isometry3d Pose_d
Definition basic_types.h:32
QList< QTableWidgetItem * > getSelectedItemsOfColumn(QTableWidget *widget, int column)
Definition utils.cpp:41