Thu, 07 Feb 2019 13:45:07 +0200
stuff
/* * 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), MAP_RADIO_BUTTON(cylinderClosed, CylinderClosed), MAP_RADIO_BUTTON(cylinderOpen, CylinderOpen), MAP_RADIO_BUTTON(chord, Chord), #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.section, &CircularSectionEditor::sectionChanged, [&](const CircularSection& newSection) { if (this->primitive) this->primitive->setSection(newSection); } ); 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(ui.inverted, &QCheckBox::clicked, [&](bool checked) { if (this->primitive) this->primitive->setInverted(checked); }); connect(ui.rotateCcw, &QPushButton::clicked, [&](){ rotate(-1); }); connect(ui.rotateCw, &QPushButton::clicked, [&](){ rotate(1); }); // 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.section, [&](){ ui.section->setSection(primitive->section()); }); withSignalsBlocked(ui.color, [&](){ ui.color->setColor(primitive->color()); }); withSignalsBlocked(ui.matrix, [&](){ ui.matrix->setMatrix(primitive->transformationMatrix()); }); withSignalsBlocked(ui.inverted, [&](){ ui.inverted->setChecked(primitive->isInverted()); }); } } /* * 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' } /* * Rotates the primitive around its origin. The angle depends on its resolution and the passed 'factor' parameter. */ void CircularPrimitiveEditor::rotate(double factor) { if (primitive) { double angle = factor * 360.0 / primitive->divisions(); QMatrix4x4 matrix = primitive->transformationMatrix(); matrix.rotate(angle, 0, 1, 0); primitive->setTransformationMatrix(matrix); } }