CloneGroupDialog.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* @package ArmarX::
17* @author Valerij Wittenbeck (valerij.wittenbeck at student dot kit dot edu
18* @date 2015
19* @copyright http://www.gnu.org/licenses/gpl-2.0.txt
20* GNU General Public License
21*/
22
23#include "CloneGroupDialog.h"
24
25#include <filesystem>
26
27#include <QFileDialog>
28#include <QPushButton>
29#include <QTimer>
30#include <QtGui>
31
34
35#include <ArmarXGui/gui-plugins/StatechartEditorPlugin/view/dialogs/ui_CloneGroupDialog.h>
36
37namespace armarx
38{
39
42 GroupClonerPtr groupCloner,
43 QWidget* parent) :
44 QDialog(parent),
45 ui(new Ui::CloneGroupDialog),
46 packageTool(packageTool),
47 group(group),
48 groupCloner(groupCloner),
49 validGroupNameRegExp("([a-zA-Z][a-zA-Z0-9]*)"),
50 colorGreen(QColor::fromRgb(120, 255, 120)),
51 colorRed(QColor::fromRgb(255, 120, 120)),
52 colorYellow(QColor::fromRgb(255, 200, 0)),
53 alreadyChecking(false)
54 {
55 ui->setupUi(this);
56
57 connect(ui->btnSelectPackageFolder, SIGNAL(clicked()), this, SLOT(selectPackagePath()));
58 connect(ui->editPackagePath,
59 SIGNAL(textChanged(QString)),
60 this,
61 SLOT(requestCheckPackagePath(QString)));
62 connect(ui->editGroupPrefix,
63 SIGNAL(textChanged(QString)),
64 this,
65 SLOT(requestPrefixUpdate(QString)));
66 connect(ui->groupsWidget,
67 SIGNAL(itemChanged(QTableWidgetItem*)),
68 this,
69 SLOT(verifyName(QTableWidgetItem*)));
70 connect(ui->checkBoxCloneRobotSkillTemplates,
71 SIGNAL(toggled(bool)),
72 this,
74
75 ui->editGroupPrefix->setEnabled(false);
76 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
77
78 ui->editGroupPrefix->setValidator(new QRegExpValidator(validGroupNameRegExp, this));
79
80 timer = new QTimer(this);
81 connect(timer, SIGNAL(timeout()), this, SLOT(checkPackagePath()));
82 timer->setSingleShot(true);
83 }
84
86 {
87 delete ui;
88 }
89
92 {
93 return packageTool;
94 }
95
96 QString
98 {
99 std::filesystem::path path = getPackagePath().toUtf8().data();
100 return QString::fromUtf8(path.filename().c_str());
101 }
102
103 QString
105 {
106 std::filesystem::path path = ui->editPackagePath->text().toUtf8().data();
107 std::filesystem::path cleanPath = path;
108
109 try
110 {
111 cleanPath = std::filesystem::canonical(path);
112 }
113 catch (...)
114 {
115 cleanPath = ArmarXDataPath::cleanPath(path.string());
116
117 if (*cleanPath.string().rbegin() == '/' || *cleanPath.string().rbegin() == '\\')
118 {
119 cleanPath = cleanPath.remove_filename();
120 }
121 }
122
123 return QString::fromUtf8(cleanPath.c_str());
124 }
125
128 {
129 return group;
130 }
131
134 {
135 return groupCloner;
136 }
137
140 {
141 return mapping;
142 }
143
144 QVector<QPair<StatechartGroupPtr, QString>>
146 {
147 QVector<QPair<StatechartGroupPtr, QString>> result;
148
149 if (ui->groupsWidget->rowCount() == 0 || ui->groupsWidget->columnCount() < 2)
150 {
151 return result;
152 }
153
154 for (const auto& g : groupsToClone)
155 {
156 for (int i = 0; i < ui->groupsWidget->rowCount(); ++i)
157 {
158 const auto oldName = ui->groupsWidget->item(i, 0)->data(Qt::UserRole).toString();
159
160 if (oldName == g->getName())
161 {
162 const auto newName = ui->groupsWidget->item(i, 1)->text();
163 ARMARX_CHECK_EXPRESSION(oldName != newName)
164 << "oldName == newName of group " << g->getName();
165 result.push_back({g, newName});
166 }
167 }
168 }
169
170 ARMARX_CHECK_EXPRESSION(groupsToClone.size() == result.size());
171 return result;
172 }
173
174 QString
176 {
177 return groupPrefix;
178 }
179
180 void
181 CloneGroupDialog::verifyName(QTableWidgetItem* item)
182 {
183
184 const int c = item->column();
185
186 if (c == 0 || ui->groupsWidget->columnCount() < 2)
187 {
188 return;
189 }
190
191 //setting the background color triggers the "itemchanged" signal, which is what called this method in the first place
192 //this elegant solution ensures that no recursive calling is taking place
193 if (alreadyChecking)
194 {
195 return;
196 }
197
198 alreadyChecking = true;
199
200 bool hasNameCollisions = false;
201
202 for (int i = 0; i < ui->groupsWidget->rowCount(); ++i)
203 {
204 const auto& itemTest = ui->groupsWidget->item(i, 1);
205
206 if (!itemTest || (!itemTest->flags().testFlag(Qt::ItemIsEditable)))
207 {
208 continue;
209 }
210
211 bool collision = false;
212
213 for (int j = 0; j < ui->groupsWidget->rowCount() && !collision; ++j)
214 {
215 const auto& itemL = ui->groupsWidget->item(j, 0);
216 const auto& itemR = ui->groupsWidget->item(j, 1);
217
218 collision |= itemL && itemTest->text() == itemL->text();
219 collision |= itemR && (i != j) && itemTest->text() == itemR->text();
220 }
221
222 itemTest->setBackgroundColor(collision ? colorRed : colorGreen);
223 hasNameCollisions |= collision;
224 }
225
226 bool validName = validGroupNameRegExp.exactMatch(item->text());
227 item->setBackgroundColor(!validName ? colorRed : item->backgroundColor());
228
229 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(validName && !hasNameCollisions);
230 alreadyChecking = false;
231 }
232
233 void
235 {
236 timer->start(250);
237 }
238
239 void
241 {
242 groupPrefix = prefix;
243 buildGroupList();
244 }
245
246 void
248 {
249 buildGroupList();
250 }
251
252 void
253 CloneGroupDialog::buildGroupList()
254 {
255 if (groupPrefix.size() == 0)
256 {
257 ui->groupsWidget->clear();
258 ui->groupsWidget->setColumnCount(0);
259 ui->groupsWidget->setRowCount(0);
260 }
261
262 updateGroupDependencies();
263
264 ui->buttonBox->button(QDialogButtonBox::Ok)
265 ->setEnabled(!deps.empty() && !groupsToClone.empty() && groupPrefix.size() != 0);
266
267 if (deps.empty() || groupPrefix.size() == 0)
268 {
269 return;
270 }
271
272 ui->groupsWidget->clear();
273 ui->groupsWidget->setRowCount(deps.size());
274 ui->groupsWidget->setColumnCount(2);
275 ui->groupsWidget->horizontalHeader()->setResizeMode(QHeaderView::Stretch);
276 ui->groupsWidget->setHorizontalHeaderLabels(
277 {"Original StatechartGroup", "Cloned StatechartGroup"});
278
279 for (int i = 0; i < deps.size(); ++i)
280 {
281 const auto& g = deps[i];
282
283 QTableWidgetItem* item =
284 new QTableWidgetItem(g->getName() + " [" + g->getPackageName() + "]");
285 item->setData(Qt::UserRole, g->getName());
286 item->setFlags(item->flags() ^ Qt::ItemIsEditable);
287 QTableWidgetItem* renamed = new QTableWidgetItem(groupPrefix + g->getName());
288
289 bool alreadyCloned = std::find_if(groupsToClone.constBegin(),
290 groupsToClone.constEnd(),
291 [&](const StatechartGroupPtr& dep) {
292 return g->getGroupPath() == dep->getGroupPath();
293 }) == groupsToClone.constEnd();
294
295 if (!alreadyCloned)
296 {
297 item->setBackgroundColor(colorGreen);
298 renamed->setBackgroundColor(colorGreen);
299 }
300 else
301 {
302 item->setBackgroundColor(colorYellow);
303 renamed->setBackgroundColor(colorYellow);
304 renamed->setFlags(renamed->flags() ^ Qt::ItemIsEditable);
305
306 if (auto newGroupName = mapping.queryMappedGroupName(g->getName()))
307 {
308 renamed->setText(*newGroupName);
309 }
310 else
311 {
312 renamed->setText("<Error: Something went terribly wrong>");
313 renamed->setBackgroundColor(colorRed);
314 }
315 }
316
317 ui->groupsWidget->setItem(i, 0, item);
318 ui->groupsWidget->setItem(i, 1, renamed);
319 }
320
321 if (groupsToClone.empty())
322 {
323 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
324 }
325 }
326
327 void
329 {
330 QFileDialog selectFolder(this, "Select Target Package Root Folder");
331 QList<QUrl> urls;
332 urls << QUrl::fromLocalFile(
333 QDesktopServices::storageLocation(QDesktopServices::HomeLocation))
334 << QUrl::fromLocalFile(
335 QDesktopServices::storageLocation(QDesktopServices::DesktopLocation));
336
337 if (!ArmarXDataPath::getHomePath().empty())
338 {
339 urls << QUrl::fromLocalFile(QString::fromStdString(ArmarXDataPath::getHomePath()));
340 }
341
342 selectFolder.setSidebarUrls(urls);
343 // selectFolder.setOption(QFileDialog::ShowDirsOnly, true);
344 selectFolder.setOption(QFileDialog::ReadOnly, true);
345 selectFolder.setOption(QFileDialog::HideNameFilterDetails, false);
346 selectFolder.setFileMode(QFileDialog::Directory);
347
348 if (selectFolder.exec() == QDialog::Accepted)
349 {
350 ui->editPackagePath->setText(*selectFolder.selectedFiles().begin());
351 }
352 }
353
354 void
355 CloneGroupDialog::updateGroupDependencies()
356 {
357 deps = groupCloner->getGroupDependencies(group, true);
358 if (!ui->checkBoxCloneRobotSkillTemplates->isChecked())
359 {
360 auto it = std::remove_if(deps.begin(),
361 deps.end(),
362 [&](const StatechartGroupPtr& g)
363 { return g->getPackageName() == "RobotSkillTemplates"; });
364 deps.erase(it, deps.end());
365 }
366
367 QVector<StatechartGroupPtr> existingDeps = groupCloner->getMappedGroups(mapping);
368
369 groupsToClone.clear();
370
371 for (const auto& g : deps)
372 {
373 bool alreadyCloned = std::find_if(existingDeps.constBegin(),
374 existingDeps.constEnd(),
375 [&](const StatechartGroupPtr& dep) {
376 return g->getGroupPath() == dep->getGroupPath();
377 }) != existingDeps.constEnd();
378
379 if (!alreadyCloned)
380 {
381 groupsToClone.push_back(g);
382 }
383 }
384 }
385
386 void
388 {
389 if (packageTool->checkPackagePath(ui->editPackagePath->text().toStdString()))
390 {
391 ui->labelPackageError->setText("Package path is valid.");
392 QPalette p(ui->labelPackageError->palette());
393 p.setColor(ui->labelPackageError->backgroundRole(), colorGreen);
394 // p.setBrush(ui->labelPackageError->backgroundRole(), p.light());
395 ui->labelPackageError->setPalette(p);
396 // ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(true);
397
398 mapping = StatechartGroupMapping();
399
400 // if (auto mappingOpt = StatechartGroupMapping::ReadStatechartGroupMappingFile(ui->editPackagePath->text()))
401 // {
402 // mapping = mappingOpt.get();
403 // }
404
405 updateGroupDependencies();
406
407 ui->editGroupPrefix->setEnabled(true);
408
409 QString prefixSuggestion;
410
411 if (!mapping.groupMappings.empty())
412 {
413 const auto gm = *mapping.groupMappings.begin();
414 prefixSuggestion = QString(gm.newGroupName).replace(gm.groupName, "");
415 }
416
417 ui->editGroupPrefix->setText(prefixSuggestion);
418 }
419 else
420 {
421 ui->labelPackageError->setText("Package path is not valid!");
422 QPalette p(ui->labelPackageError->palette());
423 p.setColor(ui->labelPackageError->backgroundRole(), colorRed);
424 ui->labelPackageError->setPalette(p);
425 ui->buttonBox->button(QDialogButtonBox::Ok)->setEnabled(false);
426 }
427 }
428
429} // namespace armarx
constexpr T c
static std::string cleanPath(const std::string &filepathStr)
static std::string getHomePath()
GroupClonerPtr getGroupCloner() const
StatechartGroupMapping getStatechartGroupMapping() const
ArmarXPackageToolInterfacePtr getPackageTool() const
void verifyName(QTableWidgetItem *item)
void requestCheckPackagePath(QString path)
void cloneRobotSkillTemplatesToggled(bool state)
void requestPrefixUpdate(QString prefix)
CloneGroupDialog(ArmarXPackageToolInterfacePtr packageTool, StatechartGroupPtr group, GroupClonerPtr groupCloner, QWidget *parent=0)
StatechartGroupPtr getGroup() const
QVector< QPair< StatechartGroupPtr, QString > > getGroupsToClone() const
#define ARMARX_CHECK_EXPRESSION(expression)
This macro evaluates the expression and if it turns out to be false it will throw an ExpressionExcept...
ArmarX Headers.
This file offers overloads of toIce() and fromIce() functions for STL container types.
std::shared_ptr< StatechartGroup > StatechartGroupPtr
std::shared_ptr< GroupCloner > GroupClonerPtr
Definition GroupCloner.h:46
std::shared_ptr< ArmarXPackageToolInterface > ArmarXPackageToolInterfacePtr