Seamly2D
Code documentation
insert_nodes_dialog.cpp
Go to the documentation of this file.
1 /***************************************************************************
2  ** @file insert_nodes_dialog.cpp
3  ** @author Douglas S Caskey
4  ** @date Dec 11, 2022
5  **
6  ** @copyright
7  ** Copyright (C) 2017 - 2022 Seamly, LLC
8  ** https://github.com/fashionfreedom/seamly2d
9  **
10  ** @brief
11  ** Seamly2D is free software: you can redistribute it and/or modify
12  ** it under the terms of the GNU General Public License as published by
13  ** the Free Software Foundation, either version 3 of the License, or
14  ** (at your option) any later version.
15  **
16  ** Seamly2D is distributed in the hope that it will be useful,
17  ** but WITHOUT ANY WARRANTY; without even the implied warranty of
18  ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  ** GNU General Public License for more details.
20  **
21  ** You should have received a copy of the GNU General Public License
22  ** along with Seamly2D. If not, see <http://www.gnu.org/licenses/>.
23  **************************************************************************/
24 
25 /************************************************************************
26  **
27  ** @file dialoginsertnode.cpp
28  ** @author Roman Telezhynskyi <dismine(at)gmail.com>
29  ** @date 21 3, 2017
30  **
31  ** @brief
32  ** @copyright
33  ** This source code is part of the Valentina project, a pattern making
34  ** program, whose allow create and modeling patterns of clothing.
35  ** Copyright (C) 2017 Valentina project
36  ** <https://bitbucket.org/dismine/valentina> All Rights Reserved.
37  **
38  ** Valentina is free software: you can redistribute it and/or modify
39  ** it under the terms of the GNU General Public License as published by
40  ** the Free Software Foundation, either version 3 of the License, or
41  ** (at your option) any later version.
42  **
43  ** Valentina is distributed in the hope that it will be useful,
44  ** but WITHOUT ANY WARRANTY; without even the implied warranty of
45  ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
46  ** GNU General Public License for more details.
47  **
48  ** You should have received a copy of the GNU General Public License
49  ** along with Valentina. If not, see <http://www.gnu.org/licenses/>.
50  **
51  *************************************************************************/
52 
53 #include "insert_nodes_dialog.h"
54 #include "ui_insert_nodes_dialog.h"
55 #include "../vpatterndb/vcontainer.h"
56 #include "../vmisc/vabstractapplication.h"
57 #include "../vmisc/vcommonsettings.h"
58 #include "vpointf.h"
59 
60 #include <QMenu>
61 #include <QKeyEvent>
62 #include <QSound>
63 
64 //---------------------------------------------------------------------------------------------------------------------
65 InsertNodesDialog::InsertNodesDialog(const VContainer *data, quint32 toolId, QWidget *parent)
66  : DialogTool(data, toolId, parent)
67  , ui(new Ui::InsertNodesDialog)
68  , m_nodes({})
69  , m_nodeFlag(false)
70  , m_piecesFlag(false)
71  , m_beep(new QSound(qApp->Settings()->getSelectionSound()))
72 {
73  ui->setupUi(this);
74  setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
75  setWindowIcon(QIcon(":/toolicon/32x32/insert_nodes_icon.png"));
76  ui->statusMsg_Label->setText("");
77 
78  ui->nodes_ListWidget->installEventFilter(this);
79 
80  initializeOkCancel(ui);
81 
82  validatePieces();
83 
84  connect(ui->piece_ComboBox, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this, [this]()
85  {
86  validatePieces();
87  });
88 
89  connect(ui->nodes_ListWidget, &QListWidget::customContextMenuRequested, this, &InsertNodesDialog::showContextMenu);
90 }
91 
92 //---------------------------------------------------------------------------------------------------------------------
94 {
95  delete ui;
96 }
97 
98 //---------------------------------------------------------------------------------------------------------------------
100 {
101  FillComboBoxPiecesList(ui->piece_ComboBox, list);
102 }
103 
104 //---------------------------------------------------------------------------------------------------------------------
106 {
107  return getCurrentObjectId(ui->piece_ComboBox);
108 }
109 
110 //---------------------------------------------------------------------------------------------------------------------
112 {
113  QVector<VPieceNode> nodes;
114  for (qint32 i = 0; i < ui->nodes_ListWidget->count(); ++i)
115  {
116  VPieceNode node = qvariant_cast<VPieceNode>(ui->nodes_ListWidget->item(i)->data(Qt::UserRole));
117  nodes.append(node);
118  }
119  return nodes;
120 }
121 
122 //---------------------------------------------------------------------------------------------------------------------
124 {
125  if (!click)
126  {
127  if (m_nodes.isEmpty())
128  {
129  return;
130  }
131 
132  for (auto &node : m_nodes)
133  {
134  newNodeItem(ui->nodes_ListWidget, node, false, false);
135  }
136 
137  m_nodes.clear();
138 
139  validateNodes();
140 
141  prepare = true;
142  setModal(true);
143  emit ToolTip(QString());
144  show();
145  }
146 }
147 
148 //---------------------------------------------------------------------------------------------------------------------
149 /*
150  * @brief Handles emitted signal from the scene if an object was selected.
151  * @param selected true is object was selected.
152  * @param objId Object Id of the selected object.
153  * @param toolId Tool Id of the selected object.
154  */
155 void InsertNodesDialog::SelectedObject(bool selected, quint32 objId, quint32 toolId)
156 {
157  Q_UNUSED(toolId)
158 
159  if (prepare)
160  {
161  return;
162  }
163 
164  auto nodeIterator = std::find_if(m_nodes.begin(), m_nodes.end(),
165  [objId](const VPieceNode &node) { return node.GetId() == objId; });
166  if (selected)
167  {
168  if (nodeIterator == m_nodes.cend())
169  {
170  GOType objType = GOType::Unknown;
171  try
172  {
173  objType = data->GetGObject(objId)->getType();
174  }
175  catch (const VExceptionBadId &)
176  {
177  qDebug() << "Cannot find an object with id" << objId;
178  return;
179  }
180  m_beep->play();
181  bool appendCurve = false;
182  QSharedPointer<VGObject> previousObj = nullptr;
183  quint32 previousObjId = getLastNodeId();
184  if (previousObjId != NULL_ID)
185  {
186  previousObj = data->GetGObject(previousObjId);
187  }
188 
189  VPieceNode node;
190  VPieceNode node2;
191  switch (objType)
192  {
193  case GOType::Point:
194  node = VPieceNode(objId, Tool::NodePoint);
195  if (previousObj != nullptr)
196  {
197  GOType previousObjType = previousObj->getType();
198  if (previousObjType == GOType::Arc || previousObjType == GOType::EllipticalArc ||
199  previousObjType == GOType::Spline || previousObjType == GOType::CubicBezier)
200  {
201  const QSharedPointer<VAbstractCurve> curve = data->GeometricObject<VAbstractCurve>(previousObjId);
202  const QPointF point = static_cast<QPointF>(*data->GeometricObject<VPointF>(objId));
203 
204  if (curve->isPointOnCurve(point) &&
205  point != curve->getFirstPoint() &&
206  point != curve->getLastPoint())
207  {
208  switch (previousObjType)
209  {
210  case GOType::Arc:
211  node2 = VPieceNode(previousObjId, Tool::NodeArc, m_nodes.last().GetReverse());
212  appendCurve = true;
213  break;
215  node2 = VPieceNode(previousObjId, Tool::NodeElArc, m_nodes.last().GetReverse());
216  appendCurve = true;
217  break;
218  case GOType::Spline:
219  case GOType::CubicBezier:
220  node2 = VPieceNode(previousObjId, Tool::NodeSpline, m_nodes.last().GetReverse());
221  appendCurve = true;
222  break;
223  case GOType::SplinePath:
225  node2 = VPieceNode(previousObjId, Tool::NodeSplinePath, m_nodes.last().GetReverse());
226  appendCurve = true;
227  break;
228  case GOType::Point:
229  case GOType::Unknown:
230  default:
231  break;
232  }
233  }
234  }
235  }
236  break;
237  case GOType::Arc:
238  node = VPieceNode(objId, Tool::NodeArc, correctCurveDirection(objId));
239  break;
241  node = VPieceNode(objId, Tool::NodeElArc, correctCurveDirection(objId));
242  break;
243  case GOType::Spline:
244  case GOType::CubicBezier:
245  node = VPieceNode(objId, Tool::NodeSpline, correctCurveDirection(objId));
246  break;
247  case GOType::SplinePath:
250  break;
251  case GOType::Unknown:
252  default:
253  qDebug() << "Ignore unknown object type.";
254  return;
255  }
256 
257  node.SetExcluded(true);
258  m_nodes.append(node);
259  if (appendCurve)
260  {
261  node2.SetExcluded(true);
262  m_nodes.append(node2);
263  }
264  if (objType != GOType::Point)
265  {
266  insertCurveNodes(node);
267  }
268  }
269  }
270  else
271  {
272  if (nodeIterator != m_nodes.end())
273  {
274  m_nodes.erase(nodeIterator);
275  }
276  }
277 }
278 
279 //---------------------------------------------------------------------------------------------------------------------
280 /*
281  * @brief Displays a context menu for the list of node items.
282  * Allows user to set notch type of point nodes.
283  * Allows user to reverse a curve node.
284  * Allows user to delete a node from the list.
285  * @param pos Mouse position on the selected list item to display the menu at.
286  */
287 void InsertNodesDialog::showContextMenu(const QPoint &pos)
288 {
289  const int row = ui->nodes_ListWidget->currentRow();
290  if (ui->nodes_ListWidget->count() == 0 || row == -1 || row >= ui->nodes_ListWidget->count())
291  {
292  return;
293  }
294 
295  // workaround for https://bugreports.qt.io/browse/QTBUG-97559: assign parent to QMenu
296  QScopedPointer<QMenu> menu(new QMenu(ui->nodes_ListWidget));
297  NodeInfo info;
298  NotchType notchType = NotchType::Slit;
299  bool isNotch = false;
300  QListWidgetItem *rowItem = ui->nodes_ListWidget->item(row);
301  SCASSERT(rowItem != nullptr);
302  VPieceNode rowNode = qvariant_cast<VPieceNode>(rowItem->data(Qt::UserRole));
303 
304  QAction *actionNotch = nullptr;
305  QAction *actionNone = nullptr;
306  QAction *actionSlit = nullptr;
307  QAction *actionTNotch = nullptr;
308  QAction *actionUNotch = nullptr;
309  QAction *actionVInternal = nullptr;
310  QAction *actionVExternal = nullptr;
311  QAction *actionCastle = nullptr;
312  QAction *actionDiamond = nullptr;
313  QAction *actionReverse = nullptr;
314 
315  if (rowNode.GetTypeTool() != Tool::NodePoint)
316  {
317  actionReverse = menu->addAction(tr("Reverse"));
318  actionReverse->setCheckable(true);
319  actionReverse->setChecked(rowNode.GetReverse());
320  }
321  else
322  {
323 
324  QMenu *notchMenu = menu->addMenu(tr("Notch"));
325  actionNotch = notchMenu->menuAction();
326  actionNotch->setCheckable(true);
327  actionNotch->setChecked(rowNode.isNotch());
328 
329  actionNone = notchMenu->addAction( tr("None"));
330  actionSlit = notchMenu->addAction(QIcon("://icon/24x24/slit_notch.png"), tr("Slit"));
331  actionTNotch = notchMenu->addAction(QIcon("://icon/24x24/t_notch.png"), tr("TNotch"));
332  actionUNotch = notchMenu->addAction(QIcon("://icon/24x24/u_notch.png"), tr("UNotch"));
333  actionVInternal = notchMenu->addAction(QIcon("://icon/24x24/internal_v_notch.png"), tr("VInternal"));
334  actionVExternal = notchMenu->addAction(QIcon("://icon/24x24/external_v_notch.png"), tr("VExternal"));
335  actionCastle = notchMenu->addAction(QIcon("://icon/24x24/castle_notch.png"), tr("Castle"));
336  actionDiamond = notchMenu->addAction(QIcon("://icon/24x24/diamond_notch.png"), tr("Diamond"));
337  }
338 
339  QAction *actionDelete = menu->addAction(QIcon::fromTheme("edit-delete"), tr("Delete") + QStringLiteral("\tDel"));
340  QAction *selectedAction = menu->exec(ui->nodes_ListWidget->viewport()->mapToGlobal(pos));
341  if (selectedAction == actionReverse && rowNode.GetTypeTool() != Tool::NodePoint)
342  {
343  rowNode.SetReverse(!rowNode.GetReverse());
344  info = getNodeInfo(rowNode, true);
345  rowItem->setData(Qt::UserRole, QVariant::fromValue(rowNode));
346  rowItem->setIcon(QIcon(info.icon));
347  rowItem->setText(info.name);
348  }
349  else if (selectedAction == actionDelete)
350  {
351  delete rowItem;
352  if (ui->nodes_ListWidget->count() == 0)
353  {
354  ui->statusMsg_Label->setText(tr("No nodes selected. Press Cancel to continue"));
355  }
356  }
357  else
358  {
359  if (selectedAction == actionNone)
360  {
361  isNotch = false;
362  notchType = NotchType::Slit;
363  }
364  else if (selectedAction == actionSlit)
365  {
366  isNotch = true;
367  notchType = NotchType::Slit;
368  }
369  else if (selectedAction == actionTNotch)
370  {
371  isNotch = true;
372  notchType = NotchType::TNotch;
373  }
374  else if (selectedAction == actionUNotch)
375  {
376  isNotch = true;
377  notchType = NotchType::UNotch;
378  }
379  else if (selectedAction == actionVInternal)
380  {
381  isNotch = true;
382  notchType = NotchType::VInternal;
383  }
384  else if (selectedAction == actionVExternal)
385  {
386  isNotch = true;
387  notchType = NotchType::VExternal;
388  }
389  else if (selectedAction == actionCastle)
390  {
391  isNotch = true;
392  notchType = NotchType::Castle;
393  }
394  else if (selectedAction == actionDiamond)
395  {
396  isNotch = true;
397  notchType = NotchType::Diamond;
398  }
399 
400  rowNode.setNotch(isNotch);
401  rowNode.setNotchType(notchType);
402  info = getNodeInfo(rowNode, true);
403  rowItem->setData(Qt::UserRole, QVariant::fromValue(rowNode));
404  rowItem->setIcon(QIcon(info.icon));
405  rowItem->setText(info.name);
406  }
407 
408  validateNodes();
409 }
410 
411 //---------------------------------------------------------------------------------------------------------------------
412 /*
413  * @brief Disable OK button if there are no nodes or a pattern piece selected.
414  */
416 {
417  SCASSERT(ok_Button != nullptr);
418  ok_Button->setEnabled(m_nodeFlag && m_piecesFlag);
419 }
420 
421 
422 //---------------------------------------------------------------------------------------------------------------------
423 /*
424  * @brief Filters keyboard event to check if the delete key was pressed.
425  * @param object QObject that sent the event.
426  * @param event QEvent.
427  * @return If the node list was the sending object: True if the key pressed was the Delete key False if any other key.
428  * If event sent by any other object pass the event on to the parent.
429  */
430 bool InsertNodesDialog::eventFilter(QObject *object, QEvent *event)
431 {
432  if (QListWidget *list = qobject_cast<QListWidget *>(object))
433  {
434  if (event->type() == QEvent::KeyPress)
435  {
436  QKeyEvent *keyEvent = static_cast<QKeyEvent *>(event);
437  if (keyEvent->key() == Qt::Key_Delete)
438  {
439  const int row = ui->nodes_ListWidget->currentRow();
440  if (ui->nodes_ListWidget->count() > 0 || row > -1 || row < ui->nodes_ListWidget->count())
441  {
442  QListWidgetItem *rowItem = list->item(row);
443  SCASSERT(rowItem != nullptr);
444  delete rowItem;
445  }
446  return true;
447  }
448  }
449  }
450  else
451  {
452  return DialogTool::eventFilter(object, event); // pass the event on to the parent class
453  }
454  return false; // pass the event to the widget
455 }
456 
457 //---------------------------------------------------------------------------------------------------------------------
459 {
460  QColor color = okColor;
461  if (ui->piece_ComboBox->count() <= 0 || ui->piece_ComboBox->currentIndex() == -1)
462  {
463  m_piecesFlag = false;
464  color = errorColor;
465  }
466  else
467  {
468  m_piecesFlag = true;
469  color = okColor;
470  }
471  ChangeColor(ui->piece_Label, color);
472  checkState();
473 }
474 
475 //---------------------------------------------------------------------------------------------------------------------
477 {
478  m_nodeFlag = ui->nodes_ListWidget->count() > 0;
479  checkState();
480 }
481 
482 //---------------------------------------------------------------------------------------------------------------------
483 /*
484  * @brief Gets last node in the list.
485  * @return node Id if list not empty otherwise NULL_ID
486  */
488 {
489  if (!m_nodes.isEmpty())
490  {
491  VPieceNode node = m_nodes.last();
492  return node.GetId();
493  }
494  else
495  {
496  return NULL_ID;
497  }
498 }
499 
500 //---------------------------------------------------------------------------------------------------------------------
501 /*
502  * @brief Checks whether a selected curve needs to be reversed so it's direction is clockwise.
503  * @param curveObjId Id of selected node.
504  * @return true if curve needs to be reversed or false if curve does not need to be reversed.
505 */
507 {
508  const QSharedPointer<VGObject> curveObj = data->GetGObject(curveObjId);
509  const QString curveName = curveObj->name();
510  quint32 nodeId;
512 
513  for (auto &node : m_nodes)
514  {
515  nodeId = node.GetId();
516  const QSharedPointer<VGObject> nodeObj = data->GetGObject(nodeId);
517  if (static_cast<GOType>(nodeObj->getType()) == GOType::Point)
518  {
519  const QPointF point = static_cast<QPointF>(*data->GeometricObject<VPointF>(nodeId));
520  if (point == curve->getLastPoint())
521  {
522  ui->statusMsg_Label->setText(ui->statusMsg_Label->text() + curveName +
523  tr(" was auto reversed.") + "\n");
524  return true;
525  }
526  else if (point == curve->getFirstPoint())
527  {
528  return false;
529  }
530  }
531  }
532  ui->statusMsg_Label->setText(ui->statusMsg_Label->text() + curveName +
533  tr(" may need to be manually reversed.") + "\n");
534  return false;
535 }
536 
537 //---------------------------------------------------------------------------------------------------------------------
538 /*
539  * @brief Checks whether to insert of copy of the selected curve before any node points that may exist on the curve.
540  * @param curveNode selected curve node.
541  */
543 {
544  quint32 nodeId;
545  quint32 prevNodeId;
546  quint32 curveNodeId = curveNode.GetId();
547 
549  for (int i = 0; i < m_nodes.size(); ++i)
550  {
551  nodeId = m_nodes.value(i).GetId();
552  const QSharedPointer<VGObject> nodeObj = data->GetGObject(nodeId);
553  if (static_cast<GOType>(nodeObj->getType()) == GOType::Point)
554  {
555  const QPointF point = static_cast<QPointF>(*data->GeometricObject<VPointF>(nodeId));
556  if (curve->isPointOnCurve(point) &&
557  point != curve->getFirstPoint() &&
558  point != curve->getLastPoint() &&
559  curveNodeId != nodeId)
560  {
561  if (i > 0)
562  {
563  prevNodeId = m_nodes.value(i - 1).GetId();
564  }
565  if (i == 0 || prevNodeId != curveNodeId)
566  {
567  VPieceNode node2 = curveNode;
568  node2.SetExcluded(true);
569  m_nodes.insert(i, 1, node2);
570  }
571  }
572  }
573  }
574 }
The DialogTool class parent for all dialog of tools.
Definition: dialogtool.h:107
const QColor okColor
Definition: dialogtool.h:219
void ToolTip(const QString &toolTip)
ToolTip emit tooltipe for tool.
QPushButton * ok_Button
ok_Button button ok
Definition: dialogtool.h:199
NodeInfo getNodeInfo(const VPieceNode &node, bool showNotch=false) const
Definition: dialogtool.cpp:628
const QColor errorColor
Definition: dialogtool.h:220
void newNodeItem(QListWidget *listWidget, const VPieceNode &node, bool nodeExcluded=true, bool isDuplicate=false)
Definition: dialogtool.cpp:679
quint32 toolId
Definition: dialogtool.h:225
const VContainer * data
data container with data
Definition: dialogtool.h:177
bool prepare
prepare show if we prepare. Show dialog after finish working with visual part of tool
Definition: dialogtool.h:228
virtual bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE
Definition: dialogtool.cpp:441
void FillComboBoxPiecesList(QComboBox *box, const QVector< quint32 > &list)
Definition: dialogtool.cpp:224
void ChangeColor(QWidget *widget, const QColor &color)
quint32 getCurrentObjectId(QComboBox *box) const
getCurrentPointId return current point id stored in combobox
Definition: dialogtool.cpp:959
QVector< VPieceNode > m_nodes
virtual void SelectedObject(bool selected, quint32 object, quint32 tool) Q_DECL_OVERRIDE
Ui::InsertNodesDialog * ui
bool correctCurveDirection(quint32 objectId)
quint32 getLastNodeId() const
QVector< VPieceNode > getNodes() const
quint32 getPieceId() const
InsertNodesDialog(const VContainer *data, quint32 toolId, QWidget *parent=nullptr)
void showContextMenu(const QPoint &pos)
virtual bool eventFilter(QObject *object, QEvent *event) Q_DECL_OVERRIDE
virtual void ShowDialog(bool click) Q_DECL_OVERRIDE
virtual void SetPiecesList(const QVector< quint32 > &list) Q_DECL_OVERRIDE
void insertCurveNodes(VPieceNode curveNode)
virtual void checkState() Q_DECL_FINAL
The VContainer class container of all variables.
Definition: vcontainer.h:141
const QSharedPointer< VGObject > GetGObject(quint32 id) const
GetGObject returns a point by id.
Definition: vcontainer.cpp:150
const QSharedPointer< T > GeometricObject(const quint32 &id) const
Definition: vcontainer.h:266
The VExceptionBadId class for exception bad id.
void SetExcluded(bool exclude)
Definition: vpiecenode.cpp:417
void setNotch(bool notch)
Definition: vpiecenode.cpp:293
Tool GetTypeTool() const
Definition: vpiecenode.cpp:161
quint32 GetId() const
Definition: vpiecenode.cpp:149
void setNotchType(NotchType notchType)
Definition: vpiecenode.cpp:320
bool GetReverse() const
Definition: vpiecenode.cpp:173
bool isNotch() const
Definition: vpiecenode.cpp:287
void SetReverse(bool reverse)
Definition: vpiecenode.cpp:179
The VPointF class keep data of point.
Definition: vpointf.h:75
NotchType
Definition: def.h:123
#define SCASSERT(cond)
Definition: def.h:317
@ NodeSplinePath
@ NodeSpline
@ NodeElArc
@ NodeArc
@ NodePoint
#define NULL_ID
Definition: ifcdef.h:76
QString name
Definition: dialogtool.h:89
QString icon
Definition: dialogtool.h:88
#define qApp
Definition: vapplication.h:67
GOType
Definition: vgeometrydef.h:56
@ CubicBezierPath
@ SplinePath
@ EllipticalArc
@ CubicBezier