Sun, 17 Jun 2018 16:13:24 +0300
Circular primitive editor complete
--- a/CMakeLists.txt Sun Jun 17 14:06:03 2018 +0300 +++ b/CMakeLists.txt Sun Jun 17 16:13:24 2018 +0300 @@ -62,7 +62,7 @@ src/algorithms/invert.cpp src/dialogs/colorselector.cpp src/dialogs/configdialog.cpp - src/dialogs/cylindereditor.cpp + src/dialogs/circularprimitiveeditor.cpp src/dialogs/externalprogrampathdialog.cpp src/dialogs/generateprimitivedialog.cpp src/dialogs/ldrawpathdialog.cpp @@ -135,7 +135,7 @@ src/algorithms/invert.h src/dialogs/colorselector.h src/dialogs/configdialog.h - src/dialogs/cylindereditor.h + src/dialogs/circularprimitiveeditor.h src/dialogs/externalprogrampathdialog.h src/dialogs/generateprimitivedialog.h src/dialogs/ldrawpathdialog.h @@ -188,7 +188,7 @@ src/dialogs/colorselector.ui src/dialogs/configdialog.ui src/dialogs/covererdialog.ui - src/dialogs/cylindereditor.ui + src/dialogs/circularprimitiveeditor.ui src/dialogs/edger2dialog.ui src/dialogs/editrawdialog.ui src/dialogs/externalprogrampathdialog.ui
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dialogs/circularprimitiveeditor.cpp Sun Jun 17 16:13:24 2018 +0300 @@ -0,0 +1,186 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2018 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "circularprimitiveeditor.h" +#include "ui_circularprimitiveeditor.h" +#include "../primitives.h" + +// offsetof doesn't work if this doesn't hold true +static_assert(std::is_standard_layout<Ui_CircularPrimitiveEditor>::value, "UI type is not of standard layout"); + +// Contains offsets to radio buttons and the choice they represent. +static const struct +{ + std::ptrdiff_t offset; + PrimitiveModel::Type primitiveType; + + QRadioButton* resolve(Ui_CircularPrimitiveEditor& ui) const + { + // Find the radio button by pointer arithmetic + return *reinterpret_cast<QRadioButton**>(reinterpret_cast<char*>(&ui) + offset); + } +} radioButtonMap[] = { +#define MAP_RADIO_BUTTON(widget, type) { offsetof(Ui_CircularPrimitiveEditor, widget), PrimitiveModel::type } + MAP_RADIO_BUTTON(circle, Circle), + MAP_RADIO_BUTTON(cylinder, Cylinder), + MAP_RADIO_BUTTON(disc, Disc), + MAP_RADIO_BUTTON(discNegative, DiscNegative), +#undef MAP_RADIO_BUTTON +}; + +/* + * Constructs a new circular primitive editor and sets up connections. + */ +CircularPrimitiveEditor::CircularPrimitiveEditor(LDCircularPrimitive* primitive, QWidget* parent) : + QDialog {parent}, + ui {*new Ui_CircularPrimitiveEditor}, + primitive {primitive} +{ + ui.setupUi(this); + + // Set the initial values of the dialog + updateWidgets(); + + if (primitive) + { + // Store the original state of the object. If the user presses "Reset" then the object is restored + // from this archive. + Serializer serializer {originalState, Serializer::Store}; + primitive->serialize(serializer); + } + + for (const auto& mapping : ::radioButtonMap) + { + QRadioButton* button = mapping.resolve(ui); + + // If the radio button gets checked, update the type of the circular primitive. + connect( + button, + &QRadioButton::toggled, + [&](bool checked) + { + if (checked and this->primitive) + this->primitive->setPrimitiveType(mapping.primitiveType); + } + ); + } + + // Connect various widgets so that changing them changes the primitive object. + connect( + ui.segments, + qOverload<int>(&QSpinBox::valueChanged), + [&](int newSegments) + { + if (this->primitive) + this->primitive->setSegments(newSegments); + } + ); + connect( + ui.divisions, + &QComboBox::currentTextChanged, + [&](const QString& newDivisions) + { + if (this->primitive) + this->primitive->setDivisions(newDivisions.toInt()); + } + ); + connect( + ui.color, + &ColorButton::colorChanged, + [&](LDColor newColor) + { + if (this->primitive) + this->primitive->setColor(newColor); + } + ); + connect( + ui.matrix, + &MatrixEditor::matrixChanged, + [&](const QMatrix4x4& newMatrix) + { + if (this->primitive) + this->primitive->setTransformationMatrix(newMatrix); + } + ); + // Connect the reset button, "reset button" here meaning any button with the reset role. + connect( + ui.buttonBox, + &QDialogButtonBox::clicked, + [&](QAbstractButton* button) + { + if (ui.buttonBox->buttonRole(button) == QDialogButtonBox::ResetRole) + reset(); + } + ); + + if (this->primitive) + { + // If the primitive is changed by some other thing (e.g. by resetting it), update the widgets. + connect(this->primitive, &LDObject::modified, this, &CircularPrimitiveEditor::updateWidgets); + + // If the object is deleted, then destroy the dialog. + connect(this->primitive, &LDObject::destroyed, this, &QDialog::reject); + } +} + +/* + * Frees the user interface memory after the dialog is destroyed. + */ +CircularPrimitiveEditor::~CircularPrimitiveEditor() +{ + delete &ui; +} + +/* + * Updates the widgets of the editor to reflect the properites of the object being modified. + */ +void CircularPrimitiveEditor::updateWidgets() +{ + setEnabled(primitive != nullptr); + + if (primitive) + { + for (const auto& mapping : ::radioButtonMap) + { + // Choose the correct radio button + QRadioButton* button = mapping.resolve(ui); + withSignalsBlocked(button, [&]() + { + button->setChecked(primitive->primitiveType() == mapping.primitiveType); + }); + } + + // Set the values of the form. + withSignalsBlocked(ui.segments, [&](){ ui.segments->setValue(primitive->segments()); }); + withSignalsBlocked(ui.divisions, [&]() + { + ui.divisions->setCurrentText(QString::number(primitive->divisions())); + }); + withSignalsBlocked(ui.color, [&](){ ui.color->setColor(primitive->color()); }); + withSignalsBlocked(ui.matrix, [&](){ ui.matrix->setMatrix(primitive->transformationMatrix()); }); + } +} + +/* + * Resets the object being modified. The object will emit a signal that is connected to updateWidgets. + */ +void CircularPrimitiveEditor::reset() +{ + if (primitive) + primitive->restore(originalState); // Restoring does not change 'originalState' +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dialogs/circularprimitiveeditor.h Sun Jun 17 16:13:24 2018 +0300 @@ -0,0 +1,38 @@ +/* + * LDForge: LDraw parts authoring CAD + * Copyright (C) 2013 - 2018 Teemu Piippo + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#pragma once +#include <QDialog> +#include "../linetypes/circularprimitive.h" + +class CircularPrimitiveEditor : public QDialog +{ + Q_OBJECT + +public: + explicit CircularPrimitiveEditor(LDCircularPrimitive* primitive = nullptr, QWidget* parent = nullptr); + ~CircularPrimitiveEditor(); + +private: + Q_SLOT void updateWidgets(); + Q_SLOT void reset(); + + class Ui_CircularPrimitiveEditor& ui; + LDCircularPrimitive* primitive; + LDObjectState originalState; +};
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/dialogs/circularprimitiveeditor.ui Sun Jun 17 16:13:24 2018 +0300 @@ -0,0 +1,180 @@ +<?xml version="1.0" encoding="UTF-8"?> +<ui version="4.0"> + <class>CircularPrimitiveEditor</class> + <widget class="QDialog" name="CircularPrimitiveEditor"> + <property name="geometry"> + <rect> + <x>0</x> + <y>0</y> + <width>579</width> + <height>531</height> + </rect> + </property> + <property name="windowTitle"> + <string>Dialog</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout_2" stretch="0,0,1,0"> + <item> + <layout class="QFormLayout" name="formLayout_2"> + <item row="0" column="0"> + <widget class="QLabel" name="label_2"> + <property name="text"> + <string>Segments:</string> + </property> + </widget> + </item> + <item row="0" column="1"> + <widget class="QSpinBox" name="segments"/> + </item> + <item row="1" column="0"> + <widget class="QLabel" name="label"> + <property name="text"> + <string>Divisions:</string> + </property> + </widget> + </item> + <item row="1" column="1"> + <widget class="QComboBox" name="divisions"> + <item> + <property name="text"> + <string>8</string> + </property> + </item> + <item> + <property name="text"> + <string>16</string> + </property> + </item> + <item> + <property name="text"> + <string>48</string> + </property> + </item> + </widget> + </item> + <item row="2" column="0"> + <widget class="QLabel" name="label_3"> + <property name="text"> + <string>Colour:</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="ColorButton" name="color"> + <property name="text"> + <string/> + </property> + </widget> + </item> + </layout> + </item> + <item> + <widget class="QGroupBox" name="groupBox_2"> + <property name="title"> + <string>Type</string> + </property> + <layout class="QFormLayout" name="formLayout"> + <item row="3" column="0"> + <widget class="QRadioButton" name="disc"> + <property name="text"> + <string>Disc</string> + </property> + </widget> + </item> + <item row="3" column="1"> + <widget class="QRadioButton" name="discNegative"> + <property name="text"> + <string>Disc negati&ve</string> + </property> + </widget> + </item> + <item row="2" column="0"> + <widget class="QRadioButton" name="circle"> + <property name="text"> + <string>Circle</string> + </property> + </widget> + </item> + <item row="2" column="1"> + <widget class="QRadioButton" name="cylinder"> + <property name="text"> + <string>C&ylinder</string> + </property> + </widget> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QGroupBox" name="groupBox"> + <property name="title"> + <string>Transformation</string> + </property> + <layout class="QVBoxLayout" name="verticalLayout"> + <item> + <widget class="MatrixEditor" name="matrix" native="true"/> + </item> + </layout> + </widget> + </item> + <item> + <widget class="QDialogButtonBox" name="buttonBox"> + <property name="orientation"> + <enum>Qt::Horizontal</enum> + </property> + <property name="standardButtons"> + <set>QDialogButtonBox::Close|QDialogButtonBox::Reset</set> + </property> + </widget> + </item> + </layout> + </widget> + <customwidgets> + <customwidget> + <class>MatrixEditor</class> + <extends>QWidget</extends> + <header>widgets/matrixeditor.h</header> + <container>1</container> + </customwidget> + <customwidget> + <class>ColorButton</class> + <extends>QPushButton</extends> + <header>widgets/colorbutton.h</header> + </customwidget> + </customwidgets> + <resources/> + <connections> + <connection> + <sender>buttonBox</sender> + <signal>accepted()</signal> + <receiver>CircularPrimitiveEditor</receiver> + <slot>accept()</slot> + <hints> + <hint type="sourcelabel"> + <x>248</x> + <y>254</y> + </hint> + <hint type="destinationlabel"> + <x>157</x> + <y>274</y> + </hint> + </hints> + </connection> + <connection> + <sender>buttonBox</sender> + <signal>rejected()</signal> + <receiver>CircularPrimitiveEditor</receiver> + <slot>reject()</slot> + <hints> + <hint type="sourcelabel"> + <x>316</x> + <y>260</y> + </hint> + <hint type="destinationlabel"> + <x>286</x> + <y>274</y> + </hint> + </hints> + </connection> + </connections> +</ui>
--- a/src/dialogs/cylindereditor.cpp Sun Jun 17 14:06:03 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -#include "cylindereditor.h" -#include "ui_cylindereditor.h" - -CylinderEditor::CylinderEditor(QWidget* parent) : - QDialog {parent}, - ui {*new Ui_CylinderEditor} -{ - ui.setupUi(this); -} - -CylinderEditor::~CylinderEditor() -{ - delete &ui; -}
--- a/src/dialogs/cylindereditor.h Sun Jun 17 14:06:03 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,14 +0,0 @@ -#pragma once -#include <QDialog> - -class CylinderEditor : public QDialog -{ - Q_OBJECT - -public: - explicit CylinderEditor(QWidget* parent = nullptr); - ~CylinderEditor(); - -private: - class Ui_CylinderEditor& ui; -};
--- a/src/dialogs/cylindereditor.ui Sun Jun 17 14:06:03 2018 +0300 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,168 +0,0 @@ -<?xml version="1.0" encoding="UTF-8"?> -<ui version="4.0"> - <class>CylinderEditor</class> - <widget class="QDialog" name="CylinderEditor"> - <property name="geometry"> - <rect> - <x>0</x> - <y>0</y> - <width>656</width> - <height>659</height> - </rect> - </property> - <property name="windowTitle"> - <string>Dialog</string> - </property> - <widget class="QDialogButtonBox" name="buttonBox"> - <property name="geometry"> - <rect> - <x>190</x> - <y>570</y> - <width>341</width> - <height>32</height> - </rect> - </property> - <property name="orientation"> - <enum>Qt::Horizontal</enum> - </property> - <property name="standardButtons"> - <set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set> - </property> - </widget> - <widget class="QGroupBox" name="groupBox"> - <property name="geometry"> - <rect> - <x>130</x> - <y>270</y> - <width>381</width> - <height>251</height> - </rect> - </property> - <property name="title"> - <string>Transformation</string> - </property> - <layout class="QVBoxLayout" name="verticalLayout"> - <item> - <widget class="MatrixEditor" name="matrixEditor" native="true"/> - </item> - </layout> - </widget> - <widget class="QComboBox" name="divisions"> - <property name="geometry"> - <rect> - <x>200</x> - <y>110</y> - <width>76</width> - <height>25</height> - </rect> - </property> - <item> - <property name="text"> - <string>8</string> - </property> - </item> - <item> - <property name="text"> - <string>16</string> - </property> - </item> - <item> - <property name="text"> - <string>48</string> - </property> - </item> - </widget> - <widget class="QLabel" name="label"> - <property name="geometry"> - <rect> - <x>80</x> - <y>110</y> - <width>81</width> - <height>17</height> - </rect> - </property> - <property name="text"> - <string>Divisions:</string> - </property> - </widget> - <widget class="QLabel" name="label_2"> - <property name="geometry"> - <rect> - <x>90</x> - <y>70</y> - <width>63</width> - <height>17</height> - </rect> - </property> - <property name="text"> - <string>Segments:</string> - </property> - </widget> - <widget class="QSpinBox" name="segments"> - <property name="geometry"> - <rect> - <x>200</x> - <y>60</y> - <width>47</width> - <height>27</height> - </rect> - </property> - </widget> - <widget class="QLabel" name="label_3"> - <property name="geometry"> - <rect> - <x>90</x> - <y>150</y> - <width>63</width> - <height>17</height> - </rect> - </property> - <property name="text"> - <string>Colour:</string> - </property> - </widget> - </widget> - <customwidgets> - <customwidget> - <class>MatrixEditor</class> - <extends>QWidget</extends> - <header>widgets/matrixeditor.h</header> - <container>1</container> - </customwidget> - </customwidgets> - <resources/> - <connections> - <connection> - <sender>buttonBox</sender> - <signal>accepted()</signal> - <receiver>CylinderEditor</receiver> - <slot>accept()</slot> - <hints> - <hint type="sourcelabel"> - <x>248</x> - <y>254</y> - </hint> - <hint type="destinationlabel"> - <x>157</x> - <y>274</y> - </hint> - </hints> - </connection> - <connection> - <sender>buttonBox</sender> - <signal>rejected()</signal> - <receiver>CylinderEditor</receiver> - <slot>reject()</slot> - <hints> - <hint type="sourcelabel"> - <x>316</x> - <y>260</y> - </hint> - <hint type="destinationlabel"> - <x>286</x> - <y>274</y> - </hint> - </hints> - </connection> - </connections> -</ui>
--- a/src/guiutilities.cpp Sun Jun 17 14:06:03 2018 +0300 +++ b/src/guiutilities.cpp Sun Jun 17 16:13:24 2018 +0300 @@ -27,7 +27,9 @@ #include "mainwindow.h" #include "linetypes/modelobject.h" #include "linetypes/comment.h" +#include "linetypes/circularprimitive.h" #include "dialogs/subfilereferenceeditor.h" +#include "dialogs/circularprimitiveeditor.h" #include "widgets/vertexobjecteditor.h" GuiUtilities::GuiUtilities (QObject* parent) : @@ -181,6 +183,12 @@ editor.setPrimitivesTree(parent->primitives()); editor.exec(); } + else if (object->type() == LDObjectType::CircularPrimitive) + { + LDCircularPrimitive* primitive = static_cast<LDCircularPrimitive*>(object); + CircularPrimitiveEditor editor {primitive, parent}; + editor.exec(); + } else if (object->type() == LDObjectType::Comment) { LDComment* comment = static_cast<LDComment*>(object);
--- a/src/linetypes/circularprimitive.cpp Sun Jun 17 14:06:03 2018 +0300 +++ b/src/linetypes/circularprimitive.cpp Sun Jun 17 16:13:24 2018 +0300 @@ -179,6 +179,36 @@ return result; } +PrimitiveModel::Type LDCircularPrimitive::primitiveType() const +{ + return m_type; +} + +void LDCircularPrimitive::setPrimitiveType(PrimitiveModel::Type newType) +{ + changeProperty(&m_type, newType); +} + +int LDCircularPrimitive::segments() const +{ + return m_segments; +} + +void LDCircularPrimitive::setSegments(int newSegments) +{ + changeProperty(&m_segments, newSegments); +} + +int LDCircularPrimitive::divisions() const +{ + return m_divisions; +} + +void LDCircularPrimitive::setDivisions(int newDivisions) +{ + changeProperty(&m_divisions, newDivisions); +} + int LDCircularPrimitive::triangleCount(DocumentManager*) const { switch (m_type)
--- a/src/linetypes/circularprimitive.h Sun Jun 17 14:06:03 2018 +0300 +++ b/src/linetypes/circularprimitive.h Sun Jun 17 16:13:24 2018 +0300 @@ -28,6 +28,12 @@ ) override; QVector<LDPolygon> rasterizePolygons(DocumentManager* context, Winding parentWinding) override; QString objectListText() const override; + PrimitiveModel::Type primitiveType() const; + void setPrimitiveType(PrimitiveModel::Type newType); + int segments() const; + void setSegments(int newSegments); + int divisions() const; + void setDivisions(int newDivisions); int triangleCount(DocumentManager*) const override; QString iconName() const override; void serialize(class Serializer& serializer) override;
--- a/src/linetypes/modelobject.cpp Sun Jun 17 14:06:03 2018 +0300 +++ b/src/linetypes/modelobject.cpp Sun Jun 17 16:13:24 2018 +0300 @@ -596,6 +596,14 @@ serializer << m_coords[3]; } +void LDObject::restore(LDObjectState& archive) +{ + Serializer restorer {archive, Serializer::Restore}; + Serializer::Archive before = Serializer::store(this); + serialize(restorer); + emit modified(before, Serializer::store(this)); +} + void LDMatrixObject::serialize(Serializer& serializer) { LDObject::serialize(serializer);
--- a/src/linetypes/modelobject.h Sun Jun 17 14:06:03 2018 +0300 +++ b/src/linetypes/modelobject.h Sun Jun 17 16:13:24 2018 +0300 @@ -89,6 +89,7 @@ virtual QString iconName() const = 0; const Vertex& vertex (int i) const; virtual void serialize(class Serializer& serializer); + void restore(LDObjectState& archive); static LDObject* newFromType(LDObjectType type);
--- a/src/widgets/matrixeditor.cpp Sun Jun 17 14:06:03 2018 +0300 +++ b/src/widgets/matrixeditor.cpp Sun Jun 17 16:13:24 2018 +0300 @@ -15,7 +15,7 @@ matrixCell(i, j), qOverload<double>(&QDoubleSpinBox::valueChanged), this, - &MatrixEditor::matrixChanged + &MatrixEditor::matrix3x3Changed ); } @@ -28,6 +28,15 @@ &MatrixEditor::scalingChanged ); } + + for (QDoubleSpinBox* spinbox : {ui.positionX, ui.positionY, ui.positionZ}) + { + connect( + spinbox, + qOverload<double>(&QDoubleSpinBox::valueChanged), + [&](){ emit matrixChanged(this->matrix()); } + ); + } } MatrixEditor::MatrixEditor(QWidget* parent) : @@ -109,6 +118,7 @@ { cellWidget->setValue(cellValue); }); + emit matrixChanged(this->matrix()); } } @@ -135,7 +145,7 @@ /* * Updates the appropriate scaling vector element when a matrix cell is changed. */ -void MatrixEditor::matrixChanged() +void MatrixEditor::matrix3x3Changed() { QDoubleSpinBox* cellWidget = static_cast<QDoubleSpinBox*>(this->sender()); @@ -147,6 +157,7 @@ { spinbox->setValue(this->matrixScaling(column)); }); + emit matrixChanged(this->matrix()); } catch (const std::out_of_range&) {} } @@ -161,7 +172,13 @@ transformationMatrix(i, j) = this->matrixCell(i, j)->value(); } - transformationMatrix.translate(ui.positionX->value(), ui.positionY->value(), ui.positionZ->value()); + QVector4D translation { + (float) ui.positionX->value(), + (float) ui.positionY->value(), + (float) ui.positionZ->value(), + 0.0f + }; + transformationMatrix.setColumn(3, translation); return transformationMatrix; }
--- a/src/widgets/matrixeditor.h Sun Jun 17 14:06:03 2018 +0300 +++ b/src/widgets/matrixeditor.h Sun Jun 17 16:13:24 2018 +0300 @@ -17,9 +17,12 @@ QMatrix4x4 matrix() const; void setMatrix(const QMatrix4x4& matrix); +signals: + void matrixChanged(const QMatrix4x4& matrix); + private slots: void scalingChanged(); - void matrixChanged(); + void matrix3x3Changed(); private: QDoubleSpinBox* matrixCell(int row, int column) const;